mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor(scripts): migrate JavaScript tools to TypeScript (#121005)
* refactor(scripts): migrate JavaScript tools to TypeScript * fix(ci): keep changed-scope preflight zero-install * fix(ci): preserve zero-install script owners * fix(ci): complete script migration follow-through * fix(release): keep stable closeout zero-install * fix(scripts): preserve standalone execution boundaries * fix(scripts): repair standalone loader boundaries * fix(scripts): normalize gateway observation ids * fix(scripts): keep Docker packager standalone * test(scripts): preserve rebase cleanup helpers * test(sessions): use tracked temp directory
This commit is contained in:
committed by
GitHub
parent
2433fa213c
commit
c70aee247e
@@ -50,7 +50,7 @@ every human `Thanks @...` attribution.
|
|||||||
writing grouped prose:
|
writing grouped prose:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
|
node --import tsx .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
|
||||||
--base <base-tag> \
|
--base <base-tag> \
|
||||||
--target <target-ref> \
|
--target <target-ref> \
|
||||||
--main-ref origin/main \
|
--main-ref origin/main \
|
||||||
@@ -200,7 +200,7 @@ every human `Thanks @...` attribution.
|
|||||||
- after the manifest-driven rewrite, regenerate and verify the complete
|
- after the manifest-driven rewrite, regenerate and verify the complete
|
||||||
contribution record before committing:
|
contribution record before committing:
|
||||||
```bash
|
```bash
|
||||||
node .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
|
node --import tsx .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
|
||||||
--base <base-tag> \
|
--base <base-tag> \
|
||||||
--target <target-ref> \
|
--target <target-ref> \
|
||||||
--main-ref origin/main \
|
--main-ref origin/main \
|
||||||
@@ -228,7 +228,7 @@ every human `Thanks @...` attribution.
|
|||||||
- after the GitHub release or prerelease is published, verify every matching
|
- after the GitHub release or prerelease is published, verify every matching
|
||||||
release page against the same source section:
|
release page against the same source section:
|
||||||
```bash
|
```bash
|
||||||
node .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
|
node --import tsx .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
|
||||||
--base <base-tag> \
|
--base <base-tag> \
|
||||||
--target <target-ref> \
|
--target <target-ref> \
|
||||||
--version <YYYY.M.PATCH> \
|
--version <YYYY.M.PATCH> \
|
||||||
@@ -238,7 +238,7 @@ every human `Thanks @...` attribution.
|
|||||||
- add one `--release-tag` for every beta and stable page in the train; a
|
- add one `--release-tag` for every beta and stable page in the train; a
|
||||||
`### Release verification` tail is permitted, but any other body drift
|
`### Release verification` tail is permitted, but any other body drift
|
||||||
fails the check
|
fails the check
|
||||||
- `scripts/render-github-release-notes.mjs` is the canonical release-body
|
- `scripts/render-github-release-notes.mts` is the canonical release-body
|
||||||
renderer used by candidate validation, publish, and verification. When the
|
renderer used by candidate validation, publish, and verification. When the
|
||||||
complete `## YYYY.M.PATCH` section fits GitHub's 125,000-character limit and
|
complete `## YYYY.M.PATCH` section fits GitHub's 125,000-character limit and
|
||||||
the renderer's matching 125,000-byte safety ceiling, the body must contain
|
the renderer's matching 125,000-byte safety ceiling, the body must contain
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env -S node --import tsx
|
||||||
|
|
||||||
import { execFileSync, spawnSync } from "node:child_process";
|
import { execFileSync, spawnSync } from "node:child_process";
|
||||||
import {
|
import {
|
||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
parseShippedBaselineExclusions,
|
parseShippedBaselineExclusions,
|
||||||
releaseNotesVersionForTag,
|
releaseNotesVersionForTag,
|
||||||
verifyGithubReleaseNotes,
|
verifyGithubReleaseNotes,
|
||||||
} from "../../../../scripts/render-github-release-notes.mjs";
|
} from "../../../../scripts/render-github-release-notes.mts";
|
||||||
|
|
||||||
const repo = "openclaw/openclaw";
|
const repo = "openclaw/openclaw";
|
||||||
const githubSnapshotSchemaVersion = 1;
|
const githubSnapshotSchemaVersion = 1;
|
||||||
@@ -78,7 +78,7 @@ function fail(message) {
|
|||||||
|
|
||||||
function printUsage() {
|
function printUsage() {
|
||||||
console.log(`Usage:
|
console.log(`Usage:
|
||||||
node .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \\
|
node --import tsx .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \\
|
||||||
--base <tag-or-sha> --target <tag-or-sha> --version <version> [options]
|
--base <tag-or-sha> --target <tag-or-sha> --version <version> [options]
|
||||||
|
|
||||||
Required:
|
Required:
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ For workflow-only or docs/skill-only changes in a Codex worktree:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
node scripts/run-vitest.mjs test/scripts/ci-workflow-guards.test.ts
|
node scripts/run-vitest.mjs test/scripts/ci-workflow-guards.test.ts
|
||||||
node scripts/check-workflows.mjs
|
node --import tsx scripts/check-workflows.mts
|
||||||
node scripts/docs-list.js
|
node scripts/docs-list.js
|
||||||
./node_modules/.bin/oxfmt --check .github/workflows/ci.yml .github/workflows/codeql-critical-quality.yml docs/ci.md test/scripts/ci-workflow-guards.test.ts .agents/skills/openclaw-ci-limits/SKILL.md .agents/skills/openclaw-ci-limits/agents/openai.yaml
|
./node_modules/.bin/oxfmt --check .github/workflows/ci.yml .github/workflows/codeql-critical-quality.yml docs/ci.md test/scripts/ci-workflow-guards.test.ts .agents/skills/openclaw-ci-limits/SKILL.md .agents/skills/openclaw-ci-limits/agents/openai.yaml
|
||||||
git diff --check
|
git diff --check
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Keep `/Users/steipete/openclaw` a read-only-to-the-agent deployment mirror: clea
|
|||||||
1. Run the deterministic updater and retain its JSON:
|
1. Run the deterministic updater and retain its JSON:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node .agents/skills/openclaw-live-updater/scripts/update-main.mjs
|
node --import tsx .agents/skills/openclaw-live-updater/scripts/update-main.mjs
|
||||||
```
|
```
|
||||||
|
|
||||||
Stop on any failed invariant. Do not repair the mirror destructively. The helper holds one checkout-scoped lock across update, build, Gateway proof, and Mac work. A concurrent heartbeat returns `reason: "overlap"`; it must not start another build. A dead owner lock may be recovered, but unreadable or unsafe lock state fails closed.
|
Stop on any failed invariant. Do not repair the mirror destructively. The helper holds one checkout-scoped lock across update, build, Gateway proof, and Mac work. A concurrent heartbeat returns `reason: "overlap"`; it must not start another build. A dead owner lock may be recovered, but unreadable or unsafe lock state fails closed.
|
||||||
|
|||||||
@@ -24,16 +24,16 @@ import { isDirectRunUrl } from "../../../../scripts/lib/direct-run.mjs";
|
|||||||
import {
|
import {
|
||||||
BUILD_STAMP_FILE,
|
BUILD_STAMP_FILE,
|
||||||
RUNTIME_POSTBUILD_STAMP_FILE,
|
RUNTIME_POSTBUILD_STAMP_FILE,
|
||||||
} from "../../../../scripts/lib/local-build-metadata.mjs";
|
} from "../../../../scripts/lib/local-build-metadata.mts";
|
||||||
import { runManagedCommand } from "../../../../scripts/lib/managed-child-process.mjs";
|
import { runManagedCommand } from "../../../../scripts/lib/managed-child-process.mts";
|
||||||
import {
|
import {
|
||||||
runNodeConfigFiles,
|
runNodeConfigFiles,
|
||||||
runNodeSourceRoots,
|
runNodeSourceRoots,
|
||||||
} from "../../../../scripts/run-node-watch-paths.mjs";
|
} from "../../../../scripts/run-node-watch-paths.mts";
|
||||||
import {
|
import {
|
||||||
resolveBuildRequirement,
|
resolveBuildRequirement,
|
||||||
resolveRuntimePostBuildRequirement,
|
resolveRuntimePostBuildRequirement,
|
||||||
} from "../../../../scripts/run-node.mjs";
|
} from "../../../../scripts/run-node.mts";
|
||||||
|
|
||||||
const DEFAULT_CHECKOUT = "/Users/steipete/openclaw";
|
const DEFAULT_CHECKOUT = "/Users/steipete/openclaw";
|
||||||
const DEFAULT_EXPECTED_ORIGIN = "openclaw/openclaw";
|
const DEFAULT_EXPECTED_ORIGIN = "openclaw/openclaw";
|
||||||
@@ -90,6 +90,68 @@ exec "$@"
|
|||||||
const DEPENDENCY_INPUT_RE =
|
const DEPENDENCY_INPUT_RE =
|
||||||
/^(?:\.npmrc$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|patches\/)|(?:^|\/)package\.json$/u;
|
/^(?:\.npmrc$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|patches\/)|(?:^|\/)package\.json$/u;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} GatewayDeploymentRef
|
||||||
|
* @property {string} entrypoint
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The fields required when the updater invokes the managed Gateway CLI.
|
||||||
|
* LaunchAgent inspection returns a richer object, while focused probes may
|
||||||
|
* provide only this execution view.
|
||||||
|
*
|
||||||
|
* @typedef {object} GatewayCliDeploymentBase
|
||||||
|
* @property {string} configPath
|
||||||
|
* @property {string} entrypoint
|
||||||
|
* @property {string} executable
|
||||||
|
* @property {string[]} invocationPrefix
|
||||||
|
* @property {number} port
|
||||||
|
* @property {Record<string, string>} [serviceEnvironment]
|
||||||
|
* @property {string | null} [workingDirectory]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {GatewayCliDeploymentBase & {
|
||||||
|
* envFilePath?: null,
|
||||||
|
* runtime?: string,
|
||||||
|
* wrapperPath?: null,
|
||||||
|
* }} DirectGatewayCliDeployment
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {GatewayCliDeploymentBase & {
|
||||||
|
* envFilePath: string,
|
||||||
|
* runtime: string,
|
||||||
|
* wrapperPath: string,
|
||||||
|
* }} WrappedGatewayCliDeployment
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @typedef {DirectGatewayCliDeployment | WrappedGatewayCliDeployment} GatewayCliDeployment */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stable identity fields used to verify a LaunchAgent retarget.
|
||||||
|
* Repointing deliberately does not require the execution-only fields above.
|
||||||
|
*
|
||||||
|
* @typedef {object} GatewayRepointDeployment
|
||||||
|
* @property {string} configPath
|
||||||
|
* @property {string} entrypoint
|
||||||
|
* @property {string} label
|
||||||
|
* @property {number} port
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The updater's established test/API result keeps owner details extensible,
|
||||||
|
* while naming the fields every completed maintenance run exposes.
|
||||||
|
*
|
||||||
|
* @typedef {Record<string, unknown> & {
|
||||||
|
* actions: Record<string, unknown>,
|
||||||
|
* buildBefore: Record<string, unknown>,
|
||||||
|
* changedPaths?: string[],
|
||||||
|
* macTarget?: Record<string, unknown>,
|
||||||
|
* release?: () => void,
|
||||||
|
* }} UpdateResult
|
||||||
|
*/
|
||||||
|
|
||||||
class UpdateInvariantError extends Error {
|
class UpdateInvariantError extends Error {
|
||||||
constructor(code, message, details, options) {
|
constructor(code, message, details, options) {
|
||||||
super(message, options);
|
super(message, options);
|
||||||
@@ -115,7 +177,11 @@ class UpdateCommandError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Re-throw the original runtime value while exposing the Error contract to type-aware lint. */
|
/**
|
||||||
|
* Re-throw the original runtime value while exposing the Error contract to type-aware lint.
|
||||||
|
*
|
||||||
|
* @returns {never}
|
||||||
|
*/
|
||||||
function throwPreservingValue(value) {
|
function throwPreservingValue(value) {
|
||||||
throw /** @type {Error} */ (value);
|
throw /** @type {Error} */ (value);
|
||||||
}
|
}
|
||||||
@@ -1377,6 +1443,13 @@ function inspectManagedGatewayDeployment(checkout) {
|
|||||||
return readManagedGatewayLaunchAgent(checkout);
|
return readManagedGatewayLaunchAgent(checkout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} checkout
|
||||||
|
* @param {GatewayRepointDeployment} deployment
|
||||||
|
* @param {(deployment: GatewayRepointDeployment, replacement: string) => void} replaceEntrypoint
|
||||||
|
* @param {(checkout: string) => GatewayRepointDeployment | null} [inspectDeployment]
|
||||||
|
* @returns {GatewayRepointDeployment & { changed: boolean, previousEntrypoint?: string }}
|
||||||
|
*/
|
||||||
export function repointManagedGatewayDeployment(
|
export function repointManagedGatewayDeployment(
|
||||||
checkout,
|
checkout,
|
||||||
deployment,
|
deployment,
|
||||||
@@ -1631,6 +1704,13 @@ export function parseLaunchctlArguments(output) {
|
|||||||
: [];
|
: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} checkout
|
||||||
|
* @param {string[]} args
|
||||||
|
* @param {GatewayCliDeployment | null | undefined} deployment
|
||||||
|
* @param {{ stderr?: "inherit" | "pipe", timeoutMs?: number }} [options]
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
export function runBuiltGatewayCli(checkout, args, deployment, options = {}) {
|
export function runBuiltGatewayCli(checkout, args, deployment, options = {}) {
|
||||||
const observedDeployment = deployment ?? readManagedGatewayLaunchAgent(checkout);
|
const observedDeployment = deployment ?? readManagedGatewayLaunchAgent(checkout);
|
||||||
const sourceEntrypoint = path.join(checkout, "dist/index.js");
|
const sourceEntrypoint = path.join(checkout, "dist/index.js");
|
||||||
@@ -1738,6 +1818,13 @@ export function runBuiltGatewayCli(checkout, args, deployment, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} checkout
|
||||||
|
* @param {string} method
|
||||||
|
* @param {Record<string, unknown>} params
|
||||||
|
* @param {GatewayCliDeployment | null | undefined} deployment
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
export function runBuiltGatewayCall(checkout, method, params, deployment) {
|
export function runBuiltGatewayCall(checkout, method, params, deployment) {
|
||||||
const managedDeployment = deployment ?? readManagedGatewayLaunchAgent(checkout);
|
const managedDeployment = deployment ?? readManagedGatewayLaunchAgent(checkout);
|
||||||
return runBuiltGatewayCli(
|
return runBuiltGatewayCli(
|
||||||
@@ -1756,6 +1843,11 @@ export function runBuiltGatewayCall(checkout, method, params, deployment) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} checkout
|
||||||
|
* @param {(checkout: string, method: string, params: { requestId: string }, deployment: GatewayDeploymentRef | null) => string} [callGateway]
|
||||||
|
* @param {GatewayDeploymentRef | null} [deployment]
|
||||||
|
*/
|
||||||
export function prepareGatewaySuspension(
|
export function prepareGatewaySuspension(
|
||||||
checkout,
|
checkout,
|
||||||
callGateway = runBuiltGatewayCall,
|
callGateway = runBuiltGatewayCall,
|
||||||
@@ -2629,6 +2721,23 @@ function defaultSleep(ms) {
|
|||||||
return delay(ms);
|
return delay(ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {(command: string, args: string[], checkout: string, options?: Record<string, unknown>) => unknown | Promise<unknown>} runCommand
|
||||||
|
* @param {string} checkout
|
||||||
|
* @param {string} expectedSha
|
||||||
|
* @param {(ms: number) => void | Promise<void>} [sleep]
|
||||||
|
* @param {GatewayCliDeployment | null} [deployment]
|
||||||
|
* @param {{
|
||||||
|
* now?: () => number,
|
||||||
|
* probeMilestones?: (deployment: GatewayCliDeployment) => {
|
||||||
|
* listenerReady: boolean,
|
||||||
|
* healthzReady: boolean,
|
||||||
|
* readyzReady: boolean,
|
||||||
|
* },
|
||||||
|
* timing?: Record<string, unknown>,
|
||||||
|
* }} [options]
|
||||||
|
* @returns {Promise<Record<string, unknown>>}
|
||||||
|
*/
|
||||||
export async function verifyGatewayReadiness(
|
export async function verifyGatewayReadiness(
|
||||||
runCommand,
|
runCommand,
|
||||||
checkout,
|
checkout,
|
||||||
@@ -2825,6 +2934,12 @@ function summarizeGatewayLogAudit(entries) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} output
|
||||||
|
* @param {number} sinceMs
|
||||||
|
* @param {string | null} [sourceRoot]
|
||||||
|
* @param {string[] | null} [managedSourceRoots]
|
||||||
|
*/
|
||||||
export function parseGatewayLogAudit(output, sinceMs, sourceRoot = null, managedSourceRoots = []) {
|
export function parseGatewayLogAudit(output, sinceMs, sourceRoot = null, managedSourceRoots = []) {
|
||||||
const entries = parseGatewayLogEntries(output, sinceMs).filter((entry) =>
|
const entries = parseGatewayLogEntries(output, sinceMs).filter((entry) =>
|
||||||
isCurrentGatewayLogSource(entry.source, sourceRoot, managedSourceRoots),
|
isCurrentGatewayLogSource(entry.source, sourceRoot, managedSourceRoots),
|
||||||
@@ -2888,6 +3003,10 @@ export function resolveManagedPluginSourceRoots(report) {
|
|||||||
return roots;
|
return roots;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} checkout
|
||||||
|
* @param {GatewayDeploymentRef | null | undefined} deployment
|
||||||
|
*/
|
||||||
export function resolveManagedGatewaySourceRoot(checkout, deployment) {
|
export function resolveManagedGatewaySourceRoot(checkout, deployment) {
|
||||||
return typeof deployment?.entrypoint === "string" && deployment.entrypoint.length > 0
|
return typeof deployment?.entrypoint === "string" && deployment.entrypoint.length > 0
|
||||||
? path.dirname(path.resolve(deployment.entrypoint))
|
? path.dirname(path.resolve(deployment.entrypoint))
|
||||||
@@ -3026,6 +3145,12 @@ async function defaultVerifyMacTarget(checkout) {
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @overload
|
||||||
|
* @param {Record<string, unknown>} options
|
||||||
|
* @param {Record<string, unknown>} [dependencies]
|
||||||
|
* @returns {Promise<UpdateResult>}
|
||||||
|
*/
|
||||||
export async function maintainMain(options, dependencies = {}) {
|
export async function maintainMain(options, dependencies = {}) {
|
||||||
const lock = acquireMaintenanceLock(options.checkout, options.lockPath);
|
const lock = acquireMaintenanceLock(options.checkout, options.lockPath);
|
||||||
if (!lock.acquired) {
|
if (!lock.acquired) {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ For **runtime fixes** (e.g., closure leaks in long-running services like the gat
|
|||||||
```
|
```
|
||||||
|
|
||||||
- For a suspected file, rerun that file with one worker and collect wall/RSS evidence: `/usr/bin/time -l pnpm test <file> --maxWorkers=1 --reporter=verbose`.
|
- For a suspected file, rerun that file with one worker and collect wall/RSS evidence: `/usr/bin/time -l pnpm test <file> --maxWorkers=1 --reporter=verbose`.
|
||||||
- Current `pnpm test` execution is planned by `scripts/test-projects.mjs`. Record the printed Vitest config or shard and preserve that shape when the report is configuration- or worker-budget-specific.
|
- Current `pnpm test` execution is planned by `scripts/test-projects.mts`. Record the printed Vitest config or shard and preserve that shape when the report is configuration- or worker-budget-specific.
|
||||||
|
|
||||||
2. Collect the strongest available heap evidence.
|
2. Collect the strongest available heap evidence.
|
||||||
- Run `pnpm test:perf:profile:runner -- --output-dir .artifacts/test-perf/vitest-runner-profile -- <file>` for a CPU profile plus a sampling heap profile of the unit runner. Open the heap profile in DevTools and inspect the largest allocation families.
|
- Run `pnpm test:perf:profile:runner -- --output-dir .artifacts/test-perf/vitest-runner-profile -- <file>` for a CPU profile plus a sampling heap profile of the unit runner. Open the heap profile in DevTools and inspect the largest allocation families.
|
||||||
@@ -53,7 +53,7 @@ For **runtime fixes** (e.g., closure leaks in long-running services like the gat
|
|||||||
## Heuristics
|
## Heuristics
|
||||||
|
|
||||||
- Do not call everything a leak. Growth in a non-isolated shared Vitest project can be a worker-lifetime problem rather than an application object leak.
|
- Do not call everything a leak. Growth in a non-isolated shared Vitest project can be a worker-lifetime problem rather than an application object leak.
|
||||||
- `scripts/test-projects.mjs`, `scripts/test-group-report.mjs`, and `scripts/run-vitest-profile.mjs` are the current execution, grouped-RSS, and profile entrypoints.
|
- `scripts/test-projects.mts`, `scripts/test-group-report.mts`, and `scripts/run-vitest-profile.mts` are the current execution, grouped-RSS, and profile entrypoints.
|
||||||
- The `[test] starting ...` lines identify the Vitest config or shard to reproduce.
|
- The `[test] starting ...` lines identify the Vitest config or shard to reproduce.
|
||||||
- `.artifacts/vitest-shard-timings.json` stores config/shard durations for scheduling. It is not a file-level memory-hotspot or behavior manifest.
|
- `.artifacts/vitest-shard-timings.json` stores config/shard durations for scheduling. It is not a file-level memory-hotspot or behavior manifest.
|
||||||
- When the same retained object families grow across multiple intervals in the same worker PID, trust the snapshots over intuition, then confirm ambiguous calls with retainer evidence.
|
- When the same retained object families grow across multiple intervals in the same worker PID, trust the snapshots over intuition, then confirm ambiguous calls with retainer evidence.
|
||||||
|
|||||||
@@ -480,7 +480,7 @@ HEAD/worktree-bound manifest under git metadata for cutover review.
|
|||||||
credit from that PR's record on the same bullet.
|
credit from that PR's record on the same bullet.
|
||||||
- Changelog entries should be user-facing, not internal release-process notes.
|
- Changelog entries should be user-facing, not internal release-process notes.
|
||||||
- GitHub release and prerelease bodies use
|
- GitHub release and prerelease bodies use
|
||||||
`scripts/render-github-release-notes.mjs`. When the full matching
|
`scripts/render-github-release-notes.mts`. When the full matching
|
||||||
`CHANGELOG.md` version section fits GitHub's 125,000-character limit and
|
`CHANGELOG.md` version section fits GitHub's 125,000-character limit and
|
||||||
the renderer's matching 125,000-byte safety ceiling, publish the exact
|
the renderer's matching 125,000-byte safety ceiling, publish the exact
|
||||||
`## YYYY.M.PATCH` block through the line before the next level-2 heading,
|
`## YYYY.M.PATCH` block through the line before the next level-2 heading,
|
||||||
|
|||||||
@@ -482,7 +482,7 @@ runs:
|
|||||||
path: .artifacts/build-all-cache
|
path: .artifacts/build-all-cache
|
||||||
# Exact keys deduplicate concurrent jobs. Coarse restore supplies the
|
# Exact keys deduplicate concurrent jobs. Coarse restore supplies the
|
||||||
# newest declaration groups; build-all rehashes every group's inputs.
|
# newest declaration groups; build-all rehashes every group's inputs.
|
||||||
key: ${{ github.repository }}-build-all-v1-${{ inputs.build-all-cache-scope }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-${{ hashFiles('package.json', 'pnpm-lock.yaml', 'tsconfig*.json', 'tsdown*.config.ts', 'scripts/build-all.mjs', 'scripts/tsdown-build.mjs', 'scripts/lib/tsdown-*.mjs', 'scripts/lib/plugin-sdk-*', 'scripts/lib/bundled-plugin-*', 'scripts/lib/optional-bundled-clusters.mjs', 'src/**', 'packages/**', 'extensions/**') }}
|
key: ${{ github.repository }}-build-all-v1-${{ inputs.build-all-cache-scope }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-${{ hashFiles('package.json', 'pnpm-lock.yaml', 'tsconfig*.json', 'tsdown*.config.ts', 'scripts/build-all.mts', 'scripts/tsdown-build.mts', 'scripts/lib/tsdown-*.mts', 'scripts/lib/plugin-sdk-*', 'scripts/lib/bundled-plugin-*', 'scripts/lib/optional-bundled-clusters.mjs', 'src/**', 'packages/**', 'extensions/**') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ github.repository }}-build-all-v1-${{ inputs.build-all-cache-scope }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-
|
${{ github.repository }}-build-all-v1-${{ inputs.build-all-cache-scope }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ than Telegram-visible behavior`. Use this manifest shape and do not create
|
|||||||
`--sut-lane`/`--sut-repo-root` during `start`.
|
`--sut-lane`/`--sut-repo-root` during `start`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node scripts/mantis/build-telegram-desktop-proof-evidence.mjs \
|
node --import tsx scripts/mantis/build-telegram-desktop-proof-evidence.mts \
|
||||||
--output-dir "$MANTIS_OUTPUT_DIR" \
|
--output-dir "$MANTIS_OUTPUT_DIR" \
|
||||||
--baseline-repo-root "$GITHUB_WORKSPACE" \
|
--baseline-repo-root "$GITHUB_WORKSPACE" \
|
||||||
--baseline-output-dir <baseline-session-output-dir> \
|
--baseline-output-dir <baseline-session-output-dir> \
|
||||||
|
|||||||
+56
-38
@@ -59,6 +59,7 @@ concurrency:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||||
|
NODE_VERSION: "24.x"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Preflight: establish routing truth and job matrices once, then let real
|
# Preflight: establish routing truth and job matrices once, then let real
|
||||||
@@ -379,6 +380,19 @@ jobs:
|
|||||||
node scripts/ci-changed-scope.mjs --base "$BASE" --head "$HEAD_SHA"
|
node scripts/ci-changed-scope.mjs --base "$BASE" --head "$HEAD_SHA"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Setup manifest TypeScript runtime
|
||||||
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
|
||||||
|
- name: Setup manifest pnpm
|
||||||
|
uses: ./.github/actions/setup-pnpm-store-cache
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
|
||||||
|
- name: Install manifest dependencies
|
||||||
|
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
|
||||||
|
|
||||||
- name: Build CI manifest
|
- name: Build CI manifest
|
||||||
id: manifest
|
id: manifest
|
||||||
env:
|
env:
|
||||||
@@ -404,7 +418,7 @@ jobs:
|
|||||||
OPENCLAW_CI_REPOSITORY: ${{ github.repository }}
|
OPENCLAW_CI_REPOSITORY: ${{ github.repository }}
|
||||||
OPENCLAW_CI_EVENT_NAME: ${{ github.event_name }}
|
OPENCLAW_CI_EVENT_NAME: ${{ github.event_name }}
|
||||||
run: |
|
run: |
|
||||||
node --input-type=module <<'EOF'
|
node --import tsx --input-type=module <<'EOF'
|
||||||
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
||||||
|
|
||||||
const eventName = process.env.OPENCLAW_CI_EVENT_NAME ?? "";
|
const eventName = process.env.OPENCLAW_CI_EVENT_NAME ?? "";
|
||||||
@@ -423,7 +437,10 @@ jobs:
|
|||||||
const frozenTarget =
|
const frozenTarget =
|
||||||
eventName === "workflow_dispatch" && checkoutRevision !== workflowRevision;
|
eventName === "workflow_dispatch" && checkoutRevision !== workflowRevision;
|
||||||
|
|
||||||
const nodeTestPlan = await import("./scripts/lib/ci-node-test-plan.mjs");
|
const nodeTestPlanPath = existsSync("./scripts/lib/ci-node-test-plan.mts")
|
||||||
|
? "./scripts/lib/ci-node-test-plan.mts"
|
||||||
|
: "./scripts/lib/ci-node-test-plan.mjs";
|
||||||
|
const nodeTestPlan = await import(nodeTestPlanPath);
|
||||||
const createNodeTestPlan =
|
const createNodeTestPlan =
|
||||||
typeof nodeTestPlan.createNodeTestShardBundles === "function"
|
typeof nodeTestPlan.createNodeTestShardBundles === "function"
|
||||||
? nodeTestPlan.createNodeTestShardBundles
|
? nodeTestPlan.createNodeTestShardBundles
|
||||||
@@ -435,9 +452,14 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
let changedNodeTestPlan = {};
|
let changedNodeTestPlan = {};
|
||||||
if (existsSync("./scripts/lib/ci-changed-node-test-plan.mjs")) {
|
const changedNodeTestPlanPath = existsSync(
|
||||||
|
"./scripts/lib/ci-changed-node-test-plan.mts",
|
||||||
|
)
|
||||||
|
? "./scripts/lib/ci-changed-node-test-plan.mts"
|
||||||
|
: "./scripts/lib/ci-changed-node-test-plan.mjs";
|
||||||
|
if (existsSync(changedNodeTestPlanPath)) {
|
||||||
try {
|
try {
|
||||||
changedNodeTestPlan = await import("./scripts/lib/ci-changed-node-test-plan.mjs");
|
changedNodeTestPlan = await import(changedNodeTestPlanPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`Changed Node test planner import failed; using compact full suite: ${error}`);
|
console.warn(`Changed Node test planner import failed; using compact full suite: ${error}`);
|
||||||
}
|
}
|
||||||
@@ -453,7 +475,9 @@ jobs:
|
|||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
const channelContractPlan = await importTargetPlan(
|
const channelContractPlan = await importTargetPlan(
|
||||||
"./scripts/lib/channel-contract-test-plan.mjs",
|
existsSync("./scripts/lib/channel-contract-test-plan.mts")
|
||||||
|
? "./scripts/lib/channel-contract-test-plan.mts"
|
||||||
|
: "./scripts/lib/channel-contract-test-plan.mjs",
|
||||||
);
|
);
|
||||||
const createChannelContractTestShards =
|
const createChannelContractTestShards =
|
||||||
typeof channelContractPlan.createChannelContractTestShards === "function"
|
typeof channelContractPlan.createChannelContractTestShards === "function"
|
||||||
@@ -469,7 +493,9 @@ jobs:
|
|||||||
};
|
};
|
||||||
|
|
||||||
const pluginContractPlan = await importTargetPlan(
|
const pluginContractPlan = await importTargetPlan(
|
||||||
"./scripts/lib/plugin-contract-test-plan.mjs",
|
existsSync("./scripts/lib/plugin-contract-test-plan.mts")
|
||||||
|
? "./scripts/lib/plugin-contract-test-plan.mts"
|
||||||
|
: "./scripts/lib/plugin-contract-test-plan.mjs",
|
||||||
);
|
);
|
||||||
const createPluginContractTestShards =
|
const createPluginContractTestShards =
|
||||||
typeof pluginContractPlan.createPluginContractTestShards === "function"
|
typeof pluginContractPlan.createPluginContractTestShards === "function"
|
||||||
@@ -1124,7 +1150,7 @@ jobs:
|
|||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: .artifacts/build-all-cache
|
path: .artifacts/build-all-cache
|
||||||
key: ${{ runner.os }}-build-all-v4-${{ hashFiles('package.json', 'pnpm-lock.yaml', 'scripts/build-all.mjs', 'scripts/runtime-postbuild.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entries.mjs', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-private-local-only-subpaths.json', 'scripts/lib/plugin-sdk-deprecated-public-subpaths.json', 'scripts/lib/plugin-sdk-deprecated-barrel-subpaths.json', 'tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'src/**', 'packages/**', '!src/**/dist/**', '!src/**/node_modules/**', '!packages/**/dist/**', '!packages/**/node_modules/**') }}
|
key: ${{ runner.os }}-build-all-v4-${{ hashFiles('package.json', 'pnpm-lock.yaml', 'scripts/build-all.mts', 'scripts/runtime-postbuild.mjs', 'scripts/runtime-postbuild.mts', 'scripts/lib/tsx-cli-shim.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entries.mts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-private-local-only-subpaths.json', 'scripts/lib/plugin-sdk-deprecated-public-subpaths.json', 'scripts/lib/plugin-sdk-deprecated-barrel-subpaths.json', 'tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'src/**', 'packages/**', '!src/**/dist/**', '!src/**/node_modules/**', '!packages/**/dist/**', '!packages/**/node_modules/**') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-build-all-v4-
|
${{ runner.os }}-build-all-v4-
|
||||||
|
|
||||||
@@ -1305,7 +1331,7 @@ jobs:
|
|||||||
|
|
||||||
if [ "$RUN_GATEWAY_WATCH" = "true" ] && [ "$PARALLEL_GATEWAY_WATCH" = "true" ]; then
|
if [ "$RUN_GATEWAY_WATCH" = "true" ] && [ "$PARALLEL_GATEWAY_WATCH" = "true" ]; then
|
||||||
start_check "gateway-watch" \
|
start_check "gateway-watch" \
|
||||||
node scripts/check-gateway-watch-regression.mjs --skip-build
|
node --import tsx scripts/check-gateway-watch-regression.mts --skip-build
|
||||||
fi
|
fi
|
||||||
|
|
||||||
wait_checks
|
wait_checks
|
||||||
@@ -1314,7 +1340,7 @@ jobs:
|
|||||||
# starve the Gateway readiness deadline used by this regression gate.
|
# starve the Gateway readiness deadline used by this regression gate.
|
||||||
if [ "$RUN_GATEWAY_WATCH" = "true" ] && [ "$PARALLEL_GATEWAY_WATCH" != "true" ]; then
|
if [ "$RUN_GATEWAY_WATCH" = "true" ] && [ "$PARALLEL_GATEWAY_WATCH" != "true" ]; then
|
||||||
start_check "gateway-watch" \
|
start_check "gateway-watch" \
|
||||||
node scripts/check-gateway-watch-regression.mjs --skip-build
|
node --import tsx scripts/check-gateway-watch-regression.mts --skip-build
|
||||||
wait_checks
|
wait_checks
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1441,7 +1467,7 @@ jobs:
|
|||||||
# Install the managed browser revision pinned by the selected target instead.
|
# Install the managed browser revision pinned by the selected target instead.
|
||||||
pnpm --dir ui exec playwright install chromium
|
pnpm --dir ui exec playwright install chromium
|
||||||
else
|
else
|
||||||
node scripts/ensure-playwright-chromium.mjs
|
node --import tsx scripts/ensure-playwright-chromium.mts
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Lint Control UI window.open usage
|
- name: Lint Control UI window.open usage
|
||||||
@@ -1491,7 +1517,7 @@ jobs:
|
|||||||
use-actions-cache: ${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'true' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true') }}
|
use-actions-cache: ${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'true' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true') }}
|
||||||
|
|
||||||
- name: Install Playwright Chromium
|
- name: Install Playwright Chromium
|
||||||
run: node scripts/ensure-playwright-chromium.mjs
|
run: node --import tsx scripts/ensure-playwright-chromium.mts
|
||||||
|
|
||||||
- name: Test Control UI end-to-end
|
- name: Test Control UI end-to-end
|
||||||
run: >-
|
run: >-
|
||||||
@@ -1731,18 +1757,18 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
# Pack the public runtime before overlaying private QA artifacts so they cannot leak.
|
# Pack the public runtime before overlaying private QA artifacts so they cannot leak.
|
||||||
unset OPENCLAW_BUILD_PRIVATE_QA
|
unset OPENCLAW_BUILD_PRIVATE_QA
|
||||||
node scripts/build-all.mjs qaRuntime
|
node --import tsx scripts/build-all.mts qaRuntime
|
||||||
pnpm ui:build
|
pnpm ui:build
|
||||||
package_args=(
|
package_args=(
|
||||||
--skip-build
|
--skip-build
|
||||||
--output-dir .artifacts/qa-e2e/smoke-ci-package
|
--output-dir .artifacts/qa-e2e/smoke-ci-package
|
||||||
--output-name openclaw-current.tgz
|
--output-name openclaw-current.tgz
|
||||||
)
|
)
|
||||||
if grep -Fq -- '--allow-unreleased-changelog' scripts/package-openclaw-for-docker.mjs; then
|
if grep -Fq -- '--allow-unreleased-changelog' scripts/package-openclaw-for-docker.mts; then
|
||||||
package_args=(--allow-unreleased-changelog "${package_args[@]}")
|
package_args=(--allow-unreleased-changelog "${package_args[@]}")
|
||||||
fi
|
fi
|
||||||
node scripts/package-openclaw-for-docker.mjs "${package_args[@]}"
|
node scripts/package-openclaw-for-docker.mjs "${package_args[@]}"
|
||||||
OPENCLAW_BUILD_PRIVATE_QA=1 node scripts/build-all.mjs qaRuntime
|
OPENCLAW_BUILD_PRIVATE_QA=1 node --import tsx scripts/build-all.mts qaRuntime
|
||||||
|
|
||||||
- name: Run smoke profile part
|
- name: Run smoke profile part
|
||||||
env:
|
env:
|
||||||
@@ -2061,13 +2087,13 @@ jobs:
|
|||||||
# Frozen release targets can predate the workflow-owned shard runner.
|
# Frozen release targets can predate the workflow-owned shard runner.
|
||||||
# Keep its implementation pinned to this workflow revision, while tests
|
# Keep its implementation pinned to this workflow revision, while tests
|
||||||
# continue to run against the checked-out candidate.
|
# continue to run against the checked-out candidate.
|
||||||
if: ${{ hashFiles('scripts/ci-run-node-test-shard.mjs') == '' }}
|
if: ${{ hashFiles('scripts/ci-run-node-test-shard.mts') == '' }}
|
||||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.workflow_sha }}
|
ref: ${{ github.workflow_sha }}
|
||||||
path: .ci-workflow
|
path: .ci-workflow
|
||||||
sparse-checkout: |
|
sparse-checkout: |
|
||||||
scripts/ci-run-node-test-shard.mjs
|
scripts/ci-run-node-test-shard.mts
|
||||||
scripts/lib/direct-run.mjs
|
scripts/lib/direct-run.mjs
|
||||||
scripts/lib/local-heavy-check-runtime.mjs
|
scripts/lib/local-heavy-check-runtime.mjs
|
||||||
sparse-checkout-cone-mode: false
|
sparse-checkout-cone-mode: false
|
||||||
@@ -2092,12 +2118,12 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
runner="scripts/ci-run-node-test-shard.mjs"
|
runner="scripts/ci-run-node-test-shard.mts"
|
||||||
if [[ ! -f "$runner" ]]; then
|
if [[ ! -f "$runner" ]]; then
|
||||||
runner=".ci-workflow/${runner}"
|
runner=".ci-workflow/${runner}"
|
||||||
[[ -f "$runner" ]]
|
[[ -f "$runner" ]]
|
||||||
fi
|
fi
|
||||||
node "$runner"
|
node --import tsx "$runner"
|
||||||
|
|
||||||
# Types, lint, and format check shards.
|
# Types, lint, and format check shards.
|
||||||
check-shard:
|
check-shard:
|
||||||
@@ -2175,7 +2201,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
# Config/toolchain inputs the tree-OID gate below cannot see; must
|
# Config/toolchain inputs the tree-OID gate below cannot see; must
|
||||||
# stay identical to the boundary lane's fingerprint composition.
|
# stay identical to the boundary lane's fingerprint composition.
|
||||||
BOUNDARY_CONFIG_HASH: ${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'package.json', 'pnpm-lock.yaml') }}
|
BOUNDARY_CONFIG_HASH: ${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mts', 'scripts/prepare-extension-package-boundary-artifacts.mts', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mts', 'package.json', 'pnpm-lock.yaml') }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
sticky_root=/var/tmp/openclaw-ext-boundary
|
sticky_root=/var/tmp/openclaw-ext-boundary
|
||||||
@@ -2218,14 +2244,6 @@ jobs:
|
|||||||
case "$TASK" in
|
case "$TASK" in
|
||||||
guards)
|
guards)
|
||||||
pnpm check:no-conflict-markers
|
pnpm check:no-conflict-markers
|
||||||
if has_package_script "check:script-declarations"; then
|
|
||||||
pnpm check:script-declarations
|
|
||||||
elif [[ "$HISTORICAL_TARGET" != "true" ]]; then
|
|
||||||
echo "Current CI targets must provide the check:script-declarations package script." >&2
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "[skip] historical target predates the script declaration contract"
|
|
||||||
fi
|
|
||||||
if [[ "$HISTORICAL_TARGET" == "true" ]]; then
|
if [[ "$HISTORICAL_TARGET" == "true" ]]; then
|
||||||
echo "[skip] historical target skips the wall-clock doctor deprecation registry guard"
|
echo "[skip] historical target skips the wall-clock doctor deprecation registry guard"
|
||||||
elif has_package_script "check:doctor-deprecation-registry"; then
|
elif has_package_script "check:doctor-deprecation-registry"; then
|
||||||
@@ -2278,7 +2296,7 @@ jobs:
|
|||||||
pnpm lint "${lint_args[@]}"
|
pnpm lint "${lint_args[@]}"
|
||||||
else
|
else
|
||||||
echo "[skip] changed scope cannot affect control-UI i18n catalogs"
|
echo "[skip] changed scope cannot affect control-UI i18n catalogs"
|
||||||
node scripts/run-oxlint-shards.mjs "${lint_args[@]}"
|
node --import tsx scripts/run-oxlint-shards.mts "${lint_args[@]}"
|
||||||
fi
|
fi
|
||||||
if [ "$FORMAT_CHECK" = "true" ]; then
|
if [ "$FORMAT_CHECK" = "true" ]; then
|
||||||
pnpm format:check
|
pnpm format:check
|
||||||
@@ -2451,7 +2469,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
# Config/toolchain inputs the tree-OID gate below cannot see; must
|
# Config/toolchain inputs the tree-OID gate below cannot see; must
|
||||||
# stay identical to the seed step and check-lint's restore gate.
|
# stay identical to the seed step and check-lint's restore gate.
|
||||||
BOUNDARY_CONFIG_HASH: ${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'package.json', 'pnpm-lock.yaml') }}
|
BOUNDARY_CONFIG_HASH: ${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mts', 'scripts/prepare-extension-package-boundary-artifacts.mts', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mts', 'package.json', 'pnpm-lock.yaml') }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
sticky_root=/var/tmp/openclaw-ext-boundary
|
sticky_root=/var/tmp/openclaw-ext-boundary
|
||||||
@@ -2489,7 +2507,7 @@ jobs:
|
|||||||
packages/plugin-sdk/dist
|
packages/plugin-sdk/dist
|
||||||
extensions/*/dist/.boundary-tsc.tsbuildinfo
|
extensions/*/dist/.boundary-tsc.tsbuildinfo
|
||||||
extensions/*/dist/.boundary-tsc.stamp
|
extensions/*/dist/.boundary-tsc.stamp
|
||||||
key: ${{ runner.os }}-extension-package-boundary-v1-${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'packages/llm-core/package.json', 'packages/model-catalog-core/package.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'src/plugin-sdk/**', 'src/plugins/types.ts', 'src/auto-reply/**', 'packages/llm-core/src/**', 'packages/model-catalog-core/src/**', 'src/video-generation/dashscope-compatible.ts', 'src/video-generation/types.ts', 'src/types/**', 'extensions/**', 'extensions/tsconfig.package-boundary*.json', 'package.json', 'pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-extension-package-boundary-v1-${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'packages/llm-core/package.json', 'packages/model-catalog-core/package.json', 'scripts/check-extension-package-tsc-boundary.mts', 'scripts/prepare-extension-package-boundary-artifacts.mts', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mts', 'src/plugin-sdk/**', 'src/plugins/types.ts', 'src/auto-reply/**', 'packages/llm-core/src/**', 'packages/model-catalog-core/src/**', 'src/video-generation/dashscope-compatible.ts', 'src/video-generation/types.ts', 'src/types/**', 'extensions/**', 'extensions/tsconfig.package-boundary*.json', 'package.json', 'pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-extension-package-boundary-v1-
|
${{ runner.os }}-extension-package-boundary-v1-
|
||||||
|
|
||||||
@@ -2522,11 +2540,11 @@ jobs:
|
|||||||
packages/plugin-sdk/tsconfig.json \
|
packages/plugin-sdk/tsconfig.json \
|
||||||
packages/llm-core/package.json \
|
packages/llm-core/package.json \
|
||||||
packages/model-catalog-core/package.json \
|
packages/model-catalog-core/package.json \
|
||||||
scripts/check-extension-package-tsc-boundary.mjs \
|
scripts/check-extension-package-tsc-boundary.mts \
|
||||||
scripts/prepare-extension-package-boundary-artifacts.mjs \
|
scripts/prepare-extension-package-boundary-artifacts.mts \
|
||||||
scripts/write-plugin-sdk-entry-dts.ts \
|
scripts/write-plugin-sdk-entry-dts.ts \
|
||||||
scripts/lib/plugin-sdk-entrypoints.json \
|
scripts/lib/plugin-sdk-entrypoints.json \
|
||||||
scripts/lib/plugin-sdk-entries.mjs \
|
scripts/lib/plugin-sdk-entries.mts \
|
||||||
package.json \
|
package.json \
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
)
|
)
|
||||||
@@ -2567,7 +2585,7 @@ jobs:
|
|||||||
|
|
||||||
case "$ADDITIONAL_CHECK_GROUP" in
|
case "$ADDITIONAL_CHECK_GROUP" in
|
||||||
boundaries)
|
boundaries)
|
||||||
node scripts/run-additional-boundary-checks.mjs
|
node --import tsx scripts/run-additional-boundary-checks.mts
|
||||||
;;
|
;;
|
||||||
prompt-snapshots)
|
prompt-snapshots)
|
||||||
# No presence fallback: the boundary runner previously invoked
|
# No presence fallback: the boundary runner previously invoked
|
||||||
@@ -2582,14 +2600,14 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
session-accessor-boundary)
|
session-accessor-boundary)
|
||||||
if [ ! -f scripts/check-session-accessor-boundary.mjs ]; then
|
if [ ! -f scripts/check-session-accessor-boundary.mts ]; then
|
||||||
echo "[skip] session accessor boundary check is not present in this checkout"
|
echo "[skip] session accessor boundary check is not present in this checkout"
|
||||||
elif ! node -e 'const pkg = require("./package.json"); process.exit(pkg.scripts?.["lint:tmp:session-accessor-boundary"] ? 0 : 1);'; then
|
elif ! node -e 'const pkg = require("./package.json"); process.exit(pkg.scripts?.["lint:tmp:session-accessor-boundary"] ? 0 : 1);'; then
|
||||||
echo "[skip] session accessor boundary script is not present in package.json"
|
echo "[skip] session accessor boundary script is not present in package.json"
|
||||||
else
|
else
|
||||||
run_check "lint:tmp:session-accessor-boundary" pnpm run lint:tmp:session-accessor-boundary
|
run_check "lint:tmp:session-accessor-boundary" pnpm run lint:tmp:session-accessor-boundary
|
||||||
fi
|
fi
|
||||||
if [ ! -f scripts/check-sqlite-transaction-boundary.mjs ]; then
|
if [ ! -f scripts/check-sqlite-transaction-boundary.mts ]; then
|
||||||
echo "[skip] SQLite transaction boundary check is not present in this checkout"
|
echo "[skip] SQLite transaction boundary check is not present in this checkout"
|
||||||
elif ! node -e 'const pkg = require("./package.json"); process.exit(pkg.scripts?.["lint:tmp:sqlite-transaction-boundary"] ? 0 : 1);'; then
|
elif ! node -e 'const pkg = require("./package.json"); process.exit(pkg.scripts?.["lint:tmp:sqlite-transaction-boundary"] ? 0 : 1);'; then
|
||||||
echo "[skip] SQLite transaction boundary script is not present in package.json"
|
echo "[skip] SQLite transaction boundary script is not present in package.json"
|
||||||
@@ -2598,7 +2616,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
session-transcript-reader-boundary)
|
session-transcript-reader-boundary)
|
||||||
if [ ! -f scripts/check-session-transcript-reader-boundary.mjs ]; then
|
if [ ! -f scripts/check-session-transcript-reader-boundary.mts ]; then
|
||||||
echo "[skip] session transcript reader boundary check is not present in this checkout"
|
echo "[skip] session transcript reader boundary check is not present in this checkout"
|
||||||
elif ! node -e 'const pkg = require("./package.json"); process.exit(pkg.scripts?.["lint:tmp:session-transcript-reader-boundary"] ? 0 : 1);'; then
|
elif ! node -e 'const pkg = require("./package.json"); process.exit(pkg.scripts?.["lint:tmp:session-transcript-reader-boundary"] ? 0 : 1);'; then
|
||||||
echo "[skip] session transcript reader boundary script is not present in package.json"
|
echo "[skip] session transcript reader boundary script is not present in package.json"
|
||||||
@@ -2649,7 +2667,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
# Must stay identical to the restore gates above.
|
# Must stay identical to the restore gates above.
|
||||||
BOUNDARY_CONFIG_HASH: ${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'package.json', 'pnpm-lock.yaml') }}
|
BOUNDARY_CONFIG_HASH: ${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mts', 'scripts/prepare-extension-package-boundary-artifacts.mts', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mts', 'package.json', 'pnpm-lock.yaml') }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
sticky_root=/var/tmp/openclaw-ext-boundary
|
sticky_root=/var/tmp/openclaw-ext-boundary
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
install-bun: "false"
|
install-bun: "false"
|
||||||
|
|
||||||
- name: Collect dated TODO candidates
|
- name: Collect dated TODO candidates
|
||||||
run: node scripts/dated-todo-scan.mjs
|
run: node --import tsx scripts/dated-todo-scan.mts
|
||||||
|
|
||||||
- name: Capture sweep date
|
- name: Capture sweep date
|
||||||
id: sweep-date
|
id: sweep-date
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ on:
|
|||||||
- docs/**
|
- docs/**
|
||||||
- scripts/docs-list.js
|
- scripts/docs-list.js
|
||||||
- scripts/docs-sync-publish.mjs
|
- scripts/docs-sync-publish.mjs
|
||||||
|
- scripts/check-docs-mdx.mjs
|
||||||
|
- scripts/check-docs-mdx.mts
|
||||||
|
- scripts/lib/mintlify-accordion.mjs
|
||||||
|
- scripts/lib/tsx-cli-shim.mjs
|
||||||
- .github/workflows/docs-sync-publish.yml
|
- .github/workflows/docs-sync-publish.yml
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
@@ -84,7 +88,7 @@ jobs:
|
|||||||
- name: Install docs MDX checker dependency
|
- name: Install docs MDX checker dependency
|
||||||
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
|
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
|
||||||
working-directory: publish
|
working-directory: publish
|
||||||
run: npm install --no-save --package-lock=false @mdx-js/mdx@3.1.1
|
run: npm install --no-save --package-lock=false @mdx-js/mdx@3.1.1 tsx@4.23.1
|
||||||
|
|
||||||
- name: Check publish docs MDX
|
- name: Check publish docs MDX
|
||||||
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
|
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ jobs:
|
|||||||
"scripts/format-swift.sh",
|
"scripts/format-swift.sh",
|
||||||
"scripts/install-swift-tools.sh",
|
"scripts/install-swift-tools.sh",
|
||||||
"scripts/ios-write-swift-filelist.mjs",
|
"scripts/ios-write-swift-filelist.mjs",
|
||||||
|
"scripts/ios-write-swift-filelist.mts",
|
||||||
"scripts/lint-swift.sh",
|
"scripts/lint-swift.sh",
|
||||||
], { ignoreReturnCode: true, silent: true });
|
], { ignoreReturnCode: true, silent: true });
|
||||||
if (result.exitCode !== 0 && result.exitCode !== 1) {
|
if (result.exitCode !== 0 && result.exitCode !== 1) {
|
||||||
|
|||||||
@@ -508,7 +508,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "telegram_exit=${telegram_exit}" >> "$GITHUB_OUTPUT"
|
echo "telegram_exit=${telegram_exit}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
node "${GITHUB_WORKSPACE}/scripts/mantis/build-telegram-evidence.mjs" \
|
node --import tsx "${GITHUB_WORKSPACE}/scripts/mantis/build-telegram-evidence.mts" \
|
||||||
--output-dir "$root" \
|
--output-dir "$root" \
|
||||||
--candidate-ref "$CANDIDATE_SHA" \
|
--candidate-ref "$CANDIDATE_SHA" \
|
||||||
--candidate-sha "$CANDIDATE_SHA" \
|
--candidate-sha "$CANDIDATE_SHA" \
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ jobs:
|
|||||||
"$candidate_repo/ui/src/test-helpers/control-ui-e2e.ts"
|
"$candidate_repo/ui/src/test-helpers/control-ui-e2e.ts"
|
||||||
|
|
||||||
cd "$candidate_repo"
|
cd "$candidate_repo"
|
||||||
node scripts/ensure-playwright-chromium.mjs
|
node --import tsx scripts/ensure-playwright-chromium.mts
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
OPENCLAW_MANTIS_WEB_UI_CHAT_OUTPUT_DIR="$root" \
|
OPENCLAW_MANTIS_WEB_UI_CHAT_OUTPUT_DIR="$root" \
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ jobs:
|
|||||||
OPENCLAW_BUILD_PRIVATE_QA: "1"
|
OPENCLAW_BUILD_PRIVATE_QA: "1"
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
node scripts/build-all.mjs qaRuntime
|
node --import tsx scripts/build-all.mts qaRuntime
|
||||||
test -f dist/plugin-sdk/qa-runtime.js
|
test -f dist/plugin-sdk/qa-runtime.js
|
||||||
test -f dist/extensions/qa-lab/runtime-api.js
|
test -f dist/extensions/qa-lab/runtime-api.js
|
||||||
|
|
||||||
|
|||||||
@@ -619,7 +619,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
node workflow/scripts/resolve-openclaw-package-candidate.mjs \
|
pnpm --dir workflow exec node --import tsx scripts/resolve-openclaw-package-candidate.mts \
|
||||||
--source artifact \
|
--source artifact \
|
||||||
--artifact-dir "$INPUT_DIR" \
|
--artifact-dir "$INPUT_DIR" \
|
||||||
--package-sha256 "$INPUT_CANDIDATE_SHA256" \
|
--package-sha256 "$INPUT_CANDIDATE_SHA256" \
|
||||||
@@ -737,11 +737,11 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
BASELINE_PACK_JSON: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/baseline/pack.json
|
BASELINE_PACK_JSON: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/baseline/pack.json
|
||||||
run: |
|
run: |
|
||||||
node --input-type=module <<'NODE' >>"$GITHUB_OUTPUT"
|
node --import tsx --input-type=module <<'NODE' >>"$GITHUB_OUTPUT"
|
||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { resolveNpmJsonEntries } from "./scripts/lib/npm-json-output.mjs";
|
import { resolveNpmJsonEntries } from "./scripts/lib/npm-json-output.mts";
|
||||||
function resolveTarballFileName(value, label) {
|
function resolveTarballFileName(value, label) {
|
||||||
const fileName = typeof value === "string" ? value.trim() : "";
|
const fileName = typeof value === "string" ? value.trim() : "";
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -2296,7 +2296,7 @@ jobs:
|
|||||||
)
|
)
|
||||||
# Frozen targets that predate the opt-in flag still strict-pack against their versioned changelog.
|
# Frozen targets that predate the opt-in flag still strict-pack against their versioned changelog.
|
||||||
if [[ "$ALLOW_UNRELEASED_CHANGELOG" == "true" ]] && \
|
if [[ "$ALLOW_UNRELEASED_CHANGELOG" == "true" ]] && \
|
||||||
grep -Fq -- '--allow-unreleased-changelog' scripts/package-openclaw-for-docker.mjs; then
|
grep -Fq -- '--allow-unreleased-changelog' scripts/package-openclaw-for-docker.mts; then
|
||||||
package_args+=(--allow-unreleased-changelog)
|
package_args+=(--allow-unreleased-changelog)
|
||||||
fi
|
fi
|
||||||
node scripts/package-openclaw-for-docker.mjs "${package_args[@]}"
|
node scripts/package-openclaw-for-docker.mjs "${package_args[@]}"
|
||||||
@@ -2341,12 +2341,12 @@ jobs:
|
|||||||
cp "${tgzs[0]}" "$target"
|
cp "${tgzs[0]}" "$target"
|
||||||
fi
|
fi
|
||||||
echo "Validating Docker E2E package tarball: $target"
|
echo "Validating Docker E2E package tarball: $target"
|
||||||
validator="scripts/check-openclaw-package-tarball.mjs"
|
validator=(node scripts/check-openclaw-package-tarball.mjs)
|
||||||
if [[ -n "${EXPECTED_PACKAGE_FILE_NAME// }" ]]; then
|
if [[ -n "${EXPECTED_PACKAGE_FILE_NAME// }" ]]; then
|
||||||
validator=".release-harness/scripts/check-openclaw-package-tarball.mjs"
|
validator=(pnpm --dir .release-harness exec node scripts/check-openclaw-package-tarball.mjs)
|
||||||
fi
|
fi
|
||||||
started_at="$(date +%s)"
|
started_at="$(date +%s)"
|
||||||
timeout --foreground 5m node "$validator" "$target"
|
timeout --foreground 5m "${validator[@]}" "$GITHUB_WORKSPACE/$target"
|
||||||
finished_at="$(date +%s)"
|
finished_at="$(date +%s)"
|
||||||
echo "Docker E2E package tarball validation finished in $((finished_at - started_at))s."
|
echo "Docker E2E package tarball validation finished in $((finished_at - started_at))s."
|
||||||
digest="$(sha256sum "$target" | awk '{print $1}')"
|
digest="$(sha256sum "$target" | awk '{print $1}')"
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ jobs:
|
|||||||
for package_dir in "${package_dirs[@]}"; do
|
for package_dir in "${package_dirs[@]}"; do
|
||||||
runtime_args+=(--package "$package_dir")
|
runtime_args+=(--package "$package_dir")
|
||||||
done
|
done
|
||||||
node scripts/check-plugin-npm-runtime-builds.mjs "${runtime_args[@]}"
|
node --import tsx scripts/check-plugin-npm-runtime-builds.mts "${runtime_args[@]}"
|
||||||
|
|
||||||
for package_dir in "${package_dirs[@]}"; do
|
for package_dir in "${package_dirs[@]}"; do
|
||||||
OPENCLAW_PLUGIN_NPM_RUNTIME_BUILD=0 \
|
OPENCLAW_PLUGIN_NPM_RUNTIME_BUILD=0 \
|
||||||
@@ -387,7 +387,7 @@ jobs:
|
|||||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
node scripts/generate-dependency-release-evidence.mjs \
|
node --import tsx scripts/generate-dependency-release-evidence.mts \
|
||||||
--release-ref "$RELEASE_REF" \
|
--release-ref "$RELEASE_REF" \
|
||||||
--npm-dist-tag "$RELEASE_NPM_DIST_TAG" \
|
--npm-dist-tag "$RELEASE_NPM_DIST_TAG" \
|
||||||
--output-dir "$RUNNER_TEMP/openclaw-release-dependency-evidence" \
|
--output-dir "$RUNNER_TEMP/openclaw-release-dependency-evidence" \
|
||||||
|
|||||||
@@ -393,14 +393,20 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
npm_wrapper="$PERFORMANCE_HELPER_DIR/scripts/ocm-npm-workspace-deps.mjs"
|
npm_wrapper="$PERFORMANCE_HELPER_DIR/scripts/ocm-npm-workspace-deps.mts"
|
||||||
|
npm_adapter="${RUNNER_TEMP}/openclaw-ocm-npm"
|
||||||
workspace_dependency_dirs=""
|
workspace_dependency_dirs=""
|
||||||
if [[ -f "${GITHUB_WORKSPACE}/packages/ai/package.json" ]]; then
|
if [[ -f "${GITHUB_WORKSPACE}/packages/ai/package.json" ]]; then
|
||||||
workspace_dependency_dirs="${GITHUB_WORKSPACE}/packages/ai"
|
workspace_dependency_dirs="${GITHUB_WORKSPACE}/packages/ai"
|
||||||
fi
|
fi
|
||||||
chmod 0755 "$npm_wrapper"
|
cat > "$npm_adapter" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
exec node --import tsx "$OPENCLAW_OCM_NPM_WRAPPER" "$@"
|
||||||
|
EOF
|
||||||
|
chmod 0755 "$npm_adapter"
|
||||||
{
|
{
|
||||||
echo "OCM_INTERNAL_NPM_BIN=$npm_wrapper"
|
echo "OCM_INTERNAL_NPM_BIN=$npm_adapter"
|
||||||
|
echo "OPENCLAW_OCM_NPM_WRAPPER=$npm_wrapper"
|
||||||
echo "OPENCLAW_OCM_REAL_NPM_BIN=$(command -v npm)"
|
echo "OPENCLAW_OCM_REAL_NPM_BIN=$(command -v npm)"
|
||||||
echo "OPENCLAW_OCM_WORKSPACE_DEPENDENCY_DIRS=$workspace_dependency_dirs"
|
echo "OPENCLAW_OCM_WORKSPACE_DEPENDENCY_DIRS=$workspace_dependency_dirs"
|
||||||
} >> "$GITHUB_ENV"
|
} >> "$GITHUB_ENV"
|
||||||
@@ -529,7 +535,7 @@ jobs:
|
|||||||
echo "report_md=$report_md" >> "$GITHUB_OUTPUT"
|
echo "report_md=$report_md" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
node "$PERFORMANCE_HELPER_DIR/scripts/lib/kova-workflow-evidence.mjs" \
|
node --import tsx "$PERFORMANCE_HELPER_DIR/scripts/lib/kova-workflow-evidence.mts" \
|
||||||
--plan "$KOVA_PLAN_JSON" \
|
--plan "$KOVA_PLAN_JSON" \
|
||||||
--report "$report_json" \
|
--report "$report_json" \
|
||||||
--profile "$PROFILE" \
|
--profile "$PROFILE" \
|
||||||
@@ -547,7 +553,7 @@ jobs:
|
|||||||
if [[ "$KOVA_REF" == "$KOVA_CANONICAL_CONFIG_REF" || "$KOVA_REF" == "$KOVA_LEGACY_LIST_CONFIG_REF" ]]; then
|
if [[ "$KOVA_REF" == "$KOVA_CANONICAL_CONFIG_REF" || "$KOVA_REF" == "$KOVA_LEGACY_LIST_CONFIG_REF" ]]; then
|
||||||
gate_args+=(--require-instrumented-performance-contract)
|
gate_args+=(--require-instrumented-performance-contract)
|
||||||
fi
|
fi
|
||||||
if node "$PERFORMANCE_HELPER_DIR/scripts/lib/kova-report-gate.mjs" "${gate_args[@]}"
|
if node --import tsx "$PERFORMANCE_HELPER_DIR/scripts/lib/kova-report-gate.mts" "${gate_args[@]}"
|
||||||
then
|
then
|
||||||
effective_status=0
|
effective_status=0
|
||||||
{
|
{
|
||||||
@@ -564,7 +570,7 @@ jobs:
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
summary_path="$SUMMARY_DIR/${LANE_ID}.md"
|
summary_path="$SUMMARY_DIR/${LANE_ID}.md"
|
||||||
summary_args=(node "$PERFORMANCE_HELPER_DIR/scripts/kova-ci-summary.mjs" --report "$report_json" --output "$summary_path" --lane "$LANE_ID")
|
summary_args=(node --import tsx "$PERFORMANCE_HELPER_DIR/scripts/kova-ci-summary.mts" --report "$report_json" --output "$summary_path" --lane "$LANE_ID")
|
||||||
set +e
|
set +e
|
||||||
"${summary_args[@]}"
|
"${summary_args[@]}"
|
||||||
summary_status=$?
|
summary_status=$?
|
||||||
@@ -733,7 +739,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$SOURCE_PERF_DIR/mock-hello"
|
mkdir -p "$SOURCE_PERF_DIR/mock-hello"
|
||||||
if ! node -e "const fs=require('node:fs'); const scripts=require('./package.json').scripts||{}; process.exit(scripts['test:gateway:cpu-scenarios'] && scripts['test:extensions:memory'] && scripts.openclaw && fs.existsSync('openclaw.mjs') && fs.existsSync('scripts/profile-extension-memory.mjs') ? 0 : 1)"; then
|
if ! node -e "const fs=require('node:fs'); const scripts=require('./package.json').scripts||{}; process.exit(scripts['test:gateway:cpu-scenarios'] && scripts['test:extensions:memory'] && scripts.openclaw && fs.existsSync('openclaw.mjs') && fs.existsSync('scripts/profile-extension-memory.mts') ? 0 : 1)"; then
|
||||||
cat > "$SOURCE_PERF_DIR/index.md" <<EOF
|
cat > "$SOURCE_PERF_DIR/index.md" <<EOF
|
||||||
# OpenClaw Source Performance
|
# OpenClaw Source Performance
|
||||||
|
|
||||||
@@ -745,15 +751,15 @@ jobs:
|
|||||||
|
|
||||||
- Tested ref: ${TESTED_REF}
|
- Tested ref: ${TESTED_REF}
|
||||||
- Tested SHA: ${TESTED_SHA}
|
- Tested SHA: ${TESTED_SHA}
|
||||||
- Required scripts: test:gateway:cpu-scenarios, test:extensions:memory, openclaw, openclaw.mjs, scripts/profile-extension-memory.mjs
|
- Required scripts: test:gateway:cpu-scenarios, test:extensions:memory, openclaw, openclaw.mjs, scripts/profile-extension-memory.mts
|
||||||
EOF
|
EOF
|
||||||
cat "$SOURCE_PERF_DIR/index.md" >> "$GITHUB_STEP_SUMMARY"
|
cat "$SOURCE_PERF_DIR/index.md" >> "$GITHUB_STEP_SUMMARY"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# target_ref may predate the dedicated profile; preserve the historical full build there.
|
# target_ref may predate the dedicated profile; preserve the historical full build there.
|
||||||
if node -e "import('./scripts/build-all.mjs').then((module) => process.exit(module.BUILD_ALL_PROFILES?.sourcePerformance ? 0 : 1)).catch(() => process.exit(1))"; then
|
if node --import tsx -e "import('./scripts/build-all.mts').then((module) => process.exit(module.BUILD_ALL_PROFILES?.sourcePerformance ? 0 : 1)).catch(() => process.exit(1))"; then
|
||||||
OPENCLAW_BUILD_PRIVATE_QA=1 node scripts/build-all.mjs sourcePerformance
|
OPENCLAW_BUILD_PRIVATE_QA=1 node --import tsx scripts/build-all.mts sourcePerformance
|
||||||
else
|
else
|
||||||
pnpm build
|
pnpm build
|
||||||
fi
|
fi
|
||||||
@@ -918,7 +924,7 @@ jobs:
|
|||||||
echo "SQLite state smoke probe is not available in ${TESTED_REF}; continuing with the remaining source probes." >> "$GITHUB_STEP_SUMMARY"
|
echo "SQLite state smoke probe is not available in ${TESTED_REF}; continuing with the remaining source probes." >> "$GITHUB_STEP_SUMMARY"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
summary_args=(node "$PERFORMANCE_HELPER_DIR/scripts/openclaw-performance-source-summary.mjs" \
|
summary_args=(node --import tsx "$PERFORMANCE_HELPER_DIR/scripts/openclaw-performance-source-summary.mts" \
|
||||||
--source-dir "$SOURCE_PERF_DIR" \
|
--source-dir "$SOURCE_PERF_DIR" \
|
||||||
--output "$SOURCE_PERF_DIR/index.md")
|
--output "$SOURCE_PERF_DIR/index.md")
|
||||||
if [[ -n "${SOURCE_PERF_BASELINE_DIR:-}" && -d "$SOURCE_PERF_BASELINE_DIR" ]]; then
|
if [[ -n "${SOURCE_PERF_BASELINE_DIR:-}" && -d "$SOURCE_PERF_BASELINE_DIR" ]]; then
|
||||||
|
|||||||
@@ -728,7 +728,7 @@ jobs:
|
|||||||
--required-plugin-packages-json "$required_packages"
|
--required-plugin-packages-json "$required_packages"
|
||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
node scripts/resolve-openclaw-package-candidate.mjs \
|
node --import tsx scripts/resolve-openclaw-package-candidate.mts \
|
||||||
"${source_args[@]}" \
|
"${source_args[@]}" \
|
||||||
"${registry_args[@]}" \
|
"${registry_args[@]}" \
|
||||||
--output-dir .artifacts/docker-e2e-package \
|
--output-dir .artifacts/docker-e2e-package \
|
||||||
@@ -1253,7 +1253,7 @@ jobs:
|
|||||||
- name: Build private QA runtime
|
- name: Build private QA runtime
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=8192
|
NODE_OPTIONS: --max-old-space-size=8192
|
||||||
run: node scripts/build-all.mjs qaRuntime
|
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||||
|
|
||||||
- name: Run parity lane
|
- name: Run parity lane
|
||||||
id: run_lane
|
id: run_lane
|
||||||
@@ -1434,7 +1434,7 @@ jobs:
|
|||||||
- name: Build private QA runtime
|
- name: Build private QA runtime
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=8192
|
NODE_OPTIONS: --max-old-space-size=8192
|
||||||
run: node scripts/build-all.mjs qaRuntime
|
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||||
|
|
||||||
- name: Generate parity report
|
- name: Generate parity report
|
||||||
id: generate_report
|
id: generate_report
|
||||||
@@ -1517,7 +1517,7 @@ jobs:
|
|||||||
- name: Build private QA runtime
|
- name: Build private QA runtime
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=8192
|
NODE_OPTIONS: --max-old-space-size=8192
|
||||||
run: node scripts/build-all.mjs qaRuntime
|
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||||
|
|
||||||
- name: Run runtime-pair lane
|
- name: Run runtime-pair lane
|
||||||
id: candidate_runtime_pair
|
id: candidate_runtime_pair
|
||||||
@@ -1581,7 +1581,7 @@ jobs:
|
|||||||
if [[ "$CANDIDATE_SUITE_OUTCOME" != "success" ]]; then
|
if [[ "$CANDIDATE_SUITE_OUTCOME" != "success" ]]; then
|
||||||
validator_args+=(--require-explicit-gap)
|
validator_args+=(--require-explicit-gap)
|
||||||
fi
|
fi
|
||||||
node trusted-suite-validator/scripts/validate-qa-runtime-pair-summary.mjs "${validator_args[@]}"
|
node --import tsx trusted-suite-validator/scripts/validate-qa-runtime-pair-summary.mts "${validator_args[@]}"
|
||||||
if [[ "$CANDIDATE_SUITE_OUTCOME" != "success" ]]; then
|
if [[ "$CANDIDATE_SUITE_OUTCOME" != "success" ]]; then
|
||||||
echo "::notice::Trusted workflow validation accepted frozen-candidate runtime-pair evidence after its suite failed."
|
echo "::notice::Trusted workflow validation accepted frozen-candidate runtime-pair evidence after its suite failed."
|
||||||
fi
|
fi
|
||||||
@@ -1653,7 +1653,7 @@ jobs:
|
|||||||
if [[ "$CANDIDATE_REPORT_OUTCOME" != "success" ]]; then
|
if [[ "$CANDIDATE_REPORT_OUTCOME" != "success" ]]; then
|
||||||
validator_args+=(--require-explicit-gap)
|
validator_args+=(--require-explicit-gap)
|
||||||
fi
|
fi
|
||||||
node trusted-report-validator/scripts/validate-qa-runtime-pair-summary.mjs "${validator_args[@]}"
|
node --import tsx trusted-report-validator/scripts/validate-qa-runtime-pair-summary.mts "${validator_args[@]}"
|
||||||
if [[ "$CANDIDATE_REPORT_OUTCOME" != "success" ]]; then
|
if [[ "$CANDIDATE_REPORT_OUTCOME" != "success" ]]; then
|
||||||
echo "::notice::Trusted workflow validation accepted the frozen-candidate runtime-pair report after its reporter failed."
|
echo "::notice::Trusted workflow validation accepted the frozen-candidate runtime-pair report after its reporter failed."
|
||||||
fi
|
fi
|
||||||
@@ -2127,7 +2127,7 @@ jobs:
|
|||||||
- name: Build private QA runtime
|
- name: Build private QA runtime
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=8192
|
NODE_OPTIONS: --max-old-space-size=8192
|
||||||
run: node scripts/build-all.mjs qaRuntime
|
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||||
|
|
||||||
- name: Run Discord live lane
|
- name: Run Discord live lane
|
||||||
id: run_lane
|
id: run_lane
|
||||||
@@ -2226,7 +2226,7 @@ jobs:
|
|||||||
- name: Build private QA runtime
|
- name: Build private QA runtime
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=8192
|
NODE_OPTIONS: --max-old-space-size=8192
|
||||||
run: node scripts/build-all.mjs qaRuntime
|
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||||
|
|
||||||
- name: Run WhatsApp live lane
|
- name: Run WhatsApp live lane
|
||||||
id: run_lane
|
id: run_lane
|
||||||
@@ -2322,7 +2322,7 @@ jobs:
|
|||||||
- name: Build private QA runtime
|
- name: Build private QA runtime
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=8192
|
NODE_OPTIONS: --max-old-space-size=8192
|
||||||
run: node scripts/build-all.mjs qaRuntime
|
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||||
|
|
||||||
- name: Run Slack live lane
|
- name: Run Slack live lane
|
||||||
id: run_lane
|
id: run_lane
|
||||||
|
|||||||
@@ -779,7 +779,7 @@ jobs:
|
|||||||
changelog_file="${RUNNER_TEMP}/CHANGELOG.md"
|
changelog_file="${RUNNER_TEMP}/CHANGELOG.md"
|
||||||
notes_file="${RUNNER_TEMP}/release-notes.md"
|
notes_file="${RUNNER_TEMP}/release-notes.md"
|
||||||
git show "${TARGET_SHA}:CHANGELOG.md" > "${changelog_file}"
|
git show "${TARGET_SHA}:CHANGELOG.md" > "${changelog_file}"
|
||||||
node scripts/render-github-release-notes.mjs \
|
node --import tsx scripts/render-github-release-notes.mts \
|
||||||
--changelog "${changelog_file}" \
|
--changelog "${changelog_file}" \
|
||||||
--tag "${RELEASE_TAG}" \
|
--tag "${RELEASE_TAG}" \
|
||||||
--repository "${GITHUB_REPOSITORY}" \
|
--repository "${GITHUB_REPOSITORY}" \
|
||||||
@@ -1399,7 +1399,7 @@ jobs:
|
|||||||
echo "openclaw@${release_version} is already published; openclaw_npm_resume_run_id is required to bind postpublish proof to the original workflow identity." >&2
|
echo "openclaw@${release_version} is already published; openclaw_npm_resume_run_id is required to bind postpublish proof to the original workflow identity." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
resume_state="$(node "${GITHUB_WORKSPACE}/.release-harness/scripts/openclaw-npm-resume-run.mjs" \
|
resume_state="$(node --import tsx "${GITHUB_WORKSPACE}/.release-harness/scripts/openclaw-npm-resume-run.mts" \
|
||||||
--repo "${GITHUB_REPOSITORY}" \
|
--repo "${GITHUB_REPOSITORY}" \
|
||||||
--run-id "${OPENCLAW_NPM_RESUME_RUN_ID}")"
|
--run-id "${OPENCLAW_NPM_RESUME_RUN_ID}")"
|
||||||
resume_url="$(printf '%s' "${resume_state}" | jq -er '.url')"
|
resume_url="$(printf '%s' "${resume_state}" | jq -er '.url')"
|
||||||
@@ -1469,7 +1469,7 @@ jobs:
|
|||||||
local metadata_file="${3:-}"
|
local metadata_file="${3:-}"
|
||||||
local changelog_file="${RUNNER_TEMP}/CHANGELOG.md"
|
local changelog_file="${RUNNER_TEMP}/CHANGELOG.md"
|
||||||
local -a render_args=(
|
local -a render_args=(
|
||||||
node scripts/render-github-release-notes.mjs
|
node --import tsx scripts/render-github-release-notes.mts
|
||||||
--changelog "${changelog_file}"
|
--changelog "${changelog_file}"
|
||||||
--tag "${RELEASE_TAG}"
|
--tag "${RELEASE_TAG}"
|
||||||
--repository "${GITHUB_REPOSITORY}"
|
--repository "${GITHUB_REPOSITORY}"
|
||||||
@@ -1514,12 +1514,12 @@ jobs:
|
|||||||
RELEASE_CHANGELOG_FILE="${changelog_file}" \
|
RELEASE_CHANGELOG_FILE="${changelog_file}" \
|
||||||
RELEASE_REPOSITORY="${GITHUB_REPOSITORY}" \
|
RELEASE_REPOSITORY="${GITHUB_REPOSITORY}" \
|
||||||
RELEASE_TAG="${RELEASE_TAG}" \
|
RELEASE_TAG="${RELEASE_TAG}" \
|
||||||
node --input-type=module <<'NODE'
|
node --import tsx --input-type=module <<'NODE'
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import {
|
import {
|
||||||
releaseNotesVersionForTag,
|
releaseNotesVersionForTag,
|
||||||
verifyGithubReleaseNotes,
|
verifyGithubReleaseNotes,
|
||||||
} from "./scripts/render-github-release-notes.mjs";
|
} from "./scripts/render-github-release-notes.mts";
|
||||||
|
|
||||||
const body = readFileSync(process.env.RELEASE_BODY_FILE, "utf8");
|
const body = readFileSync(process.env.RELEASE_BODY_FILE, "utf8");
|
||||||
const changelog = readFileSync(process.env.RELEASE_CHANGELOG_FILE, "utf8");
|
const changelog = readFileSync(process.env.RELEASE_CHANGELOG_FILE, "utf8");
|
||||||
|
|||||||
@@ -477,7 +477,7 @@ jobs:
|
|||||||
OPENCLAW_BUILD_PRIVATE_QA=1 \
|
OPENCLAW_BUILD_PRIVATE_QA=1 \
|
||||||
PATH="$PATH" \
|
PATH="$PATH" \
|
||||||
RUNNER_TEMP="$RUNNER_TEMP" \
|
RUNNER_TEMP="$RUNNER_TEMP" \
|
||||||
pnpm exec node scripts/build-all.mjs qaRuntime
|
pnpm exec node --import tsx scripts/build-all.mts qaRuntime
|
||||||
|
|
||||||
- name: Move built candidate outside trusted workspace
|
- name: Move built candidate outside trusted workspace
|
||||||
id: move_candidate
|
id: move_candidate
|
||||||
@@ -539,7 +539,7 @@ jobs:
|
|||||||
pnpm_version="$(pnpm --version)"
|
pnpm_version="$(pnpm --version)"
|
||||||
jq -n \
|
jq -n \
|
||||||
--arg archiveRoot "$archive_root" \
|
--arg archiveRoot "$archive_root" \
|
||||||
--arg buildCommand "node scripts/build-all.mjs qaRuntime" \
|
--arg buildCommand "node --import tsx scripts/build-all.mts qaRuntime" \
|
||||||
--arg candidateSha "$TARGET_SHA" \
|
--arg candidateSha "$TARGET_SHA" \
|
||||||
--arg candidateTree "$CANDIDATE_TREE" \
|
--arg candidateTree "$CANDIDATE_TREE" \
|
||||||
--arg nodeVersion "$node_version" \
|
--arg nodeVersion "$node_version" \
|
||||||
@@ -748,7 +748,7 @@ jobs:
|
|||||||
.candidateSha == $candidateSha and
|
.candidateSha == $candidateSha and
|
||||||
.candidateTree == $candidateTree and
|
.candidateTree == $candidateTree and
|
||||||
.archiveRoot == "openclaw-telegram-candidate" and
|
.archiveRoot == "openclaw-telegram-candidate" and
|
||||||
.buildCommand == "node scripts/build-all.mjs qaRuntime" and
|
.buildCommand == "node --import tsx scripts/build-all.mts qaRuntime" and
|
||||||
.sourceJob == "build_candidate"
|
.sourceJob == "build_candidate"
|
||||||
' "$manifest_path" >/dev/null
|
' "$manifest_path" >/dev/null
|
||||||
[[ -d "$candidate_root/node_modules" && -f "$candidate_root/dist/index.js" ]]
|
[[ -d "$candidate_root/node_modules" && -f "$candidate_root/dist/index.js" ]]
|
||||||
@@ -899,7 +899,7 @@ jobs:
|
|||||||
id: build_harness
|
id: build_harness
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=8192
|
NODE_OPTIONS: --max-old-space-size=8192
|
||||||
run: node scripts/build-all.mjs qaRuntime
|
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||||
|
|
||||||
- name: Validate candidate artifact metadata
|
- name: Validate candidate artifact metadata
|
||||||
id: metadata
|
id: metadata
|
||||||
|
|||||||
@@ -575,7 +575,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
node scripts/resolve-openclaw-package-candidate.mjs \
|
node --import tsx scripts/resolve-openclaw-package-candidate.mts \
|
||||||
--source "$SOURCE" \
|
--source "$SOURCE" \
|
||||||
--package-ref "$PACKAGE_REF" \
|
--package-ref "$PACKAGE_REF" \
|
||||||
--package-spec "$PACKAGE_SPEC" \
|
--package-spec "$PACKAGE_SPEC" \
|
||||||
@@ -707,7 +707,7 @@ jobs:
|
|||||||
--pre-date 2026-03-15T00:00:00Z
|
--pre-date 2026-03-15T00:00:00Z
|
||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
node scripts/resolve-upgrade-survivor-baselines.mjs "${args[@]}" >/dev/null
|
node --import tsx scripts/resolve-upgrade-survivor-baselines.mts "${args[@]}" >/dev/null
|
||||||
echo "baseline=$fallback_baseline" >> "$GITHUB_OUTPUT"
|
echo "baseline=$fallback_baseline" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Upload package-under-test artifact
|
- name: Upload package-under-test artifact
|
||||||
|
|||||||
@@ -537,7 +537,7 @@ jobs:
|
|||||||
package_slug="${package_name#@openclaw/}"
|
package_slug="${package_name#@openclaw/}"
|
||||||
output_dir="${artifact_root}/packages/${package_slug}"
|
output_dir="${artifact_root}/packages/${package_slug}"
|
||||||
mkdir -p "${output_dir}"
|
mkdir -p "${output_dir}"
|
||||||
node scripts/check-plugin-npm-runtime-builds.mjs --package "${package_dir}"
|
node --import tsx scripts/check-plugin-npm-runtime-builds.mts --package "${package_dir}"
|
||||||
OPENCLAW_CLAWHUB_PACK_OUTPUT_DIR="${output_dir}" \
|
OPENCLAW_CLAWHUB_PACK_OUTPUT_DIR="${output_dir}" \
|
||||||
PACKAGE_TAG="${package_tag}" \
|
PACKAGE_TAG="${package_tag}" \
|
||||||
bash .release-harness/scripts/plugin-clawhub-publish.sh --pack "${package_dir}"
|
bash .release-harness/scripts/plugin-clawhub-publish.sh --pack "${package_dir}"
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ jobs:
|
|||||||
install-deps: "true"
|
install-deps: "true"
|
||||||
|
|
||||||
- name: Verify package-local runtime build
|
- name: Verify package-local runtime build
|
||||||
run: node scripts/check-plugin-npm-runtime-builds.mjs --package "${{ matrix.plugin.packageDir }}"
|
run: node --import tsx scripts/check-plugin-npm-runtime-builds.mts --package "${{ matrix.plugin.packageDir }}"
|
||||||
|
|
||||||
- name: Install pinned ClawHub CLI wrapper
|
- name: Install pinned ClawHub CLI wrapper
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -10,17 +10,20 @@ on:
|
|||||||
- "extensions/**"
|
- "extensions/**"
|
||||||
- "package.json"
|
- "package.json"
|
||||||
- "scripts/generate-npm-package-lock.mjs"
|
- "scripts/generate-npm-package-lock.mjs"
|
||||||
|
- "scripts/generate-npm-package-lock.mts"
|
||||||
- "scripts/lib/npm-publish-plan.mjs"
|
- "scripts/lib/npm-publish-plan.mjs"
|
||||||
- "scripts/lib/npm-json-output.mjs"
|
- "scripts/lib/npm-json-output.mts"
|
||||||
- "scripts/lib/release-version.mjs"
|
- "scripts/lib/release-version.mjs"
|
||||||
- "scripts/lib/plugin-npm-package-manifest.mjs"
|
- "scripts/lib/plugin-npm-package-manifest.mjs"
|
||||||
|
- "scripts/lib/plugin-npm-package-manifest.mts"
|
||||||
|
- "scripts/lib/tsx-cli-shim.mjs"
|
||||||
- "scripts/lib/plugin-npm-release.ts"
|
- "scripts/lib/plugin-npm-release.ts"
|
||||||
- "scripts/lib/actions-artifact-archive.mjs"
|
- "scripts/lib/actions-artifact-archive.mjs"
|
||||||
- "scripts/plugin-npm-publish.sh"
|
- "scripts/plugin-npm-publish.sh"
|
||||||
- "scripts/plugin-publication-artifact.mjs"
|
- "scripts/plugin-publication-artifact.mjs"
|
||||||
- "scripts/plugin-npm-release-check.ts"
|
- "scripts/plugin-npm-release-check.ts"
|
||||||
- "scripts/plugin-npm-release-plan.ts"
|
- "scripts/plugin-npm-release-plan.ts"
|
||||||
- "scripts/verify-plugin-npm-published-runtime.mjs"
|
- "scripts/verify-plugin-npm-published-runtime.mts"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
publish_scope:
|
publish_scope:
|
||||||
@@ -339,8 +342,11 @@ jobs:
|
|||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
sparse-checkout: |
|
sparse-checkout: |
|
||||||
scripts/generate-npm-package-lock.mjs
|
scripts/generate-npm-package-lock.mjs
|
||||||
scripts/lib/npm-json-output.mjs
|
scripts/generate-npm-package-lock.mts
|
||||||
|
scripts/lib/npm-json-output.mts
|
||||||
scripts/lib/plugin-npm-package-manifest.mjs
|
scripts/lib/plugin-npm-package-manifest.mjs
|
||||||
|
scripts/lib/plugin-npm-package-manifest.mts
|
||||||
|
scripts/lib/tsx-cli-shim.mjs
|
||||||
sparse-checkout-cone-mode: false
|
sparse-checkout-cone-mode: false
|
||||||
|
|
||||||
- name: Overlay trusted packaging helper
|
- name: Overlay trusted packaging helper
|
||||||
@@ -350,11 +356,20 @@ jobs:
|
|||||||
.release-tooling/scripts/generate-npm-package-lock.mjs \
|
.release-tooling/scripts/generate-npm-package-lock.mjs \
|
||||||
scripts/generate-npm-package-lock.mjs
|
scripts/generate-npm-package-lock.mjs
|
||||||
cp \
|
cp \
|
||||||
.release-tooling/scripts/lib/npm-json-output.mjs \
|
.release-tooling/scripts/generate-npm-package-lock.mts \
|
||||||
scripts/lib/npm-json-output.mjs
|
scripts/generate-npm-package-lock.mts
|
||||||
|
cp \
|
||||||
|
.release-tooling/scripts/lib/npm-json-output.mts \
|
||||||
|
scripts/lib/npm-json-output.mts
|
||||||
cp \
|
cp \
|
||||||
.release-tooling/scripts/lib/plugin-npm-package-manifest.mjs \
|
.release-tooling/scripts/lib/plugin-npm-package-manifest.mjs \
|
||||||
scripts/lib/plugin-npm-package-manifest.mjs
|
scripts/lib/plugin-npm-package-manifest.mjs
|
||||||
|
cp \
|
||||||
|
.release-tooling/scripts/lib/plugin-npm-package-manifest.mts \
|
||||||
|
scripts/lib/plugin-npm-package-manifest.mts
|
||||||
|
cp \
|
||||||
|
.release-tooling/scripts/lib/tsx-cli-shim.mjs \
|
||||||
|
scripts/lib/tsx-cli-shim.mjs
|
||||||
|
|
||||||
- name: Setup Node environment
|
- name: Setup Node environment
|
||||||
uses: ./.github/actions/setup-node-env
|
uses: ./.github/actions/setup-node-env
|
||||||
@@ -403,9 +418,9 @@ jobs:
|
|||||||
OPENCLAW_PLUGIN_NPM_PACK_OUTPUT_DIR="${artifact_dir}" \
|
OPENCLAW_PLUGIN_NPM_PACK_OUTPUT_DIR="${artifact_dir}" \
|
||||||
bash scripts/plugin-npm-publish.sh --pack "${PACKAGE_DIR}" > "${pack_output}"
|
bash scripts/plugin-npm-publish.sh --pack "${PACKAGE_DIR}" > "${pack_output}"
|
||||||
|
|
||||||
node --input-type=module - "${pack_output}" "${pack_json}" <<'NODE'
|
node --import tsx --input-type=module - "${pack_output}" "${pack_json}" <<'NODE'
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import { resolveNpmJsonEntries } from "./scripts/lib/npm-json-output.mjs";
|
import { resolveNpmJsonEntries } from "./scripts/lib/npm-json-output.mts";
|
||||||
|
|
||||||
const raw = fs.readFileSync(process.argv[2], "utf8").trim();
|
const raw = fs.readFileSync(process.argv[2], "utf8").trim();
|
||||||
let pack;
|
let pack;
|
||||||
@@ -1245,11 +1260,23 @@ jobs:
|
|||||||
scripts/generate-npm-package-lock.mjs \
|
scripts/generate-npm-package-lock.mjs \
|
||||||
.publication-target/scripts/generate-npm-package-lock.mjs
|
.publication-target/scripts/generate-npm-package-lock.mjs
|
||||||
cp \
|
cp \
|
||||||
scripts/lib/npm-json-output.mjs \
|
scripts/generate-npm-package-lock.mts \
|
||||||
.publication-target/scripts/lib/npm-json-output.mjs
|
.publication-target/scripts/generate-npm-package-lock.mts
|
||||||
|
cp \
|
||||||
|
scripts/lib/npm-json-output.mts \
|
||||||
|
.publication-target/scripts/lib/npm-json-output.mts
|
||||||
cp \
|
cp \
|
||||||
scripts/lib/plugin-npm-package-manifest.mjs \
|
scripts/lib/plugin-npm-package-manifest.mjs \
|
||||||
.publication-target/scripts/lib/plugin-npm-package-manifest.mjs
|
.publication-target/scripts/lib/plugin-npm-package-manifest.mjs
|
||||||
|
cp \
|
||||||
|
scripts/lib/plugin-npm-package-manifest.mts \
|
||||||
|
.publication-target/scripts/lib/plugin-npm-package-manifest.mts
|
||||||
|
cp \
|
||||||
|
scripts/lib/tsx-cli-shim.mjs \
|
||||||
|
.publication-target/scripts/lib/tsx-cli-shim.mjs
|
||||||
|
cp \
|
||||||
|
scripts/lib/tsx-cli-shim.mjs \
|
||||||
|
.publication-target/scripts/lib/tsx-cli-shim.mjs
|
||||||
|
|
||||||
- name: Setup OIDC publication target
|
- name: Setup OIDC publication target
|
||||||
if: steps.publication_evidence.outputs.publish_route == 'npm-oidc'
|
if: steps.publication_evidence.outputs.publish_route == 'npm-oidc'
|
||||||
@@ -1286,7 +1313,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
PACKAGE_NAME: ${{ matrix.plugin.packageName }}
|
PACKAGE_NAME: ${{ matrix.plugin.packageName }}
|
||||||
PACKAGE_VERSION: ${{ matrix.plugin.version }}
|
PACKAGE_VERSION: ${{ matrix.plugin.version }}
|
||||||
run: node scripts/verify-plugin-npm-published-runtime.mjs "${PACKAGE_NAME}@${PACKAGE_VERSION}"
|
run: node --import tsx scripts/verify-plugin-npm-published-runtime.mts "${PACKAGE_NAME}@${PACKAGE_VERSION}"
|
||||||
|
|
||||||
- name: Check bootstrap npm package version
|
- name: Check bootstrap npm package version
|
||||||
id: bootstrap_npm_package_version
|
id: bootstrap_npm_package_version
|
||||||
@@ -1385,14 +1412,14 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
PACKAGE_NAME: ${{ steps.publication_evidence.outputs.package_name }}
|
PACKAGE_NAME: ${{ steps.publication_evidence.outputs.package_name }}
|
||||||
PACKAGE_VERSION: ${{ steps.publication_evidence.outputs.package_version }}
|
PACKAGE_VERSION: ${{ steps.publication_evidence.outputs.package_version }}
|
||||||
run: node scripts/verify-plugin-npm-published-runtime.mjs "${PACKAGE_NAME}@${PACKAGE_VERSION}"
|
run: node --import tsx scripts/verify-plugin-npm-published-runtime.mts "${PACKAGE_NAME}@${PACKAGE_VERSION}"
|
||||||
|
|
||||||
- name: Verify immutable npm readback runtime
|
- name: Verify immutable npm readback runtime
|
||||||
if: steps.publication_evidence.outputs.publish_route == 'npm-readback'
|
if: steps.publication_evidence.outputs.publish_route == 'npm-readback'
|
||||||
env:
|
env:
|
||||||
PACKAGE_NAME: ${{ steps.publication_evidence.outputs.package_name }}
|
PACKAGE_NAME: ${{ steps.publication_evidence.outputs.package_name }}
|
||||||
PACKAGE_VERSION: ${{ steps.publication_evidence.outputs.package_version }}
|
PACKAGE_VERSION: ${{ steps.publication_evidence.outputs.package_version }}
|
||||||
run: node scripts/verify-plugin-npm-published-runtime.mjs "${PACKAGE_NAME}@${PACKAGE_VERSION}"
|
run: node --import tsx scripts/verify-plugin-npm-published-runtime.mts "${PACKAGE_NAME}@${PACKAGE_VERSION}"
|
||||||
|
|
||||||
- name: Record Meta trusted publisher checkpoint
|
- name: Record Meta trusted publisher checkpoint
|
||||||
if: steps.publication_evidence.outputs.publish_route == 'npm-token-bootstrap'
|
if: steps.publication_evidence.outputs.publish_route == 'npm-token-bootstrap'
|
||||||
|
|||||||
@@ -67,14 +67,27 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: false
|
submodules: false
|
||||||
|
|
||||||
|
- name: Setup manifest TypeScript runtime
|
||||||
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
|
with:
|
||||||
|
node-version: "24.x"
|
||||||
|
|
||||||
|
- name: Setup manifest pnpm
|
||||||
|
uses: ./.github/actions/setup-pnpm-store-cache
|
||||||
|
with:
|
||||||
|
node-version: "24.x"
|
||||||
|
|
||||||
|
- name: Install manifest dependencies
|
||||||
|
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
|
||||||
|
|
||||||
- name: Build plugin prerelease manifest
|
- name: Build plugin prerelease manifest
|
||||||
id: manifest
|
id: manifest
|
||||||
env:
|
env:
|
||||||
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
||||||
FULL_RELEASE_VALIDATION: ${{ inputs.full_release_validation && 'true' || 'false' }}
|
FULL_RELEASE_VALIDATION: ${{ inputs.full_release_validation && 'true' || 'false' }}
|
||||||
run: |
|
run: |
|
||||||
node --input-type=module <<'EOF'
|
node --import tsx --input-type=module <<'EOF'
|
||||||
import { appendFileSync } from "node:fs";
|
import { appendFileSync, existsSync } from "node:fs";
|
||||||
import { execFileSync } from "node:child_process";
|
import { execFileSync } from "node:child_process";
|
||||||
|
|
||||||
const createMatrix = (include) => ({ include });
|
const createMatrix = (include) => ({ include });
|
||||||
@@ -95,9 +108,19 @@ jobs:
|
|||||||
let extensionShards = [];
|
let extensionShards = [];
|
||||||
let nodeShards = [];
|
let nodeShards = [];
|
||||||
|
|
||||||
|
const targetPlanPaths = {
|
||||||
|
"plugin-prerelease-test-plan": "./scripts/lib/plugin-prerelease-test-plan.mts",
|
||||||
|
"extension-test-plan": "./scripts/lib/extension-test-plan.mts",
|
||||||
|
"ci-node-test-plan": "./scripts/lib/ci-node-test-plan.mts",
|
||||||
|
};
|
||||||
|
const targetPlanPath = (name) => {
|
||||||
|
const mtsPath = targetPlanPaths[name];
|
||||||
|
return existsSync(mtsPath) ? mtsPath : mtsPath.replace(/\.mts$/u, ".mjs");
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { assertPluginPrereleaseTestPlanComplete } = await import(
|
const { assertPluginPrereleaseTestPlanComplete } = await import(
|
||||||
"./scripts/lib/plugin-prerelease-test-plan.mjs"
|
targetPlanPath("plugin-prerelease-test-plan")
|
||||||
);
|
);
|
||||||
pluginPrereleasePlan = assertPluginPrereleaseTestPlanComplete();
|
pluginPrereleasePlan = assertPluginPrereleaseTestPlanComplete();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -107,7 +130,8 @@ jobs:
|
|||||||
error && typeof error === "object" && "url" in error ? String(error.url) : "";
|
error && typeof error === "object" && "url" in error ? String(error.url) : "";
|
||||||
if (
|
if (
|
||||||
errorCode === "ERR_MODULE_NOT_FOUND" &&
|
errorCode === "ERR_MODULE_NOT_FOUND" &&
|
||||||
moduleUrl.endsWith("/scripts/lib/plugin-prerelease-test-plan.mjs")
|
(moduleUrl.endsWith("/scripts/lib/plugin-prerelease-test-plan.mjs") ||
|
||||||
|
moduleUrl.endsWith("/scripts/lib/plugin-prerelease-test-plan.mts"))
|
||||||
) {
|
) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Plugin prerelease plan unavailable in target ref; skipping static and Docker plugin prerelease lanes.",
|
"Plugin prerelease plan unavailable in target ref; skipping static and Docker plugin prerelease lanes.",
|
||||||
@@ -119,7 +143,7 @@ jobs:
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { createExtensionTestShards, DEFAULT_EXTENSION_TEST_SHARD_COUNT } = await import(
|
const { createExtensionTestShards, DEFAULT_EXTENSION_TEST_SHARD_COUNT } = await import(
|
||||||
"./scripts/lib/extension-test-plan.mjs"
|
targetPlanPath("extension-test-plan")
|
||||||
);
|
);
|
||||||
extensionShards = createExtensionTestShards({
|
extensionShards = createExtensionTestShards({
|
||||||
shardCount: DEFAULT_EXTENSION_TEST_SHARD_COUNT,
|
shardCount: DEFAULT_EXTENSION_TEST_SHARD_COUNT,
|
||||||
@@ -146,7 +170,8 @@ jobs:
|
|||||||
error && typeof error === "object" && "url" in error ? String(error.url) : "";
|
error && typeof error === "object" && "url" in error ? String(error.url) : "";
|
||||||
if (
|
if (
|
||||||
errorCode === "ERR_MODULE_NOT_FOUND" &&
|
errorCode === "ERR_MODULE_NOT_FOUND" &&
|
||||||
moduleUrl.endsWith("/scripts/lib/extension-test-plan.mjs")
|
(moduleUrl.endsWith("/scripts/lib/extension-test-plan.mjs") ||
|
||||||
|
moduleUrl.endsWith("/scripts/lib/extension-test-plan.mts"))
|
||||||
) {
|
) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Extension test plan unavailable in target ref; skipping extension prerelease shards.",
|
"Extension test plan unavailable in target ref; skipping extension prerelease shards.",
|
||||||
@@ -157,7 +182,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { createNodeTestShards } = await import("./scripts/lib/ci-node-test-plan.mjs");
|
const { createNodeTestShards } = await import(targetPlanPath("ci-node-test-plan"));
|
||||||
nodeShards = createNodeTestShards({
|
nodeShards = createNodeTestShards({
|
||||||
includeReleaseOnlyPluginShards: true,
|
includeReleaseOnlyPluginShards: true,
|
||||||
})
|
})
|
||||||
@@ -178,7 +203,8 @@ jobs:
|
|||||||
error && typeof error === "object" && "url" in error ? String(error.url) : "";
|
error && typeof error === "object" && "url" in error ? String(error.url) : "";
|
||||||
if (
|
if (
|
||||||
errorCode === "ERR_MODULE_NOT_FOUND" &&
|
errorCode === "ERR_MODULE_NOT_FOUND" &&
|
||||||
moduleUrl.endsWith("/scripts/lib/ci-node-test-plan.mjs")
|
(moduleUrl.endsWith("/scripts/lib/ci-node-test-plan.mjs") ||
|
||||||
|
moduleUrl.endsWith("/scripts/lib/ci-node-test-plan.mts"))
|
||||||
) {
|
) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Node test plan unavailable in target ref; skipping release-only plugin Node shard.",
|
"Node test plan unavailable in target ref; skipping release-only plugin Node shard.",
|
||||||
@@ -322,7 +348,7 @@ jobs:
|
|||||||
|
|
||||||
const result = spawnSync(
|
const result = spawnSync(
|
||||||
"pnpm",
|
"pnpm",
|
||||||
["exec", "node", "scripts/test-projects.mjs", ...configs],
|
["exec", "node", "--import", "tsx", "scripts/test-projects.mts", ...configs],
|
||||||
{
|
{
|
||||||
env: childEnv,
|
env: childEnv,
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
|
|||||||
@@ -290,10 +290,10 @@ jobs:
|
|||||||
- name: Build private QA runtime
|
- name: Build private QA runtime
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=8192
|
NODE_OPTIONS: --max-old-space-size=8192
|
||||||
run: node scripts/build-all.mjs qaRuntime
|
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||||
|
|
||||||
- name: Ensure Playwright Chromium
|
- name: Ensure Playwright Chromium
|
||||||
run: node scripts/ensure-playwright-chromium.mjs
|
run: node --import tsx scripts/ensure-playwright-chromium.mts
|
||||||
|
|
||||||
- name: Run QA profile
|
- name: Run QA profile
|
||||||
id: run_profile
|
id: run_profile
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ jobs:
|
|||||||
"scripts/periphery-intersection.mjs",
|
"scripts/periphery-intersection.mjs",
|
||||||
"scripts/ios-configure-signing.sh",
|
"scripts/ios-configure-signing.sh",
|
||||||
"scripts/ios-write-swift-filelist.mjs",
|
"scripts/ios-write-swift-filelist.mjs",
|
||||||
|
"scripts/ios-write-swift-filelist.mts",
|
||||||
"scripts/ios-write-version-xcconfig.sh",
|
"scripts/ios-write-version-xcconfig.sh",
|
||||||
"test/scripts/periphery-intersection.test.ts",
|
"test/scripts/periphery-intersection.test.ts",
|
||||||
], { ignoreReturnCode: true, silent: true });
|
], { ignoreReturnCode: true, silent: true });
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
node --input-type=module <<'EOF'
|
node --import tsx --input-type=module <<'EOF'
|
||||||
import { appendFileSync } from "node:fs";
|
import { appendFileSync } from "node:fs";
|
||||||
import { createNodeTestShards } from "./scripts/lib/ci-node-test-plan.mjs";
|
import { createNodeTestShards } from "./scripts/lib/ci-node-test-plan.mts";
|
||||||
|
|
||||||
// Warm the selected planner envelopes for the striped unit-fast graph
|
// Warm the selected planner envelopes for the striped unit-fast graph
|
||||||
// plus import-bound graphs that remained cold in protected-cache readers.
|
// plus import-bound graphs that remained cold in protected-cache readers.
|
||||||
@@ -90,4 +90,4 @@ jobs:
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
- name: Warm transform and compile caches
|
- name: Warm transform and compile caches
|
||||||
run: node scripts/ci-run-node-test-shard.mjs
|
run: node --import tsx scripts/ci-run-node-test-shard.mts
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ Skills own workflows; root owns hard policy and routing. Product direction and m
|
|||||||
- Typecheck: `tsgo` lanes only (`pnpm tsgo*`, `pnpm check:test-types`); never add `tsc --noEmit`, `typecheck`, `check:types`.
|
- Typecheck: `tsgo` lanes only (`pnpm tsgo*`, `pnpm check:test-types`); never add `tsc --noEmit`, `typecheck`, `check:types`.
|
||||||
- Formatting: `oxfmt`, not Prettier. Write paths with `pnpm format <paths>`; no `format:write` script. Checks use repo wrappers (`pnpm format:*`, `scripts/run-oxlint.mjs`; full `pnpm lint:*` only when scope requires).
|
- Formatting: `oxfmt`, not Prettier. Write paths with `pnpm format <paths>`; no `format:write` script. Checks use repo wrappers (`pnpm format:*`, `scripts/run-oxlint.mjs`; full `pnpm lint:*` only when scope requires).
|
||||||
- SDK surface gate: `pnpm plugin-sdk:surface:check`; no `plugin-sdk:surface-report` script.
|
- SDK surface gate: `pnpm plugin-sdk:surface:check`; no `plugin-sdk:surface-report` script.
|
||||||
- `scripts/*.mjs` exports: matching declaration in sibling `.d.mts` mandatory. `pnpm check:script-declarations` (check-guards) + `check-test-types` enforce; new export without declaration = red CI.
|
- Script implementations use TypeScript where their runtime supports `tsx`; plain-Node lifecycle, packaged, Docker, and loader closures remain JavaScript and are included in the scripts program through `allowJs`.
|
||||||
- Script wrappers: failing or crashed run must end with one final `[tool] FAILED (exit N)` stderr line; crash = nonzero exit. Truncated output must never read as success. Pattern: `scripts/run-oxlint.mjs`.
|
- Script wrappers: failing or crashed run must end with one final `[tool] FAILED (exit N)` stderr line; crash = nonzero exit. Truncated output must never read as success. Pattern: `scripts/run-oxlint.mjs`.
|
||||||
- Tooling crash `Cannot find module ...` right after pulling/merging main = stale `node_modules`, not a code bug. `pnpm install` first; only then debug.
|
- Tooling crash `Cannot find module ...` right after pulling/merging main = stale `node_modules`, not a code bug. `pnpm install` first; only then debug.
|
||||||
- Build before push when build output, packaging, lazy/module boundaries, dynamic imports, or published surfaces can change; agent builds default to the selected remote box unless platform-specific proof requires another remote host.
|
- Build before push when build output, packaging, lazy/module boundaries, dynamic imports, or published surfaces can change; agent builds default to the selected remote box unless platform-specific proof requires another remote host.
|
||||||
|
|||||||
+3
-3
@@ -63,9 +63,9 @@ For coordinated change sets that genuinely need more than 20 PRs, join the **#cl
|
|||||||
- These commands also cover the shared seam/smoke files that the default unit lane skips
|
- These commands also cover the shared seam/smoke files that the default unit lane skips
|
||||||
- If you changed broader runtime behavior, still run the relevant wider lanes (`pnpm test:extensions`, `pnpm test:channels`, or `pnpm test`) before asking for review
|
- If you changed broader runtime behavior, still run the relevant wider lanes (`pnpm test:extensions`, `pnpm test:channels`, or `pnpm test`) before asking for review
|
||||||
- If you touched bundled-plugin boundaries in shared code, run the matching inventories:
|
- If you touched bundled-plugin boundaries in shared code, run the matching inventories:
|
||||||
- `node scripts/check-src-extension-import-boundary.mjs --json` for `src/**`
|
- `node --import tsx scripts/check-src-extension-import-boundary.mts --json` for `src/**`
|
||||||
- `node scripts/check-sdk-package-extension-import-boundary.mjs --json` for `src/plugin-sdk/**` and `packages/**`
|
- `node --import tsx scripts/check-sdk-package-extension-import-boundary.mts --json` for `src/plugin-sdk/**` and `packages/**`
|
||||||
- `node scripts/check-test-helper-extension-import-boundary.mjs --json` for `test/helpers/**`
|
- `node --import tsx scripts/check-test-helper-extension-import-boundary.mts --json` for `test/helpers/**`
|
||||||
- Shared test helpers must use `src/test-utils/bundled-plugin-public-surface.ts` instead of repo-relative `extensions/**` imports. Keep plugin-local deep mocks inside the owning bundled plugin package.
|
- Shared test helpers must use `src/test-utils/bundled-plugin-public-surface.ts` instead of repo-relative `extensions/**` imports. Keep plugin-local deep mocks inside the owning bundled plugin package.
|
||||||
- If you are using an AI coding agent with OpenClaw skills available, run the `autoreview` skill before opening or updating your PR. Address accepted/actionable findings before asking for review.
|
- If you are using an AI coding agent with OpenClaw skills available, run the `autoreview` skill before opening or updating your PR. Address accepted/actionable findings before asking for review.
|
||||||
- Do not submit refactor-only PRs unless a maintainer explicitly requested that refactor for an active fix or deliverable.
|
- Do not submit refactor-only PRs unless a maintainer explicitly requested that refactor for an active fix or deliverable.
|
||||||
|
|||||||
+1
-1
@@ -75,7 +75,7 @@ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
|
|||||||
COPY openclaw.mjs ./
|
COPY openclaw.mjs ./
|
||||||
COPY ui/package.json ./ui/package.json
|
COPY ui/package.json ./ui/package.json
|
||||||
COPY patches ./patches
|
COPY patches ./patches
|
||||||
COPY scripts/postinstall-bundled-plugins.mjs scripts/preinstall-package-manager-warning.mjs scripts/npm-runner.mjs scripts/windows-cmd-helpers.mjs scripts/prepare-git-hooks.mjs ./scripts/
|
COPY scripts/postinstall-bundled-plugins.mjs scripts/preinstall-package-manager-warning.mjs scripts/windows-cmd-helpers.mjs scripts/prepare-git-hooks.mjs ./scripts/
|
||||||
COPY scripts/lib/guard-inventory-utils.mjs ./scripts/lib/guard-inventory-utils.mjs
|
COPY scripts/lib/guard-inventory-utils.mjs ./scripts/lib/guard-inventory-utils.mjs
|
||||||
COPY scripts/lib/package-dist-imports.mjs ./scripts/lib/package-dist-imports.mjs
|
COPY scripts/lib/package-dist-imports.mjs ./scripts/lib/package-dist-imports.mjs
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -275,7 +275,7 @@ Security boundary notes:
|
|||||||
- Enforcement reference points:
|
- Enforcement reference points:
|
||||||
- temp root resolver: `src/infra/tmp-openclaw-dir.ts`
|
- temp root resolver: `src/infra/tmp-openclaw-dir.ts`
|
||||||
- SDK temp helpers: `src/plugin-sdk/temp-path.ts`
|
- SDK temp helpers: `src/plugin-sdk/temp-path.ts`
|
||||||
- messaging/channel tmp guardrail: `scripts/check-no-random-messaging-tmp.mjs`
|
- messaging/channel tmp guardrail: `scripts/check-no-random-messaging-tmp.mts`
|
||||||
|
|
||||||
### Operational Guidance
|
### Operational Guidance
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,9 @@ abstract class StageCanvasA2uiTask
|
|||||||
workingDir(root)
|
workingDir(root)
|
||||||
commandLine(
|
commandLine(
|
||||||
"node",
|
"node",
|
||||||
"scripts/sync-native-a2ui.mjs",
|
"--import",
|
||||||
|
"tsx",
|
||||||
|
"scripts/sync-native-a2ui.mts",
|
||||||
"--write",
|
"--write",
|
||||||
"--output",
|
"--output",
|
||||||
outputDirectory
|
outputDirectory
|
||||||
@@ -172,8 +174,8 @@ val stageCanvasA2ui =
|
|||||||
sourceFiles.from(
|
sourceFiles.from(
|
||||||
openClawRepositoryRoot.resolve("package.json"),
|
openClawRepositoryRoot.resolve("package.json"),
|
||||||
openClawRepositoryRoot.resolve("pnpm-lock.yaml"),
|
openClawRepositoryRoot.resolve("pnpm-lock.yaml"),
|
||||||
openClawRepositoryRoot.resolve("scripts/bundle-a2ui.mjs"),
|
openClawRepositoryRoot.resolve("scripts/bundle-a2ui.mts"),
|
||||||
openClawRepositoryRoot.resolve("scripts/sync-native-a2ui.mjs"),
|
openClawRepositoryRoot.resolve("scripts/sync-native-a2ui.mts"),
|
||||||
openClawRepositoryRoot.resolve("extensions/canvas/package.json"),
|
openClawRepositoryRoot.resolve("extensions/canvas/package.json"),
|
||||||
openClawRepositoryRoot.resolve("extensions/canvas/scripts/bundle-a2ui.mjs"),
|
openClawRepositoryRoot.resolve("extensions/canvas/scripts/bundle-a2ui.mjs"),
|
||||||
openClawRepositoryRoot.resolve("extensions/canvas/src/host/a2ui/index.html"),
|
openClawRepositoryRoot.resolve("extensions/canvas/src/host/a2ui/index.html"),
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ targets:
|
|||||||
resource_bundle="$TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/OpenClawKit_OpenClawKit.bundle"
|
resource_bundle="$TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/OpenClawKit_OpenClawKit.bundle"
|
||||||
test -d "$resource_product"
|
test -d "$resource_product"
|
||||||
test -d "$resource_bundle"
|
test -d "$resource_bundle"
|
||||||
node "$repo_root/scripts/sync-native-a2ui.mjs" \
|
node --import tsx "$repo_root/scripts/sync-native-a2ui.mts" \
|
||||||
--write \
|
--write \
|
||||||
--output "$resource_product/CanvasA2UI"
|
--output "$resource_product/CanvasA2UI"
|
||||||
rm -rf "$resource_bundle/CanvasA2UI"
|
rm -rf "$resource_bundle/CanvasA2UI"
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ The running app gives the headless `openclaw node run` host a single Canvas WebV
|
|||||||
|
|
||||||
The Canvas plugin sources remain the source of truth for the A2UI renderer. Each native build
|
The Canvas plugin sources remain the source of truth for the A2UI renderer. Each native build
|
||||||
generates `index.html` and `a2ui.bundle.js` into its isolated build output before compiling. Run
|
generates `index.html` and `a2ui.bundle.js` into its isolated build output before compiling. Run
|
||||||
`node scripts/sync-native-a2ui.mjs --check` from the repository root to verify fresh bundles are
|
`node --import tsx scripts/sync-native-a2ui.mts --check` from the repository root to verify fresh bundles are
|
||||||
byte-identical and every native build owner is wired.
|
byte-identical and every native build owner is wired.
|
||||||
|
|
||||||
## Quick Chat widgets
|
## Quick Chat widgets
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ fn stage_canvas_a2ui() {
|
|||||||
for input in [
|
for input in [
|
||||||
"package.json",
|
"package.json",
|
||||||
"pnpm-lock.yaml",
|
"pnpm-lock.yaml",
|
||||||
"scripts/bundle-a2ui.mjs",
|
"scripts/bundle-a2ui.mts",
|
||||||
"scripts/sync-native-a2ui.mjs",
|
"scripts/sync-native-a2ui.mts",
|
||||||
"extensions/canvas/package.json",
|
"extensions/canvas/package.json",
|
||||||
"extensions/canvas/scripts/bundle-a2ui.mjs",
|
"extensions/canvas/scripts/bundle-a2ui.mjs",
|
||||||
"extensions/canvas/src/host/a2ui/index.html",
|
"extensions/canvas/src/host/a2ui/index.html",
|
||||||
@@ -44,7 +44,13 @@ fn stage_canvas_a2ui() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let status = Command::new("node")
|
let status = Command::new("node")
|
||||||
.args(["scripts/sync-native-a2ui.mjs", "--write", "--output"])
|
.args([
|
||||||
|
"--import",
|
||||||
|
"tsx",
|
||||||
|
"scripts/sync-native-a2ui.mts",
|
||||||
|
"--write",
|
||||||
|
"--output",
|
||||||
|
])
|
||||||
.arg(&output_dir)
|
.arg(&output_dir)
|
||||||
.current_dir(&repo_root)
|
.current_dir(&repo_root)
|
||||||
.status()
|
.status()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ struct HostEnvOverrideDiagnostics: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum HostEnvSanitizer {
|
enum HostEnvSanitizer {
|
||||||
/// Generated from src/infra/host-env-security-policy.json via scripts/generate-host-env-security-policy-swift.mjs.
|
/// Generated from src/infra/host-env-security-policy.json via scripts/generate-host-env-security-policy-swift.mts.
|
||||||
/// Parity is validated by src/infra/host-env-security.policy-parity.test.ts.
|
/// Parity is validated by src/infra/host-env-security.policy-parity.test.ts.
|
||||||
private static let blockedInheritedKeys = HostEnvSecurityPolicy.blockedInheritedKeys
|
private static let blockedInheritedKeys = HostEnvSecurityPolicy.blockedInheritedKeys
|
||||||
private static let blockedInheritedPrefixes = HostEnvSecurityPolicy.blockedInheritedPrefixes
|
private static let blockedInheritedPrefixes = HostEnvSecurityPolicy.blockedInheritedPrefixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Generated file. Do not edit directly.
|
// Generated file. Do not edit directly.
|
||||||
// Source: src/infra/host-env-security-policy.json
|
// Source: src/infra/host-env-security-policy.json
|
||||||
// Regenerate: node scripts/generate-host-env-security-policy-swift.mjs --write
|
// Regenerate: node --import tsx scripts/generate-host-env-security-policy-swift.mts --write
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const ROOT_TEST_ENTRY_GLOBS = [
|
|||||||
// QA scenario YAML dispatches these scripts/tests by path rather than import.
|
// QA scenario YAML dispatches these scripts/tests by path rather than import.
|
||||||
...QA_SCENARIO_EXECUTION_ENTRIES,
|
...QA_SCENARIO_EXECUTION_ENTRIES,
|
||||||
// Invoked directly by the sandbox bind-conflict E2E verification script.
|
// Invoked directly by the sandbox bind-conflict E2E verification script.
|
||||||
"scripts/e2e-sandbox-bind-conflict.mjs!",
|
"scripts/e2e-sandbox-bind-conflict.mts!",
|
||||||
// The Voice Call QA scenario loads this fixture through a generated plugin directory.
|
// The Voice Call QA scenario loads this fixture through a generated plugin directory.
|
||||||
"test/e2e/qa-lab/runtime/fixtures/voice-call-runtime-plugin/index.js!",
|
"test/e2e/qa-lab/runtime/fixtures/voice-call-runtime-plugin/index.js!",
|
||||||
// Loaded with cache-busting query strings so configuration fallback tests
|
// Loaded with cache-busting query strings so configuration fallback tests
|
||||||
|
|||||||
+32
-11
@@ -1,6 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* Knip configuration for OpenClaw root and bundled plugin dependency hygiene.
|
* Knip configuration for OpenClaw root and bundled plugin dependency hygiene.
|
||||||
*/
|
*/
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
const BUNDLED_PLUGIN_ROOT_DIR = "extensions";
|
const BUNDLED_PLUGIN_ROOT_DIR = "extensions";
|
||||||
|
|
||||||
function bundledPluginFile(pluginId: string, relativePath: string, suffix = ""): string {
|
function bundledPluginFile(pluginId: string, relativePath: string, suffix = ""): string {
|
||||||
@@ -16,13 +19,14 @@ const repositoryScriptEntries = [
|
|||||||
".github/actions/register-bind-mount-cleanup/main.cjs!",
|
".github/actions/register-bind-mount-cleanup/main.cjs!",
|
||||||
".github/actions/register-bind-mount-cleanup/post.cjs!",
|
".github/actions/register-bind-mount-cleanup/post.cjs!",
|
||||||
"apps/android/scripts/build-release-artifacts.ts!",
|
"apps/android/scripts/build-release-artifacts.ts!",
|
||||||
"scripts/build-discord-activity-sdk.mjs!",
|
"scripts/bundle-a2ui.mts!",
|
||||||
|
"scripts/build-discord-activity-sdk.mts!",
|
||||||
"scripts/check-live-cache.ts!",
|
"scripts/check-live-cache.ts!",
|
||||||
"scripts/check-package-dist-imports.mjs!",
|
"scripts/check-package-dist-imports.mjs!",
|
||||||
"scripts/dev/ios-node-e2e.ts!",
|
"scripts/dev/ios-node-e2e.ts!",
|
||||||
"scripts/diffs-shiki-curated.ts!",
|
"scripts/diffs-shiki-curated.ts!",
|
||||||
// Reusable Docker workflows invoke this from the downloaded .release-harness tree.
|
// Reusable Docker workflows invoke this from the downloaded .release-harness tree.
|
||||||
"scripts/docker-e2e.mjs!",
|
"scripts/docker-e2e.mts!",
|
||||||
"scripts/e2e/lib/browser-cdp-snapshot/assert-snapshot.mjs!",
|
"scripts/e2e/lib/browser-cdp-snapshot/assert-snapshot.mjs!",
|
||||||
"scripts/e2e/lib/browser-cdp-snapshot/fixture-server.mjs!",
|
"scripts/e2e/lib/browser-cdp-snapshot/fixture-server.mjs!",
|
||||||
"scripts/e2e/lib/bundled-plugin-install-uninstall/runtime-smoke.mjs!",
|
"scripts/e2e/lib/bundled-plugin-install-uninstall/runtime-smoke.mjs!",
|
||||||
@@ -40,7 +44,7 @@ const repositoryScriptEntries = [
|
|||||||
"scripts/e2e/lib/fixtures/config.mjs!",
|
"scripts/e2e/lib/fixtures/config.mjs!",
|
||||||
"scripts/e2e/lib/fixtures/plugins.mjs!",
|
"scripts/e2e/lib/fixtures/plugins.mjs!",
|
||||||
"scripts/e2e/lib/fixtures/workspace.mjs!",
|
"scripts/e2e/lib/fixtures/workspace.mjs!",
|
||||||
"scripts/e2e/lib/npm-telegram-live/prepare-package.mjs!",
|
"scripts/e2e/lib/npm-telegram-live/prepare-package.mts!",
|
||||||
"scripts/e2e/lib/onboard/assert-config.mjs!",
|
"scripts/e2e/lib/onboard/assert-config.mjs!",
|
||||||
"scripts/e2e/lib/onboard/write-config.mjs!",
|
"scripts/e2e/lib/onboard/write-config.mjs!",
|
||||||
"scripts/e2e/lib/openai-chat-tools/client.mjs!",
|
"scripts/e2e/lib/openai-chat-tools/client.mjs!",
|
||||||
@@ -61,9 +65,9 @@ const repositoryScriptEntries = [
|
|||||||
"scripts/fixtures/packed-plugin-sdk-type-smoke.ts!",
|
"scripts/fixtures/packed-plugin-sdk-type-smoke.ts!",
|
||||||
"scripts/ios-release-cut.ts!",
|
"scripts/ios-release-cut.ts!",
|
||||||
"scripts/ios-release-plan.ts!",
|
"scripts/ios-release-plan.ts!",
|
||||||
"scripts/ios-release-signing.mjs!",
|
"scripts/ios-release-signing.mts!",
|
||||||
"scripts/lib/docker-plugin-selection.mjs!",
|
"scripts/lib/docker-plugin-selection.mjs!",
|
||||||
"scripts/lib/openclaw-test-state.mjs!",
|
"scripts/lib/openclaw-test-state.mts!",
|
||||||
"scripts/list-prod-store-packages.mjs!",
|
"scripts/list-prod-store-packages.mjs!",
|
||||||
// Invoked by scripts/lib/live-docker-stage.sh during container validation.
|
// Invoked by scripts/lib/live-docker-stage.sh during container validation.
|
||||||
"scripts/live-docker-normalize-config.ts!",
|
"scripts/live-docker-normalize-config.ts!",
|
||||||
@@ -73,10 +77,10 @@ const repositoryScriptEntries = [
|
|||||||
"scripts/openclaw-release-clawhub-runtime-state.ts!",
|
"scripts/openclaw-release-clawhub-runtime-state.ts!",
|
||||||
// Oxlint loads this JS plugin by path from config/oxlint/boundary-guards.json.
|
// Oxlint loads this JS plugin by path from config/oxlint/boundary-guards.json.
|
||||||
"scripts/oxlint-boundary-guards.mjs!",
|
"scripts/oxlint-boundary-guards.mjs!",
|
||||||
"scripts/plugin-prerelease-liveish-matrix.mjs!",
|
"scripts/plugin-prerelease-liveish-matrix.mts!",
|
||||||
// Generates the checked-in native protocol models from core descriptor metadata.
|
// Generates the checked-in native protocol models from core descriptor metadata.
|
||||||
"scripts/protocol-gen.ts!",
|
"scripts/protocol-gen.ts!",
|
||||||
"scripts/pr-gates-lock.mjs!",
|
"scripts/pr-gates-lock.mts!",
|
||||||
"scripts/pr-lib/ci-dispatch.mjs!",
|
"scripts/pr-lib/ci-dispatch.mjs!",
|
||||||
"scripts/pr-lib/review-artifacts.mjs!",
|
"scripts/pr-lib/review-artifacts.mjs!",
|
||||||
"scripts/pr-lib/process-group-runner.mjs!",
|
"scripts/pr-lib/process-group-runner.mjs!",
|
||||||
@@ -87,7 +91,7 @@ const repositoryScriptEntries = [
|
|||||||
"scripts/secrets/openclaw-bws-resolver.mjs!",
|
"scripts/secrets/openclaw-bws-resolver.mjs!",
|
||||||
"scripts/sqlite-session-entry-cache-lifetime-proof.ts!",
|
"scripts/sqlite-session-entry-cache-lifetime-proof.ts!",
|
||||||
"scripts/sync-labels.ts!",
|
"scripts/sync-labels.ts!",
|
||||||
"scripts/test-built-bundled-channel-entry-smoke.mjs!",
|
"scripts/test-built-bundled-channel-entry-smoke.mts!",
|
||||||
"scripts/update-clawtributors.ts!",
|
"scripts/update-clawtributors.ts!",
|
||||||
"scripts/verify-stable-main-closeout.mjs!",
|
"scripts/verify-stable-main-closeout.mjs!",
|
||||||
"scripts/write-package-dist-inventory.ts!",
|
"scripts/write-package-dist-inventory.ts!",
|
||||||
@@ -97,8 +101,25 @@ const repositoryScriptEntries = [
|
|||||||
"skills/meme-maker/scripts/meme.mjs!",
|
"skills/meme-maker/scripts/meme.mjs!",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
// Compatibility shims are executable roots and load their typed implementations by computed URL,
|
||||||
|
// which Knip cannot follow in either direction.
|
||||||
|
function listScriptShimEntries(dir = "scripts"): string[] {
|
||||||
|
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
||||||
|
const entryPath = path.join(dir, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
return listScriptShimEntries(entryPath);
|
||||||
|
}
|
||||||
|
if (!entry.isFile() || (!entry.name.endsWith(".mjs") && !entry.name.endsWith(".js"))) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const implementationPath = entryPath.replace(/\.(?:mjs|js)$/u, ".mts");
|
||||||
|
return fs.existsSync(implementationPath) ? [`${entryPath}!`, `${implementationPath}!`] : [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const rootEntries = [
|
const rootEntries = [
|
||||||
...repositoryScriptEntries,
|
...repositoryScriptEntries,
|
||||||
|
...listScriptShimEntries(),
|
||||||
// Knip loads these audit configurations directly by command-line path.
|
// Knip loads these audit configurations directly by command-line path.
|
||||||
"config/knip.config.ts!",
|
"config/knip.config.ts!",
|
||||||
"config/knip.all-exports.config.ts!",
|
"config/knip.all-exports.config.ts!",
|
||||||
@@ -161,7 +182,7 @@ const rootEntries = [
|
|||||||
// Package-script owners invoke these generated-artifact modules directly.
|
// Package-script owners invoke these generated-artifact modules directly.
|
||||||
"src/config/doc-baseline.ts!",
|
"src/config/doc-baseline.ts!",
|
||||||
"src/plugins/runtime-sidecar-paths-baseline.ts!",
|
"src/plugins/runtime-sidecar-paths-baseline.ts!",
|
||||||
// Imported by scripts/tsdown-build.mjs as the AI package build configuration.
|
// Imported by scripts/tsdown-build.mts as the AI package build configuration.
|
||||||
"tsdown.ai.config.ts!",
|
"tsdown.ai.config.ts!",
|
||||||
// Maintainer-owned compatibility data referenced by release/docs workflows.
|
// Maintainer-owned compatibility data referenced by release/docs workflows.
|
||||||
"src/commands/doctor/shared/deprecation-compat.ts!",
|
"src/commands/doctor/shared/deprecation-compat.ts!",
|
||||||
@@ -266,7 +287,7 @@ const rootToolingAndWorkspaceDependencies = [
|
|||||||
"@lit-labs/signals",
|
"@lit-labs/signals",
|
||||||
"@lit/context",
|
"@lit/context",
|
||||||
"@lit/task",
|
"@lit/task",
|
||||||
// scripts/ui.js anchors these lookups at ui/package.json before invoking the UI workspace.
|
// scripts/ui.mts anchors these lookups at ui/package.json before invoking the UI workspace.
|
||||||
"@vitest/browser-playwright",
|
"@vitest/browser-playwright",
|
||||||
"dompurify",
|
"dompurify",
|
||||||
// Root typecheck/test projects compile @openclaw/net-policy source directly.
|
// Root typecheck/test projects compile @openclaw/net-policy source directly.
|
||||||
@@ -691,7 +712,7 @@ const config = {
|
|||||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/deepinfra`]: bundledPluginWorkspace(),
|
[`${BUNDLED_PLUGIN_ROOT_DIR}/deepinfra`]: bundledPluginWorkspace(),
|
||||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/discord`]: bundledPluginWorkspace(),
|
[`${BUNDLED_PLUGIN_ROOT_DIR}/discord`]: bundledPluginWorkspace(),
|
||||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/diffs`]: bundledPluginWorkspace([
|
[`${BUNDLED_PLUGIN_ROOT_DIR}/diffs`]: bundledPluginWorkspace([
|
||||||
// scripts/build-diffs-viewer-runtime.mjs bundles this browser entry.
|
// scripts/build-diffs-viewer-runtime.mts bundles this browser entry.
|
||||||
"src/viewer-client.ts!",
|
"src/viewer-client.ts!",
|
||||||
]),
|
]),
|
||||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/elevenlabs`]: bundledPluginWorkspace(),
|
[`${BUNDLED_PLUGIN_ROOT_DIR}/elevenlabs`]: bundledPluginWorkspace(),
|
||||||
|
|||||||
@@ -5,10 +5,21 @@
|
|||||||
* companion pass keeps the rest of scripts/** as library project files and
|
* companion pass keeps the rest of scripts/** as library project files and
|
||||||
* makes repository tests real consumers of deliberately testable helpers.
|
* makes repository tests real consumers of deliberately testable helpers.
|
||||||
*/
|
*/
|
||||||
|
import fs from "node:fs";
|
||||||
import productionConfig from "./knip.config.ts";
|
import productionConfig from "./knip.config.ts";
|
||||||
|
|
||||||
const scriptEntries = productionConfig.workspaces["."].entry.filter((entry) =>
|
function isTypedShimImplementationEntry(entry: string): boolean {
|
||||||
entry.startsWith("scripts/"),
|
const filePath = entry.endsWith("!") ? entry.slice(0, -1) : entry;
|
||||||
|
// The export-free Crabbox implementation must remain a root so its library imports stay live.
|
||||||
|
if (!filePath.endsWith(".mts") || filePath === "scripts/crabbox-wrapper.mts") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const basePath = filePath.slice(0, -".mts".length);
|
||||||
|
return fs.existsSync(`${basePath}.mjs`) || fs.existsSync(`${basePath}.js`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptEntries = productionConfig.workspaces["."].entry.filter(
|
||||||
|
(entry) => entry.startsWith("scripts/") && !isTypedShimImplementationEntry(entry),
|
||||||
);
|
);
|
||||||
|
|
||||||
const repositoryToolEntries = [
|
const repositoryToolEntries = [
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ excluded:
|
|||||||
- "*.playground"
|
- "*.playground"
|
||||||
# Generated (protocol-gen-swift.ts)
|
# Generated (protocol-gen-swift.ts)
|
||||||
- ../apps/macos/Sources/OpenClawProtocol/GatewayModels.swift
|
- ../apps/macos/Sources/OpenClawProtocol/GatewayModels.swift
|
||||||
# Generated (generate-host-env-security-policy-swift.mjs)
|
# Generated (generate-host-env-security-policy-swift.mts)
|
||||||
- ../apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift
|
- ../apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift
|
||||||
|
|
||||||
analyzer_rules:
|
analyzer_rules:
|
||||||
|
|||||||
+4
-4
@@ -120,7 +120,7 @@ The slowest Node test families are split or balanced so each job stays small wit
|
|||||||
- Node shard and build-artifact jobs also restore Node's portable on-disk compile cache through immutable Actions caches. Independent `test` and `build` namespaces prevent their writers from replacing each other's archives: the scheduled test warmer owns the protected test seed, while `build-artifacts` may publish at most one protected build archive per UTC day from trusted `main` pushes. PR and ordinary test jobs only read protected snapshots, so feature-branch bytecode never enters the shared seed and PR traffic creates no cache archives. This reuses V8 bytecode for Node-loaded orchestration, build tooling, and external dependencies across different checkout paths, including when only part of the source graph changes. Vitest child processes disable an inherited compile cache because coverage can be enabled inside dynamic configs and V8 coverage can lose source-position precision when scripts are deserialized from bytecode.
|
- Node shard and build-artifact jobs also restore Node's portable on-disk compile cache through immutable Actions caches. Independent `test` and `build` namespaces prevent their writers from replacing each other's archives: the scheduled test warmer owns the protected test seed, while `build-artifacts` may publish at most one protected build archive per UTC day from trusted `main` pushes. PR and ordinary test jobs only read protected snapshots, so feature-branch bytecode never enters the shared seed and PR traffic creates no cache archives. This reuses V8 bytecode for Node-loaded orchestration, build tooling, and external dependencies across different checkout paths, including when only part of the source graph changes. Vitest child processes disable an inherited compile cache because coverage can be enabled inside dynamic configs and V8 coverage can lose source-position precision when scripts are deserialized from bytecode.
|
||||||
- The build-artifact job also persists content-fingerprinted `build-all` step outputs. CI's self-built plugin SDK declarations hash the complete repository-owned TypeScript/JSON source graph, exclude installed and generated directories, and restore both flat declarations and package bridges after `tsdown` clears `dist`. Documentation, workflow, plugin, and other changes outside that graph can reuse the declaration snapshot; source changes rebuild it before the export gate runs.
|
- The build-artifact job also persists content-fingerprinted `build-all` step outputs. CI's self-built plugin SDK declarations hash the complete repository-owned TypeScript/JSON source graph, exclude installed and generated directories, and restore both flat declarations and package bridges after `tsdown` clears `dist`. Documentation, workflow, plugin, and other changes outside that graph can reuse the declaration snapshot; source changes rebuild it before the export gate runs.
|
||||||
- Full declaration builds split `tsdown` into AI, workspace-package, and unified groups. Each group caches declarations only, then still rebuilds runtime JavaScript before restoring those declarations. Core or plugin changes therefore invalidate only the large unified graph, while workspace-package changes conservatively invalidate every dependent declaration group. Public full builds generally use an immutable Actions cache; coarse restore keys seed partial changes, per-group content fingerprints reject stale data, and GitHub's cache quota evicts old generations. The weekly Node 22 lane instead publishes a 14-day artifact after successful `main` runs and restores only artifacts whose immutable producer identity resolves to that workflow on `main`, avoiding quota churn without allowing PR code to write a shared cache. Private-QA declarations are never persisted in Actions caches because cache namespaces are not confidentiality boundaries.
|
- Full declaration builds split `tsdown` into AI, workspace-package, and unified groups. Each group caches declarations only, then still rebuilds runtime JavaScript before restoring those declarations. Core or plugin changes therefore invalidate only the large unified graph, while workspace-package changes conservatively invalidate every dependent declaration group. Public full builds generally use an immutable Actions cache; coarse restore keys seed partial changes, per-group content fingerprints reject stale data, and GitHub's cache quota evicts old generations. The weekly Node 22 lane instead publishes a 14-day artifact after successful `main` runs and restores only artifacts whose immutable producer identity resolves to that workflow on `main`, avoiding quota churn without allowing PR code to write a shared cache. Private-QA declarations are never persisted in Actions caches because cache namespaces are not confidentiality boundaries.
|
||||||
- `check-additional-*` stripes the supplemental boundary guard list (`scripts/run-additional-boundary-checks.mjs`) into one prompt-heavy shard (`check-additional-boundaries-a`, which includes the Codex prompt snapshot drift check) and one combined shard for the remaining stripes (`check-additional-boundaries-bcd`), each running independent guards concurrently and printing per-check timings. Package-boundary compile/canary work stays together, and runtime topology architecture runs separately from the gateway watch coverage embedded in `build-artifacts`.
|
- `check-additional-*` stripes the supplemental boundary guard list (`scripts/run-additional-boundary-checks.mts`) into one prompt-heavy shard (`check-additional-boundaries-a`, which includes the Codex prompt snapshot drift check) and one combined shard for the remaining stripes (`check-additional-boundaries-bcd`), each running independent guards concurrently and printing per-check timings. Package-boundary compile/canary work stays together, and runtime topology architecture runs separately from the gateway watch coverage embedded in `build-artifacts`.
|
||||||
- On the 32-vCPU self-hosted build runner, Gateway watch, channel tests, and the core support-boundary shard start together inside `build-artifacts` after `dist/` and `dist-runtime/` are already built. GitHub-hosted fallback runs keep Gateway watch serial so low-core contention cannot consume its readiness deadline. Both paths then run the two built TUI PTY artifact canaries alone; the dedicated Node shard owns the full serial suite.
|
- On the 32-vCPU self-hosted build runner, Gateway watch, channel tests, and the core support-boundary shard start together inside `build-artifacts` after `dist/` and `dist-runtime/` are already built. GitHub-hosted fallback runs keep Gateway watch serial so low-core contention cannot consume its readiness deadline. Both paths then run the two built TUI PTY artifact canaries alone; the dedicated Node shard owns the full serial suite.
|
||||||
|
|
||||||
Once admitted, canonical Linux CI permits up to 28 concurrent Node test jobs and
|
Once admitted, canonical Linux CI permits up to 28 concurrent Node test jobs and
|
||||||
@@ -219,7 +219,7 @@ ratchet-down when cleanup lowers the real count.
|
|||||||
|
|
||||||
- `config/env-var-count-budget.txt` caps the number of distinct `OPENCLAW_*`
|
- `config/env-var-count-budget.txt` caps the number of distinct `OPENCLAW_*`
|
||||||
names in production source under `src/`, `packages/`, and `extensions/`
|
names in production source under `src/`, `packages/`, and `extensions/`
|
||||||
(tests and QA Lab excluded). Checked by `node scripts/check-env-var-count.mjs`.
|
(tests and QA Lab excluded). Checked by `node --import tsx scripts/check-env-var-count.mts`.
|
||||||
Removing env vars: lower the number in the same PR. Adding one is a
|
Removing env vars: lower the number in the same PR. Adding one is a
|
||||||
config-surface decision — justify it in the PR body.
|
config-surface decision — justify it in the PR body.
|
||||||
- `docs/.generated/config-baseline.counts.json` caps the per-kind
|
- `docs/.generated/config-baseline.counts.json` caps the per-kind
|
||||||
@@ -521,7 +521,7 @@ The slow Bun global install image-provider smoke is separately gated by `run_bun
|
|||||||
- a bare Node/Git runner for installer/update/plugin-dependency lanes;
|
- a bare Node/Git runner for installer/update/plugin-dependency lanes;
|
||||||
- a functional image that installs the same tarball into `/app` for normal functionality lanes.
|
- a functional image that installs the same tarball into `/app` for normal functionality lanes.
|
||||||
|
|
||||||
Docker lane definitions live in `scripts/lib/docker-e2e-scenarios.mjs`, planner logic lives in `scripts/lib/docker-e2e-plan.mjs`, and the runner only executes the selected plan. The scheduler selects the image per lane with `OPENCLAW_DOCKER_E2E_BARE_IMAGE` and `OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE`, then runs lanes with `OPENCLAW_SKIP_DOCKER_BUILD=1`.
|
Docker lane definitions live in `scripts/lib/docker-e2e-scenarios.mts`, planner logic lives in `scripts/lib/docker-e2e-plan.mts`, and the runner only executes the selected plan. The scheduler selects the image per lane with `OPENCLAW_DOCKER_E2E_BARE_IMAGE` and `OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE`, then runs lanes with `OPENCLAW_SKIP_DOCKER_BUILD=1`.
|
||||||
|
|
||||||
### Tunables
|
### Tunables
|
||||||
|
|
||||||
@@ -679,7 +679,7 @@ Local changed-lane logic lives in `scripts/changed-lanes.mjs` and is executed by
|
|||||||
- release metadata-only version bumps run targeted version/config/root-dependency checks;
|
- release metadata-only version bumps run targeted version/config/root-dependency checks;
|
||||||
- unknown root/config changes fail safe to all check lanes.
|
- unknown root/config changes fail safe to all check lanes.
|
||||||
|
|
||||||
Local changed-test routing lives in `scripts/test-projects.test-support.mjs` and is intentionally cheaper than `check:changed`: direct test edits run themselves, source edits prefer explicit mappings, then sibling tests and import-graph dependents. Shared group-room delivery config is one of the explicit mappings: changes to the group visible-reply config, source reply delivery mode, or the message-tool system prompt route through the core reply tests plus Discord and Slack delivery regressions so a shared default change fails before the first PR push. Use `OPENCLAW_TEST_CHANGED_BROAD=1 pnpm test:changed` only when the change is harness-wide enough that the cheap mapped set is not a trustworthy proxy.
|
Local changed-test routing lives in `scripts/test-projects.test-support.mts` and is intentionally cheaper than `check:changed`: direct test edits run themselves, source edits prefer explicit mappings, then sibling tests and import-graph dependents. Shared group-room delivery config is one of the explicit mappings: changes to the group visible-reply config, source reply delivery mode, or the message-tool system prompt route through the core reply tests plus Discord and Slack delivery regressions so a shared default change fails before the first PR push. Use `OPENCLAW_TEST_CHANGED_BROAD=1 pnpm test:changed` only when the change is harness-wide enough that the cheap mapped set is not a trustworthy proxy.
|
||||||
|
|
||||||
## Testbox validation
|
## Testbox validation
|
||||||
|
|
||||||
|
|||||||
@@ -416,7 +416,7 @@ OPENCLAW_LIVE_CODEX_HARNESS=1 \
|
|||||||
OPENCLAW_LIVE_CODEX_HARNESS_COMPACTION_STRESS_TURNS=8 \
|
OPENCLAW_LIVE_CODEX_HARNESS_COMPACTION_STRESS_TURNS=8 \
|
||||||
OPENCLAW_LIVE_CODEX_HARNESS_LARGE_OUTPUT_BYTES=800000 \
|
OPENCLAW_LIVE_CODEX_HARNESS_LARGE_OUTPUT_BYTES=800000 \
|
||||||
OPENCLAW_LIVE_CODEX_HARNESS_DEBUG=1 \
|
OPENCLAW_LIVE_CODEX_HARNESS_DEBUG=1 \
|
||||||
node scripts/test-live.mjs --quiet src/gateway/gateway-codex-harness.live.test.ts
|
node --import tsx scripts/test-live.mts --quiet src/gateway/gateway-codex-harness.live.test.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
GPT-5.6 native Codex matrix:
|
GPT-5.6 native Codex matrix:
|
||||||
@@ -456,7 +456,7 @@ OPENCLAW_LIVE_OPENAI_LONG_CONTEXT=1 \
|
|||||||
OPENCLAW_LIVE_OPENAI_LONG_CONTEXT_PROFILE=full \
|
OPENCLAW_LIVE_OPENAI_LONG_CONTEXT_PROFILE=full \
|
||||||
OPENCLAW_LIVE_OPENAI_LONG_CONTEXT_METRICS=1 \
|
OPENCLAW_LIVE_OPENAI_LONG_CONTEXT_METRICS=1 \
|
||||||
OPENCLAW_LIVE_OPENAI_LONG_CONTEXT_OUTPUT=1 \
|
OPENCLAW_LIVE_OPENAI_LONG_CONTEXT_OUTPUT=1 \
|
||||||
node scripts/test-live.mjs --quiet src/gateway/gateway-openai-long-context.live.test.ts
|
node --import tsx scripts/test-live.mts --quiet src/gateway/gateway-openai-long-context.live.test.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
Reduced-budget recipe:
|
Reduced-budget recipe:
|
||||||
@@ -466,7 +466,7 @@ OPENCLAW_LIVE_OPENAI_LONG_CONTEXT=1 \
|
|||||||
OPENCLAW_LIVE_OPENAI_LONG_CONTEXT_PROFILE=reduced \
|
OPENCLAW_LIVE_OPENAI_LONG_CONTEXT_PROFILE=reduced \
|
||||||
OPENCLAW_LIVE_OPENAI_LONG_CONTEXT_METRICS=1 \
|
OPENCLAW_LIVE_OPENAI_LONG_CONTEXT_METRICS=1 \
|
||||||
OPENCLAW_LIVE_OPENAI_LONG_CONTEXT_OUTPUT=1 \
|
OPENCLAW_LIVE_OPENAI_LONG_CONTEXT_OUTPUT=1 \
|
||||||
node scripts/test-live.mjs --quiet src/gateway/gateway-openai-long-context.live.test.ts
|
node --import tsx scripts/test-live.mts --quiet src/gateway/gateway-openai-long-context.live.test.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
### Long-context hard oracles
|
### Long-context hard oracles
|
||||||
|
|||||||
@@ -741,7 +741,7 @@ Native dependency policy:
|
|||||||
committed diff and prints wall time plus macOS max RSS.
|
committed diff and prints wall time plus macOS max RSS.
|
||||||
- `pnpm test:perf:changed:bench -- --worktree` benchmarks the current
|
- `pnpm test:perf:changed:bench -- --worktree` benchmarks the current
|
||||||
dirty tree by routing the changed file list through
|
dirty tree by routing the changed file list through
|
||||||
`scripts/test-projects.mjs` and the root Vitest config.
|
`scripts/test-projects.mts` and the root Vitest config.
|
||||||
- `pnpm test:perf:profile:main` writes a main-thread CPU profile for
|
- `pnpm test:perf:profile:main` writes a main-thread CPU profile for
|
||||||
Vitest/Vite startup and transform overhead.
|
Vitest/Vite startup and transform overhead.
|
||||||
- `pnpm test:perf:profile:runner` writes runner CPU+heap profiles for
|
- `pnpm test:perf:profile:runner` writes runner CPU+heap profiles for
|
||||||
@@ -882,9 +882,9 @@ These Docker runners split into two buckets:
|
|||||||
`OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=45000`, and
|
`OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=45000`, and
|
||||||
`OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=90000`. Set `OPENCLAW_LIVE_MAX_MODELS`
|
`OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=90000`. Set `OPENCLAW_LIVE_MAX_MODELS`
|
||||||
or the gateway env vars when you explicitly want a smaller cap or larger scan.
|
or the gateway env vars when you explicitly want a smaller cap or larger scan.
|
||||||
- `test:docker:all` builds the live Docker image once via `test:docker:live-build`, packs OpenClaw once as an npm tarball through `scripts/package-openclaw-for-docker.mjs`, then builds/reuses two `scripts/e2e/Dockerfile` images. The bare image is only the Node/Git runner for install/update/plugin-dependency lanes; those lanes mount the prebuilt tarball. The functional image installs the same tarball into `/app` for built-app functionality lanes. Docker lane definitions live in `scripts/lib/docker-e2e-scenarios.mjs`; planner logic lives in `scripts/lib/docker-e2e-plan.mjs`; `scripts/test-docker-all.mjs` executes the selected plan. The aggregate uses a weighted local scheduler: `OPENCLAW_DOCKER_ALL_PARALLELISM` controls process slots, while resource caps keep heavy live, npm-install, and multi-service lanes from all starting at once. If a single lane is heavier than the active caps, the scheduler can still start it when the pool is empty and then keeps it running alone until capacity is available again. Defaults are 10 slots, `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=9`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=5`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7`; tune `OPENCLAW_DOCKER_ALL_WEIGHT_LIMIT` or `OPENCLAW_DOCKER_ALL_DOCKER_LIMIT` (and other `OPENCLAW_DOCKER_ALL_<RESOURCE>_LIMIT` overrides) only when the Docker host has more headroom. The runner performs a Docker preflight by default, removes stale OpenClaw E2E containers, prints status every 30 seconds, stores successful lane timings in `.artifacts/docker-tests/lane-timings.json`, and uses those timings to start longer lanes first on later runs. Use `OPENCLAW_DOCKER_ALL_DRY_RUN=1` to print the weighted lane manifest without building or running Docker, or `node scripts/test-docker-all.mjs --plan-json` to print the CI plan for selected lanes, package/image needs, and credentials.
|
- `test:docker:all` builds the live Docker image once via `test:docker:live-build`, packs OpenClaw once as an npm tarball through `scripts/package-openclaw-for-docker.mjs`, then builds/reuses two `scripts/e2e/Dockerfile` images. The bare image is only the Node/Git runner for install/update/plugin-dependency lanes; those lanes mount the prebuilt tarball. The functional image installs the same tarball into `/app` for built-app functionality lanes. Docker lane definitions live in `scripts/lib/docker-e2e-scenarios.mts`; planner logic lives in `scripts/lib/docker-e2e-plan.mts`; `scripts/test-docker-all.mjs` executes the selected plan. The aggregate uses a weighted local scheduler: `OPENCLAW_DOCKER_ALL_PARALLELISM` controls process slots, while resource caps keep heavy live, npm-install, and multi-service lanes from all starting at once. If a single lane is heavier than the active caps, the scheduler can still start it when the pool is empty and then keeps it running alone until capacity is available again. Defaults are 10 slots, `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=9`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=5`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7`; tune `OPENCLAW_DOCKER_ALL_WEIGHT_LIMIT` or `OPENCLAW_DOCKER_ALL_DOCKER_LIMIT` (and other `OPENCLAW_DOCKER_ALL_<RESOURCE>_LIMIT` overrides) only when the Docker host has more headroom. The runner performs a Docker preflight by default, removes stale OpenClaw E2E containers, prints status every 30 seconds, stores successful lane timings in `.artifacts/docker-tests/lane-timings.json`, and uses those timings to start longer lanes first on later runs. Use `OPENCLAW_DOCKER_ALL_DRY_RUN=1` to print the weighted lane manifest without building or running Docker, or `node scripts/test-docker-all.mjs --plan-json` to print the CI plan for selected lanes, package/image needs, and credentials.
|
||||||
- `Package Acceptance` is the GitHub-native package gate for "does this installable tarball work as a product?" It resolves one candidate package from `source=npm`, `source=ref`, `source=url`, `source=trusted-url`, or `source=artifact`, uploads it as `package-under-test`, then runs the reusable Docker E2E lanes against that exact tarball instead of repacking the selected ref. Profiles are ordered by breadth: `smoke`, `package`, `product`, and `full` (plus `custom` for an explicit lane list). See [Testing updates and plugins](/help/testing-updates-plugins) for the package/update/plugin contract, published-upgrade survivor matrix, release defaults, and failure triage.
|
- `Package Acceptance` is the GitHub-native package gate for "does this installable tarball work as a product?" It resolves one candidate package from `source=npm`, `source=ref`, `source=url`, `source=trusted-url`, or `source=artifact`, uploads it as `package-under-test`, then runs the reusable Docker E2E lanes against that exact tarball instead of repacking the selected ref. Profiles are ordered by breadth: `smoke`, `package`, `product`, and `full` (plus `custom` for an explicit lane list). See [Testing updates and plugins](/help/testing-updates-plugins) for the package/update/plugin contract, published-upgrade survivor matrix, release defaults, and failure triage.
|
||||||
- Build and release checks run `scripts/check-cli-bootstrap-imports.mjs` after tsdown. The guard walks the static built graph from `dist/entry.js` and `dist/cli/run-main.js` and fails if that pre-dispatch bootstrap graph statically imports any external package (Commander, prompt UI, undici, logging, and similar startup-heavy deps all count) before command dispatch; it also caps the bundled gateway run chunk at 70 KB and rejects static imports of known cold gateway paths (`control-ui-assets`, `diagnostic-stability-bundle`, `onboard-helpers`, `process-respawn`, `restart-sentinel`, `server-close`, `server-reload-handlers`) from that chunk. `scripts/release-check.ts` separately smoke-tests the packed CLI with `--help`, `onboard --help`, `doctor --help`, `status --json --timeout 1`, `config schema`, and `models list --provider openai`.
|
- Build and release checks run `scripts/check-cli-bootstrap-imports.mts` after tsdown. The guard walks the static built graph from `dist/entry.js` and `dist/cli/run-main.js` and fails if that pre-dispatch bootstrap graph statically imports any external package (Commander, prompt UI, undici, logging, and similar startup-heavy deps all count) before command dispatch; it also caps the bundled gateway run chunk at 70 KB and rejects static imports of known cold gateway paths (`control-ui-assets`, `diagnostic-stability-bundle`, `onboard-helpers`, `process-respawn`, `restart-sentinel`, `server-close`, `server-reload-handlers`) from that chunk. `scripts/release-check.ts` separately smoke-tests the packed CLI with `--help`, `onboard --help`, `doctor --help`, `status --json --timeout 1`, `config schema`, and `models list --provider openai`.
|
||||||
- Package Acceptance legacy compatibility is capped at `2026.4.25` (`2026.4.25-beta.*` included). Through that cutoff, the harness tolerates only shipped-package metadata gaps: omitted private QA inventory entries, missing `gateway install --wrapper`, missing patch files in the tarball-derived git fixture, missing persisted `update.channel`, legacy plugin install-record locations, missing marketplace install-record persistence, and config metadata migration during `plugins update`. For packages after `2026.4.25`, those paths are strict failures.
|
- Package Acceptance legacy compatibility is capped at `2026.4.25` (`2026.4.25-beta.*` included). Through that cutoff, the harness tolerates only shipped-package metadata gaps: omitted private QA inventory entries, missing `gateway install --wrapper`, missing patch files in the tarball-derived git fixture, missing persisted `update.channel`, legacy plugin install-record locations, missing marketplace install-record persistence, and config metadata migration during `plugins update`. For packages after `2026.4.25`, those paths are strict failures.
|
||||||
- Container smoke runners: `test:docker:openwebui`, `test:docker:onboard`, `test:docker:npm-onboard-channel-agent`, `test:docker:release-user-journey`, `test:docker:release-typed-onboarding`, `test:docker:release-media-memory`, `test:docker:release-upgrade-user-journey`, `test:docker:release-plugin-marketplace`, `test:docker:skill-install`, `test:docker:update-channel-switch`, `test:docker:upgrade-survivor`, `test:docker:published-upgrade-survivor`, `test:docker:session-runtime-context`, `test:docker:agents-delete-shared-workspace`, `test:docker:gateway-network`, `test:docker:browser-cdp-snapshot`, `test:docker:mcp-channels`, `test:docker:agent-bundle-mcp-tools`, `test:docker:cron-mcp-cleanup`, `test:docker:plugins`, `test:docker:plugin-update`, `test:docker:plugin-lifecycle-matrix`, and `test:docker:config-reload` boot one or more real containers and verify higher-level integration paths.
|
- Container smoke runners: `test:docker:openwebui`, `test:docker:onboard`, `test:docker:npm-onboard-channel-agent`, `test:docker:release-user-journey`, `test:docker:release-typed-onboarding`, `test:docker:release-media-memory`, `test:docker:release-upgrade-user-journey`, `test:docker:release-plugin-marketplace`, `test:docker:skill-install`, `test:docker:update-channel-switch`, `test:docker:upgrade-survivor`, `test:docker:published-upgrade-survivor`, `test:docker:session-runtime-context`, `test:docker:agents-delete-shared-workspace`, `test:docker:gateway-network`, `test:docker:browser-cdp-snapshot`, `test:docker:mcp-channels`, `test:docker:agent-bundle-mcp-tools`, `test:docker:cron-mcp-cleanup`, `test:docker:plugins`, `test:docker:plugin-update`, `test:docker:plugin-lifecycle-matrix`, and `test:docker:config-reload` boot one or more real containers and verify higher-level integration paths.
|
||||||
- Docker/Bash E2E lanes that install the packed OpenClaw tarball through `scripts/lib/openclaw-e2e-instance.sh` cap `npm install` at `OPENCLAW_E2E_NPM_INSTALL_TIMEOUT` (default `600s`; set `0` to disable the wrapper for debugging).
|
- Docker/Bash E2E lanes that install the packed OpenClaw tarball through `scripts/lib/openclaw-e2e-instance.sh` cap `npm install` at `OPENCLAW_E2E_NPM_INSTALL_TIMEOUT` (default `600s`; set `0` to disable the wrapper for debugging).
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ pnpm test src/plugins/contracts/runtime-seams.contract.test.ts
|
|||||||
|
|
||||||
## Lint enforcement (in-repo plugins)
|
## Lint enforcement (in-repo plugins)
|
||||||
|
|
||||||
`scripts/run-additional-boundary-checks.mjs` runs a set of `lint:plugins:*`
|
`scripts/run-additional-boundary-checks.mts` runs a set of `lint:plugins:*`
|
||||||
import-boundary checks in CI; each can also be run standalone locally:
|
import-boundary checks in CI; each can also be run standalone locally:
|
||||||
|
|
||||||
| Command | Enforces |
|
| Command | Enforces |
|
||||||
|
|||||||
@@ -1131,8 +1131,8 @@ compatibility fallback when the shared
|
|||||||
never prints token material:
|
never prints token material:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_GPT_LIVE=1 node scripts/test-live.mjs -- extensions/openai/realtime-quicksilver.live.test.ts
|
OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_GPT_LIVE=1 node --import tsx scripts/test-live.mts -- extensions/openai/realtime-quicksilver.live.test.ts
|
||||||
OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_GPT_LIVE=1 node scripts/test-live.mjs -- extensions/openai/realtime-quicksilver-gateway-bridge.live.test.ts
|
OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_GPT_LIVE=1 node --import tsx scripts/test-live.mts -- extensions/openai/realtime-quicksilver-gateway-bridge.live.test.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
|
|||||||
@@ -490,7 +490,7 @@ Use this box to answer "does the release behave correctly in QA scenarios and li
|
|||||||
|
|
||||||
### Package
|
### Package
|
||||||
|
|
||||||
The Package box is the installable-product gate. It is backed by `Package Acceptance` and the resolver `scripts/resolve-openclaw-package-candidate.mjs`. The resolver normalizes a candidate into the `package-under-test` tarball consumed by Docker E2E, validates the package inventory, records the package version and SHA-256, and keeps the workflow harness ref separate from the package source ref.
|
The Package box is the installable-product gate. It is backed by `Package Acceptance` and the resolver `scripts/resolve-openclaw-package-candidate.mts`. The resolver normalizes a candidate into the `package-under-test` tarball consumed by Docker E2E, validates the package inventory, records the package version and SHA-256, and keeps the workflow harness ref separate from the package source ref.
|
||||||
|
|
||||||
Supported candidate sources:
|
Supported candidate sources:
|
||||||
|
|
||||||
@@ -719,7 +719,7 @@ If a maintainer must fall back to local npm authentication, run any 1Password CL
|
|||||||
- [`.github/workflows/openclaw-release-checks.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/openclaw-release-checks.yml)
|
- [`.github/workflows/openclaw-release-checks.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/openclaw-release-checks.yml)
|
||||||
- [`.github/workflows/openclaw-cross-os-release-checks-reusable.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/openclaw-cross-os-release-checks-reusable.yml)
|
- [`.github/workflows/openclaw-cross-os-release-checks-reusable.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/openclaw-cross-os-release-checks-reusable.yml)
|
||||||
- [`.github/workflows/docker-release.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/docker-release.yml)
|
- [`.github/workflows/docker-release.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/docker-release.yml)
|
||||||
- [`scripts/resolve-openclaw-package-candidate.mjs`](https://github.com/openclaw/openclaw/blob/main/scripts/resolve-openclaw-package-candidate.mjs)
|
- [`scripts/resolve-openclaw-package-candidate.mts`](https://github.com/openclaw/openclaw/blob/main/scripts/resolve-openclaw-package-candidate.mts)
|
||||||
- [`scripts/openclaw-npm-release-check.ts`](https://github.com/openclaw/openclaw/blob/main/scripts/openclaw-npm-release-check.ts)
|
- [`scripts/openclaw-npm-release-check.ts`](https://github.com/openclaw/openclaw/blob/main/scripts/openclaw-npm-release-check.ts)
|
||||||
- [`scripts/package-mac-dist.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/package-mac-dist.sh)
|
- [`scripts/package-mac-dist.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/package-mac-dist.sh)
|
||||||
- [`scripts/make_appcast.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/make_appcast.sh)
|
- [`scripts/make_appcast.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/make_appcast.sh)
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ Test wrapper runs end with a short `[test] passed|failed|skipped ... in ...` sum
|
|||||||
- `src/test-utils/openclaw-test-state.ts`: use from Vitest when a test needs an isolated `HOME`, `OPENCLAW_STATE_DIR`, `OPENCLAW_CONFIG_PATH`, config fixture, workspace, agent dir, or auth-profile store.
|
- `src/test-utils/openclaw-test-state.ts`: use from Vitest when a test needs an isolated `HOME`, `OPENCLAW_STATE_DIR`, `OPENCLAW_CONFIG_PATH`, config fixture, workspace, agent dir, or auth-profile store.
|
||||||
- `pnpm test:env-mutations:report`: non-blocking report of tests/harnesses that mutate `HOME`, `OPENCLAW_STATE_DIR`, `OPENCLAW_CONFIG_PATH`, `OPENCLAW_WORKSPACE_DIR`, or related env keys directly. Use it to find migration candidates for the shared test-state helper.
|
- `pnpm test:env-mutations:report`: non-blocking report of tests/harnesses that mutate `HOME`, `OPENCLAW_STATE_DIR`, `OPENCLAW_CONFIG_PATH`, `OPENCLAW_WORKSPACE_DIR`, or related env keys directly. Use it to find migration candidates for the shared test-state helper.
|
||||||
- `test/helpers/openclaw-test-instance.ts`: process-level E2E tests needing a running Gateway, CLI env, log capture, and cleanup in one place.
|
- `test/helpers/openclaw-test-instance.ts`: process-level E2E tests needing a running Gateway, CLI env, log capture, and cleanup in one place.
|
||||||
- Docker/Bash E2E lanes that source `scripts/lib/docker-e2e-image.sh` can pass `docker_e2e_test_state_shell_b64 <label> <scenario>` into the container and decode it with `scripts/lib/openclaw-e2e-instance.sh`; multi-home scripts can pass `docker_e2e_test_state_function_b64` and call `openclaw_test_state_create <label> <scenario>` in each flow. `node scripts/lib/openclaw-test-state.mjs -- create --label <name> --scenario <name> --env-file <path> --json` writes a sourceable host env file (the `--` before `create` keeps newer Node runtimes from treating `--env-file` as a Node flag). Lanes that launch a Gateway can source `scripts/lib/openclaw-e2e-instance.sh` for entrypoint resolution, mock OpenAI startup, foreground/background launch, readiness probes, state env export, log dumps, and process cleanup.
|
- Docker/Bash E2E lanes that source `scripts/lib/docker-e2e-image.sh` can pass `docker_e2e_test_state_shell_b64 <label> <scenario>` into the container and decode it with `scripts/lib/openclaw-e2e-instance.sh`; multi-home scripts can pass `docker_e2e_test_state_function_b64` and call `openclaw_test_state_create <label> <scenario>` in each flow. `node --import tsx scripts/lib/openclaw-test-state.mts -- create --label <name> --scenario <name> --env-file <path> --json` writes a sourceable host env file (the `--` before `create` keeps newer Node runtimes from treating `--env-file` as a Node flag). Lanes that launch a Gateway can source `scripts/lib/openclaw-e2e-instance.sh` for entrypoint resolution, mock OpenAI startup, foreground/background launch, readiness probes, state env export, log dumps, and process cleanup.
|
||||||
|
|
||||||
## Control UI, TUI, and extension lanes
|
## Control UI, TUI, and extension lanes
|
||||||
|
|
||||||
@@ -118,11 +118,11 @@ Test wrapper runs end with a short `[test] passed|failed|skipped ... in ...` sum
|
|||||||
|
|
||||||
## Full Docker suite (`pnpm test:docker:all`)
|
## Full Docker suite (`pnpm test:docker:all`)
|
||||||
|
|
||||||
Builds the shared live-test image, packs OpenClaw once as an npm tarball, builds/reuses a bare Node/Git runner image plus a functional image that installs that tarball into `/app`, then runs Docker smoke lanes through a weighted scheduler. `scripts/package-openclaw-for-docker.mjs` is the single local/CI package packer and validates the tarball plus `dist/postinstall-inventory.json` before Docker consumes it.
|
Builds the shared live-test image, packs OpenClaw once as an npm tarball, builds/reuses a bare Node/Git runner image plus a functional image that installs that tarball into `/app`, then runs Docker smoke lanes through a weighted scheduler. `scripts/package-openclaw-for-docker.mjs` is the stable local/CI package packer entrypoint and validates the tarball plus `dist/postinstall-inventory.json` before Docker consumes it.
|
||||||
|
|
||||||
- Bare image (`OPENCLAW_DOCKER_E2E_BARE_IMAGE`): installer/update/plugin-dependency lanes; mounts the prebuilt tarball instead of copied repo sources.
|
- Bare image (`OPENCLAW_DOCKER_E2E_BARE_IMAGE`): installer/update/plugin-dependency lanes; mounts the prebuilt tarball instead of copied repo sources.
|
||||||
- Functional image (`OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE`): normal built-app functionality lanes.
|
- Functional image (`OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE`): normal built-app functionality lanes.
|
||||||
- Lane definitions: `scripts/lib/docker-e2e-scenarios.mjs`. Planner: `scripts/lib/docker-e2e-plan.mjs`. Executor: `scripts/test-docker-all.mjs`.
|
- Lane definitions: `scripts/lib/docker-e2e-scenarios.mts`. Planner: `scripts/lib/docker-e2e-plan.mts`. Executor: `scripts/test-docker-all.mjs`.
|
||||||
- `node scripts/test-docker-all.mjs --plan-json` emits the scheduler-owned CI plan (lanes, image kinds, package/live-image needs, state scenarios, credential checks) without building or running Docker.
|
- `node scripts/test-docker-all.mjs --plan-json` emits the scheduler-owned CI plan (lanes, image kinds, package/live-image needs, state scenarios, credential checks) without building or running Docker.
|
||||||
|
|
||||||
Scheduling knobs (env vars, defaults in parentheses):
|
Scheduling knobs (env vars, defaults in parentheses):
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"pluginApi": ">=2026.8.1"
|
"pluginApi": ">=2026.8.1"
|
||||||
},
|
},
|
||||||
"assetScripts": {
|
"assetScripts": {
|
||||||
"build": "node ../../scripts/build-diffs-viewer-runtime.mjs full"
|
"build": "node --import tsx ../../scripts/build-diffs-viewer-runtime.mts full"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"openclawVersion": "2026.8.1",
|
"openclawVersion": "2026.8.1",
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"pluginApi": ">=2026.8.1"
|
"pluginApi": ">=2026.8.1"
|
||||||
},
|
},
|
||||||
"assetScripts": {
|
"assetScripts": {
|
||||||
"build": "node ../../scripts/build-diffs-viewer-runtime.mjs curated"
|
"build": "node --import tsx ../../scripts/build-diffs-viewer-runtime.mts curated"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"openclawVersion": "2026.8.1",
|
"openclawVersion": "2026.8.1",
|
||||||
|
|||||||
@@ -36,9 +36,13 @@ export async function ensureCuratedViewerRuntimeForTests(): Promise<void> {
|
|||||||
|
|
||||||
// The curated runtime is generated output. Source tests that serve viewer
|
// The curated runtime is generated output. Source tests that serve viewer
|
||||||
// assets need a clean-checkout fixture before the normal build hook runs.
|
// assets need a clean-checkout fixture before the normal build hook runs.
|
||||||
await execFileAsync(process.execPath, ["scripts/build-diffs-viewer-runtime.mjs", "curated"], {
|
await execFileAsync(
|
||||||
cwd: repoRoot,
|
process.execPath,
|
||||||
});
|
["--import", "tsx", "scripts/build-diffs-viewer-runtime.mts", "curated"],
|
||||||
|
{
|
||||||
|
cwd: repoRoot,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createTempDiffRoot(prefix: string): Promise<{
|
export async function createTempDiffRoot(prefix: string): Promise<{
|
||||||
|
|||||||
@@ -108,7 +108,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"assetScripts": {
|
"assetScripts": {
|
||||||
"build": "node ../../scripts/build-discord-activity-sdk.mjs"
|
"build": "node --import tsx ../../scripts/build-discord-activity-sdk.mts"
|
||||||
},
|
},
|
||||||
"release": {
|
"release": {
|
||||||
"publishToClawHub": true,
|
"publishToClawHub": true,
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ describe("qa test file scenario runner", () => {
|
|||||||
|
|
||||||
expect(result.executionKind).toBe("playwright");
|
expect(result.executionKind).toBe("playwright");
|
||||||
expect(commands.map((command) => command.args)).toEqual([
|
expect(commands.map((command) => command.args)).toEqual([
|
||||||
["scripts/ensure-playwright-chromium.mjs"],
|
["--import", "tsx", "scripts/ensure-playwright-chromium.mts"],
|
||||||
[
|
[
|
||||||
"scripts/run-vitest.mjs",
|
"scripts/run-vitest.mjs",
|
||||||
"run",
|
"run",
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ function playwrightSteps(
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
command: process.execPath,
|
command: process.execPath,
|
||||||
args: ["scripts/ensure-playwright-chromium.mjs"],
|
args: ["--import", "tsx", "scripts/ensure-playwright-chromium.mts"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: process.execPath,
|
command: process.execPath,
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ describe("qa web runtime", () => {
|
|||||||
const launchOptions = requireLaunchOptions();
|
const launchOptions = requireLaunchOptions();
|
||||||
expect(spawnSync).toHaveBeenCalledWith(
|
expect(spawnSync).toHaveBeenCalledWith(
|
||||||
process.execPath,
|
process.execPath,
|
||||||
["scripts/ensure-playwright-chromium.mjs", "--skip-ffmpeg"],
|
["--import", "tsx", "scripts/ensure-playwright-chromium.mts", "--skip-ffmpeg"],
|
||||||
expect.objectContaining({ cwd: process.cwd(), stdio: "inherit" }),
|
expect.objectContaining({ cwd: process.cwd(), stdio: "inherit" }),
|
||||||
);
|
);
|
||||||
expect(launchOptions?.channel).toBeUndefined();
|
expect(launchOptions?.channel).toBeUndefined();
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ function resolveRunnableChromiumExecutablePath(): string | undefined {
|
|||||||
function ensureChromiumAvailable(repoRoot: string) {
|
function ensureChromiumAvailable(repoRoot: string) {
|
||||||
const result = spawnSync(
|
const result = spawnSync(
|
||||||
process.execPath,
|
process.execPath,
|
||||||
["scripts/ensure-playwright-chromium.mjs", "--skip-ffmpeg"],
|
["--import", "tsx", "scripts/ensure-playwright-chromium.mts", "--skip-ffmpeg"],
|
||||||
{
|
{
|
||||||
cwd: repoRoot,
|
cwd: repoRoot,
|
||||||
env: process.env,
|
env: process.env,
|
||||||
|
|||||||
+169
-168
@@ -362,13 +362,14 @@
|
|||||||
"!docs/**/*.jpg",
|
"!docs/**/*.jpg",
|
||||||
"!docs/**/*.png",
|
"!docs/**/*.png",
|
||||||
"src/agents/templates/",
|
"src/agents/templates/",
|
||||||
"scripts/crabbox-routing-policy.mjs",
|
"scripts/crabbox-routing-policy.mts",
|
||||||
"scripts/crabbox-wrapper-providers.mjs",
|
"scripts/crabbox-wrapper-providers.mts",
|
||||||
"scripts/crabbox-wrapper.mjs",
|
"scripts/crabbox-wrapper.mjs",
|
||||||
"scripts/testbox-lease-freshness.mjs",
|
"scripts/crabbox-wrapper.mts",
|
||||||
|
"scripts/testbox-lease-freshness.mts",
|
||||||
|
"scripts/lib/tsx-cli-shim.mjs",
|
||||||
"patches/",
|
"patches/",
|
||||||
"skills/",
|
"skills/",
|
||||||
"scripts/npm-runner.mjs",
|
|
||||||
"scripts/prepare-git-hooks.mjs",
|
"scripts/prepare-git-hooks.mjs",
|
||||||
"scripts/preinstall-package-manager-warning.mjs",
|
"scripts/preinstall-package-manager-warning.mjs",
|
||||||
"scripts/lib/official-external-channel-catalog.json",
|
"scripts/lib/official-external-channel-catalog.json",
|
||||||
@@ -1477,16 +1478,16 @@
|
|||||||
"./cli-entry": "./openclaw.mjs"
|
"./cli-entry": "./openclaw.mjs"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android:assemble": "node scripts/run-android-gradle.mjs :app:assemblePlayDebug :wear:assembleDebug :wear-shared:assembleDebug",
|
"android:assemble": "node --import tsx scripts/run-android-gradle.mts :app:assemblePlayDebug :wear:assembleDebug :wear-shared:assembleDebug",
|
||||||
"android:assemble:third-party": "node scripts/run-android-gradle.mjs :app:assembleThirdPartyDebug",
|
"android:assemble:third-party": "node --import tsx scripts/run-android-gradle.mts :app:assembleThirdPartyDebug",
|
||||||
"android:bundle:release": "bash -lc 'source ./scripts/lib/android-fastlane.sh && cd apps/android && run_android_fastlane android play_store_archive'",
|
"android:bundle:release": "bash -lc 'source ./scripts/lib/android-fastlane.sh && cd apps/android && run_android_fastlane android play_store_archive'",
|
||||||
"android:format": "cd apps/android && ./gradlew :app:ktlintFormat :benchmark:ktlintFormat :wear:ktlintFormat :wear-shared:ktlintFormat",
|
"android:format": "cd apps/android && ./gradlew :app:ktlintFormat :benchmark:ktlintFormat :wear:ktlintFormat :wear-shared:ktlintFormat",
|
||||||
"android:install": "node scripts/run-android-gradle.mjs :app:installPlayDebug",
|
"android:install": "node --import tsx scripts/run-android-gradle.mts :app:installPlayDebug",
|
||||||
"android:install:third-party": "node scripts/run-android-gradle.mjs :app:installThirdPartyDebug",
|
"android:install:third-party": "node --import tsx scripts/run-android-gradle.mts :app:installThirdPartyDebug",
|
||||||
"android:lint": "cd apps/android && ./gradlew :app:ktlintCheck :benchmark:ktlintCheck :wear:ktlintCheck :wear-shared:ktlintCheck",
|
"android:lint": "cd apps/android && ./gradlew :app:ktlintCheck :benchmark:ktlintCheck :wear:ktlintCheck :wear-shared:ktlintCheck",
|
||||||
"android:lint:android": "node scripts/run-android-gradle.mjs :app:lintPlayDebug :app:lintThirdPartyDebug :wear:lintDebug :wear-shared:lintDebug",
|
"android:lint:android": "node --import tsx scripts/run-android-gradle.mts :app:lintPlayDebug :app:lintThirdPartyDebug :wear:lintDebug :wear-shared:lintDebug",
|
||||||
"android:run": "node scripts/run-android-gradle.mjs :app:installPlayDebug -- adb shell am start -n ai.openclaw.app/.MainActivity",
|
"android:run": "node --import tsx scripts/run-android-gradle.mts :app:installPlayDebug -- adb shell am start -n ai.openclaw.app/.MainActivity",
|
||||||
"android:run:third-party": "node scripts/run-android-gradle.mjs :app:installThirdPartyDebug -- adb shell am start -n ai.openclaw.app/.MainActivity",
|
"android:run:third-party": "node --import tsx scripts/run-android-gradle.mts :app:installThirdPartyDebug -- adb shell am start -n ai.openclaw.app/.MainActivity",
|
||||||
"android:release": "bash scripts/android-release.sh",
|
"android:release": "bash scripts/android-release.sh",
|
||||||
"android:release:archive": "bash -lc 'source ./scripts/lib/android-fastlane.sh && cd apps/android && run_android_fastlane android play_store_archive'",
|
"android:release:archive": "bash -lc 'source ./scripts/lib/android-fastlane.sh && cd apps/android && run_android_fastlane android play_store_archive'",
|
||||||
"android:release:auth:check": "bash -lc 'source ./scripts/lib/android-fastlane.sh && cd apps/android && run_android_fastlane android auth_check'",
|
"android:release:auth:check": "bash -lc 'source ./scripts/lib/android-fastlane.sh && cd apps/android && run_android_fastlane android auth_check'",
|
||||||
@@ -1498,55 +1499,54 @@
|
|||||||
"android:release:signing:sync:push": "bash -lc 'source ./scripts/lib/android-fastlane.sh && cd apps/android && run_android_fastlane android signing_sync_push'",
|
"android:release:signing:sync:push": "bash -lc 'source ./scripts/lib/android-fastlane.sh && cd apps/android && run_android_fastlane android signing_sync_push'",
|
||||||
"android:release:upload": "bash scripts/android-release-upload.sh",
|
"android:release:upload": "bash scripts/android-release-upload.sh",
|
||||||
"android:screenshots": "bash scripts/android-screenshots.sh",
|
"android:screenshots": "bash scripts/android-screenshots.sh",
|
||||||
"android:test": "node scripts/run-android-gradle.mjs :app:testPlayDebugUnitTest :wear:testDebugUnitTest :wear-shared:testDebugUnitTest",
|
"android:test": "node --import tsx scripts/run-android-gradle.mts :app:testPlayDebugUnitTest :wear:testDebugUnitTest :wear-shared:testDebugUnitTest",
|
||||||
"android:test:integration": "node scripts/run-with-env.mjs OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_ANDROID_NODE=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.live.config.ts src/gateway/android-node.capabilities.live.test.ts",
|
"android:test:integration": "node --import tsx scripts/run-with-env.mts OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_ANDROID_NODE=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.live.config.ts src/gateway/android-node.capabilities.live.test.ts",
|
||||||
"android:test:third-party": "node scripts/run-android-gradle.mjs :app:testThirdPartyDebugUnitTest",
|
"android:test:third-party": "node --import tsx scripts/run-android-gradle.mts :app:testThirdPartyDebugUnitTest",
|
||||||
"android:version": "node --import tsx scripts/android-version.ts --json",
|
"android:version": "node --import tsx scripts/android-version.ts --json",
|
||||||
"android:version:check": "node --import tsx scripts/android-sync-versioning.ts --check",
|
"android:version:check": "node --import tsx scripts/android-sync-versioning.ts --check",
|
||||||
"android:version:pin": "node --import tsx scripts/android-pin-version.ts",
|
"android:version:pin": "node --import tsx scripts/android-pin-version.ts",
|
||||||
"android:version:sync": "node --import tsx scripts/android-sync-versioning.ts --write",
|
"android:version:sync": "node --import tsx scripts/android-sync-versioning.ts --write",
|
||||||
"audit:seams": "node scripts/audit-seams.mjs",
|
"audit:seams": "node --import tsx scripts/audit-seams.mts",
|
||||||
"build": "node scripts/build-all.mjs",
|
"build": "node --import tsx scripts/build-all.mts",
|
||||||
"build:ci-artifacts": "node scripts/build-all.mjs ciArtifacts",
|
"build:ci-artifacts": "node --import tsx scripts/build-all.mts ciArtifacts",
|
||||||
"build:docker": "node scripts/tsdown-build.mjs && node scripts/check-cli-bootstrap-imports.mjs && node scripts/runtime-postbuild.mjs && node scripts/build-stamp.mjs && node scripts/runtime-postbuild-stamp.mjs && pnpm plugins:assets:build && pnpm plugins:assets:copy && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/write-build-info.ts && node --import tsx scripts/write-cli-startup-metadata.ts",
|
"build:docker": "node --import tsx scripts/tsdown-build.mts && node --import tsx scripts/check-cli-bootstrap-imports.mts && node scripts/runtime-postbuild.mjs && node --import tsx scripts/build-stamp.mts && node --import tsx scripts/runtime-postbuild-stamp.mts && pnpm plugins:assets:build && pnpm plugins:assets:copy && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/write-build-info.ts && node --import tsx scripts/write-cli-startup-metadata.ts",
|
||||||
"build:plugin-sdk:dts": "node scripts/run-tsgo.mjs -p tsconfig.plugin-sdk.dts.json --declaration true",
|
"build:plugin-sdk:dts": "node scripts/run-tsgo.mjs -p tsconfig.plugin-sdk.dts.json --declaration true",
|
||||||
"build:plugin-sdk:strict-smoke": "node scripts/tsdown-build.mjs && node scripts/runtime-postbuild.mjs && node scripts/run-with-env.mjs OPENCLAW_PLUGIN_SDK_CANONICAL_DTS=1 -- node --import tsx scripts/write-plugin-sdk-entry-dts.ts && node scripts/check-plugin-sdk-exports.mjs",
|
"build:plugin-sdk:strict-smoke": "node --import tsx scripts/tsdown-build.mts && node scripts/runtime-postbuild.mjs && node --import tsx scripts/run-with-env.mts OPENCLAW_PLUGIN_SDK_CANONICAL_DTS=1 -- node --import tsx scripts/write-plugin-sdk-entry-dts.ts && node --import tsx scripts/check-plugin-sdk-exports.mts",
|
||||||
"build:strict-smoke": "pnpm plugins:assets:build && node scripts/tsdown-build.mjs && node scripts/check-cli-bootstrap-imports.mjs && node scripts/runtime-postbuild.mjs && node scripts/build-stamp.mjs && node scripts/runtime-postbuild-stamp.mjs && node scripts/run-with-env.mjs OPENCLAW_PLUGIN_SDK_CANONICAL_DTS=1 -- node --import tsx scripts/write-plugin-sdk-entry-dts.ts && node scripts/check-plugin-sdk-exports.mjs",
|
"build:strict-smoke": "pnpm plugins:assets:build && node --import tsx scripts/tsdown-build.mts && node --import tsx scripts/check-cli-bootstrap-imports.mts && node scripts/runtime-postbuild.mjs && node --import tsx scripts/build-stamp.mts && node --import tsx scripts/runtime-postbuild-stamp.mts && node --import tsx scripts/run-with-env.mts OPENCLAW_PLUGIN_SDK_CANONICAL_DTS=1 -- node --import tsx scripts/write-plugin-sdk-entry-dts.ts && node --import tsx scripts/check-plugin-sdk-exports.mts",
|
||||||
"canvas:a2ui:bundle": "node scripts/bundle-a2ui.mjs",
|
"canvas:a2ui:bundle": "node --import tsx scripts/bundle-a2ui.mts",
|
||||||
"canvas:a2ui:native:check": "node scripts/sync-native-a2ui.mjs --check",
|
"canvas:a2ui:native:check": "node --import tsx scripts/sync-native-a2ui.mts --check",
|
||||||
"channels:catalog:check": "node scripts/write-official-channel-catalog.mjs --check",
|
"channels:catalog:check": "node scripts/write-official-channel-catalog.mjs --check",
|
||||||
"channels:catalog:gen": "node scripts/write-official-channel-catalog.mjs --write",
|
"channels:catalog:gen": "node scripts/write-official-channel-catalog.mjs --write",
|
||||||
"changed:lanes": "node scripts/changed-lanes.mjs",
|
"changed:lanes": "node scripts/changed-lanes.mjs",
|
||||||
"check": "node scripts/check.mjs",
|
"check": "node --import tsx scripts/check.mts",
|
||||||
"check:architecture": "pnpm check:import-cycles && pnpm check:madge-import-cycles && pnpm check:deprecated-api-usage && pnpm check:deprecated-jsdoc && pnpm db:kysely:check && pnpm lint:kysely && pnpm check:database-first-legacy-stores",
|
"check:architecture": "pnpm check:import-cycles && pnpm check:madge-import-cycles && pnpm check:deprecated-api-usage && pnpm check:deprecated-jsdoc && pnpm db:kysely:check && pnpm lint:kysely && pnpm check:database-first-legacy-stores",
|
||||||
"check:base-config-schema": "node --import tsx scripts/generate-base-config-schema.ts --check",
|
"check:base-config-schema": "node --import tsx scripts/generate-base-config-schema.ts --check",
|
||||||
"check:bundled-channel-config-metadata": "node --import tsx scripts/generate-bundled-channel-config-metadata.ts --check",
|
"check:bundled-channel-config-metadata": "node --import tsx scripts/generate-bundled-channel-config-metadata.ts --check",
|
||||||
"check:changed": "node scripts/check-changed.mjs",
|
"check:changed": "node scripts/check-changed.mjs",
|
||||||
"check:changelog-attributions": "node scripts/check-changelog-attributions.mjs",
|
"check:changelog-attributions": "node --import tsx scripts/check-changelog-attributions.mts",
|
||||||
"check:database-first-legacy-stores": "node scripts/check-database-first-legacy-stores.mjs",
|
"check:database-first-legacy-stores": "node --import tsx scripts/check-database-first-legacy-stores.mts",
|
||||||
"check:deprecated-api-usage": "node scripts/check-deprecated-api-usage.mjs",
|
"check:deprecated-api-usage": "node --import tsx scripts/check-deprecated-api-usage.mts",
|
||||||
"check:deprecated-jsdoc": "node scripts/check-deprecated-jsdoc.mjs",
|
"check:deprecated-jsdoc": "node --import tsx scripts/check-deprecated-jsdoc.mts",
|
||||||
"check:doctor-deprecation-registry": "node --import tsx scripts/check-doctor-deprecation-registry.ts",
|
"check:doctor-deprecation-registry": "node --import tsx scripts/check-doctor-deprecation-registry.ts",
|
||||||
"check:docs": "pnpm format:docs:check && pnpm lint:docs && pnpm docs:check-mdx && pnpm docs:check-i18n-glossary && pnpm docs:check-links",
|
"check:docs": "pnpm format:docs:check && pnpm lint:docs && pnpm docs:check-mdx && pnpm docs:check-i18n-glossary && pnpm docs:check-links",
|
||||||
"check:env-var-count": "node scripts/check-env-var-count.mjs",
|
"check:env-var-count": "node --import tsx scripts/check-env-var-count.mts",
|
||||||
"check:host-env-policy:swift": "node scripts/generate-host-env-security-policy-swift.mjs --check",
|
"check:host-env-policy:swift": "node --import tsx scripts/generate-host-env-security-policy-swift.mts --check",
|
||||||
"check:import-cycles": "node --import tsx scripts/check-import-cycles.ts",
|
"check:import-cycles": "node --import tsx scripts/check-import-cycles.ts",
|
||||||
"check:max-lines-ratchet": "node scripts/check-max-lines-ratchet.mjs",
|
"check:max-lines-ratchet": "node --import tsx scripts/check-max-lines-ratchet.mts",
|
||||||
"check:madge-import-cycles": "node --import tsx scripts/check-madge-import-cycles.ts",
|
"check:madge-import-cycles": "node --import tsx scripts/check-madge-import-cycles.ts",
|
||||||
"check:media-download-helpers": "node scripts/check-media-download-helper-roundtrip.mjs",
|
"check:media-download-helpers": "node --import tsx scripts/check-media-download-helper-roundtrip.mts",
|
||||||
"check:no-conflict-markers": "node scripts/check-no-conflict-markers.mjs",
|
"check:no-conflict-markers": "node scripts/check-no-conflict-markers.mjs",
|
||||||
"check:no-runtime-action-load-config": "node scripts/check-no-runtime-action-load-config.mjs",
|
"check:no-runtime-action-load-config": "node --import tsx scripts/check-no-runtime-action-load-config.mts",
|
||||||
"check:opengrep-rule-metadata": "node security/opengrep/check-rule-metadata.mjs",
|
"check:opengrep-rule-metadata": "node security/opengrep/check-rule-metadata.mjs",
|
||||||
"check:protocol-coverage": "node scripts/check-protocol-event-coverage.mjs",
|
"check:protocol-coverage": "node scripts/check-protocol-event-coverage.mjs",
|
||||||
"check:runtime-sidecar-loaders": "node --import tsx scripts/check-runtime-sidecar-loaders.mjs",
|
"check:runtime-sidecar-loaders": "node --import tsx scripts/check-runtime-sidecar-loaders.mts",
|
||||||
"check:script-declarations": "node scripts/check-script-declarations.mjs",
|
|
||||||
"check:static-import-sccs": "pnpm check:madge-import-cycles",
|
"check:static-import-sccs": "pnpm check:madge-import-cycles",
|
||||||
"check:temp-path-guardrails": "node --import tsx scripts/check-temp-path-guardrails.ts",
|
"check:temp-path-guardrails": "node --import tsx scripts/check-temp-path-guardrails.ts",
|
||||||
"check:test-types": "pnpm tsgo:test",
|
"check:test-types": "pnpm tsgo:test",
|
||||||
"check:timed": "node scripts/check-timed.mjs",
|
"check:timed": "node --import tsx scripts/check-timed.mts",
|
||||||
"check:timed:all-types": "node scripts/check-timed.mjs --include-test-types",
|
"check:timed:all-types": "node --import tsx scripts/check-timed.mts --include-test-types",
|
||||||
"check:timed:architecture": "node scripts/check-timed.mjs --include-architecture",
|
"check:timed:architecture": "node --import tsx scripts/check-timed.mts --include-architecture",
|
||||||
"check:workflows": "node scripts/check-workflows.mjs",
|
"check:workflows": "node --import tsx scripts/check-workflows.mts",
|
||||||
"ci:full-release": "node scripts/full-release-validation-at-sha.mjs",
|
"ci:full-release": "node scripts/full-release-validation-at-sha.mjs",
|
||||||
"ci:timings": "node scripts/ci-run-timings.mjs --latest-main",
|
"ci:timings": "node scripts/ci-run-timings.mjs --latest-main",
|
||||||
"ci:timings:recent": "node scripts/ci-run-timings.mjs --recent 10",
|
"ci:timings:recent": "node scripts/ci-run-timings.mjs --recent 10",
|
||||||
@@ -1566,27 +1566,27 @@
|
|||||||
"crabbox:stop": "node scripts/crabbox-wrapper.mjs stop",
|
"crabbox:stop": "node scripts/crabbox-wrapper.mjs stop",
|
||||||
"crabbox:warmup": "node scripts/crabbox-wrapper.mjs warmup",
|
"crabbox:warmup": "node scripts/crabbox-wrapper.mjs warmup",
|
||||||
"deadcode:dependencies": "pnpm deadcode:full",
|
"deadcode:dependencies": "pnpm deadcode:full",
|
||||||
"deadcode:exports": "node scripts/check-deadcode-exports.mjs",
|
"deadcode:exports": "node --import tsx scripts/check-deadcode-exports.mts",
|
||||||
"deadcode:full": "pnpm --config.minimum-release-age=0 dlx --package knip@6.8.0 knip --config config/knip.config.ts --production --no-progress --reporter compact --no-config-hints --exclude duplicates && pnpm --config.minimum-release-age=0 dlx --package knip@6.8.0 knip --config config/knip.all-exports.config.ts --no-progress --reporter compact --no-config-hints --exclude duplicates",
|
"deadcode:full": "pnpm --config.minimum-release-age=0 dlx --package knip@6.8.0 knip --config config/knip.config.ts --production --no-progress --reporter compact --no-config-hints --exclude duplicates && pnpm --config.minimum-release-age=0 dlx --package knip@6.8.0 knip --config config/knip.all-exports.config.ts --no-progress --reporter compact --no-config-hints --exclude duplicates",
|
||||||
"deadcode:knip": "pnpm --config.minimum-release-age=0 dlx --package knip@6.8.0 knip --config config/knip.config.ts --production --no-progress --reporter compact --files --dependencies",
|
"deadcode:knip": "pnpm --config.minimum-release-age=0 dlx --package knip@6.8.0 knip --config config/knip.config.ts --production --no-progress --reporter compact --files --dependencies",
|
||||||
"deadcode:report": "pnpm deadcode:full; pnpm deadcode:exports",
|
"deadcode:report": "pnpm deadcode:full; pnpm deadcode:exports",
|
||||||
"deadcode:unused-files": "node scripts/check-deadcode-unused-files.mjs",
|
"deadcode:unused-files": "node --import tsx scripts/check-deadcode-unused-files.mts",
|
||||||
"deps:root-ownership": "node scripts/root-dependency-ownership-audit.mjs",
|
"deps:root-ownership": "node --import tsx scripts/root-dependency-ownership-audit.mts",
|
||||||
"deps:root-ownership:check": "node scripts/root-dependency-ownership-audit.mjs --check",
|
"deps:root-ownership:check": "node --import tsx scripts/root-dependency-ownership-audit.mts --check",
|
||||||
"deps:changes:report": "node scripts/dependency-changes-report.mjs",
|
"deps:changes:report": "node --import tsx scripts/dependency-changes-report.mts",
|
||||||
"deps:patches:check": "node scripts/check-package-patches.mjs",
|
"deps:patches:check": "node --import tsx scripts/check-package-patches.mts",
|
||||||
"deps:pins:check": "node scripts/check-dependency-pins.mjs",
|
"deps:pins:check": "node --import tsx scripts/check-dependency-pins.mts",
|
||||||
"deps:npm-lock:check": "node scripts/generate-npm-package-lock.mjs --all",
|
"deps:npm-lock:check": "node scripts/generate-npm-package-lock.mjs --all",
|
||||||
"deps:npm-lock:check:changed": "node scripts/generate-npm-package-lock.mjs --changed",
|
"deps:npm-lock:check:changed": "node scripts/generate-npm-package-lock.mjs --changed",
|
||||||
"deps:ownership-surface:check": "node scripts/dependency-ownership-surface-report.mjs --check",
|
"deps:ownership-surface:check": "node --import tsx scripts/dependency-ownership-surface-report.mts --check",
|
||||||
"deps:ownership-surface:report": "node scripts/dependency-ownership-surface-report.mjs",
|
"deps:ownership-surface:report": "node --import tsx scripts/dependency-ownership-surface-report.mts",
|
||||||
"deps:transitive-risk:report": "node scripts/transitive-manifest-risk-report.mjs",
|
"deps:transitive-risk:report": "node --import tsx scripts/transitive-manifest-risk-report.mts",
|
||||||
"deps:vuln:gate": "node scripts/dependency-vulnerability-gate.mjs",
|
"deps:vuln:gate": "node --import tsx scripts/dependency-vulnerability-gate.mts",
|
||||||
"db:kysely:check": "node scripts/generate-kysely-types.mjs --verify",
|
"db:kysely:check": "node --import tsx scripts/generate-kysely-types.mts --verify",
|
||||||
"db:kysely:gen": "node scripts/generate-kysely-types.mjs",
|
"db:kysely:gen": "node --import tsx scripts/generate-kysely-types.mts",
|
||||||
"dev": "node scripts/run-node.mjs",
|
"dev": "node scripts/run-node.mjs",
|
||||||
"dev:ui:mock": "node --import tsx scripts/control-ui-mock-dev.ts",
|
"dev:ui:mock": "node --import tsx scripts/control-ui-mock-dev.ts",
|
||||||
"docs:check-i18n-glossary": "node scripts/check-docs-i18n-glossary.mjs",
|
"docs:check-i18n-glossary": "node --import tsx scripts/check-docs-i18n-glossary.mts",
|
||||||
"docs:check-links": "node scripts/docs-link-audit.mjs",
|
"docs:check-links": "node scripts/docs-link-audit.mjs",
|
||||||
"docs:check-links:anchors": "node scripts/docs-link-audit.mjs --anchors",
|
"docs:check-links:anchors": "node scripts/docs-link-audit.mjs --anchors",
|
||||||
"docs:check-mdx": "node scripts/check-docs-mdx.mjs docs README.md",
|
"docs:check-mdx": "node scripts/check-docs-mdx.mjs docs README.md",
|
||||||
@@ -1597,23 +1597,23 @@
|
|||||||
"docs:spellcheck:fix": "bash scripts/docs-spellcheck.sh --write",
|
"docs:spellcheck:fix": "bash scripts/docs-spellcheck.sh --write",
|
||||||
"maturity:check": "node --import tsx scripts/qa/render-maturity-docs.ts --check",
|
"maturity:check": "node --import tsx scripts/qa/render-maturity-docs.ts --check",
|
||||||
"maturity:render": "node --import tsx scripts/qa/render-maturity-docs.ts",
|
"maturity:render": "node --import tsx scripts/qa/render-maturity-docs.ts",
|
||||||
"dup:check": "node scripts/check-duplicates.mjs",
|
"dup:check": "node --import tsx scripts/check-duplicates.mts",
|
||||||
"dup:check:coverage": "node scripts/check-duplicates.mjs --coverage",
|
"dup:check:coverage": "node --import tsx scripts/check-duplicates.mts --coverage",
|
||||||
"dup:check:json": "node scripts/check-duplicates.mjs --json",
|
"dup:check:json": "node --import tsx scripts/check-duplicates.mts --json",
|
||||||
"format": "oxfmt --write --threads=1",
|
"format": "oxfmt --write --threads=1",
|
||||||
"format:all": "pnpm format && pnpm format:swift",
|
"format:all": "pnpm format && pnpm format:swift",
|
||||||
"format:check": "oxfmt --check",
|
"format:check": "oxfmt --check",
|
||||||
"format:diff": "oxfmt --write --threads=1 && git --no-pager diff",
|
"format:diff": "oxfmt --write --threads=1 && git --no-pager diff",
|
||||||
"format:docs": "node scripts/format-docs.mjs",
|
"format:docs": "node --import tsx scripts/format-docs.mts",
|
||||||
"format:docs:check": "node scripts/format-docs.mjs --check",
|
"format:docs:check": "node --import tsx scripts/format-docs.mts --check",
|
||||||
"format:fix": "oxfmt --write --threads=1",
|
"format:fix": "oxfmt --write --threads=1",
|
||||||
"format:swift": "./scripts/format-swift.sh",
|
"format:swift": "./scripts/format-swift.sh",
|
||||||
"gateway:dev": "node scripts/run-with-env.mjs OPENCLAW_SKIP_CHANNELS=1 -- node scripts/run-node.mjs --dev gateway",
|
"gateway:dev": "node --import tsx scripts/run-with-env.mts OPENCLAW_SKIP_CHANNELS=1 -- node scripts/run-node.mjs --dev gateway",
|
||||||
"gateway:dev:reset": "node scripts/run-with-env.mjs OPENCLAW_SKIP_CHANNELS=1 -- node scripts/run-node.mjs --dev gateway --reset",
|
"gateway:dev:reset": "node --import tsx scripts/run-with-env.mts OPENCLAW_SKIP_CHANNELS=1 -- node scripts/run-node.mjs --dev gateway --reset",
|
||||||
"gateway:watch": "node scripts/gateway-watch-tmux.mjs gateway --force",
|
"gateway:watch": "node --import tsx scripts/gateway-watch-tmux.mts gateway --force",
|
||||||
"gateway:watch:raw": "node scripts/watch-node.mjs gateway --force",
|
"gateway:watch:raw": "node scripts/watch-node.mjs gateway --force",
|
||||||
"gen:host-env-policy:swift": "node scripts/generate-host-env-security-policy-swift.mjs --write",
|
"gen:host-env-policy:swift": "node --import tsx scripts/generate-host-env-security-policy-swift.mts --write",
|
||||||
"ghsa:patch": "node scripts/ghsa-patch.mjs",
|
"ghsa:patch": "node --import tsx scripts/ghsa-patch.mts",
|
||||||
"ios:app-review-notes:pdf": "xcrun swift scripts/ios-app-review-notes-pdf.swift apps/ios/APP-REVIEW-NOTES.md apps/ios/build/app-review/APP-REVIEW-NOTES.pdf",
|
"ios:app-review-notes:pdf": "xcrun swift scripts/ios-app-review-notes-pdf.swift apps/ios/APP-REVIEW-NOTES.md apps/ios/build/app-review/APP-REVIEW-NOTES.pdf",
|
||||||
"ios:build": "bash -c 'export PATH=\"$PATH:/opt/homebrew/bin:/usr/local/bin\"; ./scripts/ios-configure-signing.sh && ./scripts/ios-write-version-xcconfig.sh && node scripts/ios-write-swift-filelist.mjs && cd apps/ios && xcodegen generate && xcodebuild -project OpenClaw.xcodeproj -scheme OpenClaw -destination \"${IOS_DEST:-generic/platform=iOS Simulator}\" -configuration Debug build'",
|
"ios:build": "bash -c 'export PATH=\"$PATH:/opt/homebrew/bin:/usr/local/bin\"; ./scripts/ios-configure-signing.sh && ./scripts/ios-write-version-xcconfig.sh && node scripts/ios-write-swift-filelist.mjs && cd apps/ios && xcodegen generate && xcodebuild -project OpenClaw.xcodeproj -scheme OpenClaw -destination \"${IOS_DEST:-generic/platform=iOS Simulator}\" -configuration Debug build'",
|
||||||
"ios:filelist:gen": "node scripts/ios-write-swift-filelist.mjs",
|
"ios:filelist:gen": "node scripts/ios-write-swift-filelist.mjs",
|
||||||
@@ -1635,51 +1635,51 @@
|
|||||||
"ios:version:check": "node --import tsx scripts/ios-sync-versioning.ts --check",
|
"ios:version:check": "node --import tsx scripts/ios-sync-versioning.ts --check",
|
||||||
"ios:version:sync": "node --import tsx scripts/ios-sync-versioning.ts --write",
|
"ios:version:sync": "node --import tsx scripts/ios-sync-versioning.ts --write",
|
||||||
"leak:embedded-run": "node --import tsx --expose-gc scripts/embedded-run-abort-leak.ts",
|
"leak:embedded-run": "node --import tsx --expose-gc scripts/embedded-run-abort-leak.ts",
|
||||||
"lint": "node scripts/run-lint.mjs",
|
"lint": "node --import tsx scripts/run-lint.mts",
|
||||||
"lint:agent:ingress-owner": "node scripts/check-ingress-agent-owner-context.mjs",
|
"lint:agent:ingress-owner": "node --import tsx scripts/check-ingress-agent-owner-context.mts",
|
||||||
"lint:all": "node scripts/run-oxlint.mjs",
|
"lint:all": "node scripts/run-oxlint.mjs",
|
||||||
"lint:apps": "pnpm lint:swift",
|
"lint:apps": "pnpm lint:swift",
|
||||||
"lint:auth:no-pairing-store-group": "node scripts/check-no-pairing-store-group-auth.mjs",
|
"lint:auth:no-pairing-store-group": "node --import tsx scripts/check-no-pairing-store-group-auth.mts",
|
||||||
"lint:auth:pairing-account-scope": "node scripts/check-pairing-account-scope.mjs",
|
"lint:auth:pairing-account-scope": "node --import tsx scripts/check-pairing-account-scope.mts",
|
||||||
"lint:core": "node scripts/run-oxlint-shards.mjs --only=core --split-core",
|
"lint:core": "node --import tsx scripts/run-oxlint-shards.mts --only=core --split-core",
|
||||||
"lint:docker-e2e": "node scripts/check-docker-e2e-boundaries.mjs",
|
"lint:docker-e2e": "node --import tsx scripts/check-docker-e2e-boundaries.mts",
|
||||||
"lint:kysely": "node scripts/check-kysely-guardrails.mjs",
|
"lint:kysely": "node --import tsx scripts/check-kysely-guardrails.mts",
|
||||||
"lint:docs": "pnpm dlx --config.resolution-mode=highest markdownlint-cli2 --config config/markdownlint-cli2.jsonc",
|
"lint:docs": "pnpm dlx --config.resolution-mode=highest markdownlint-cli2 --config config/markdownlint-cli2.jsonc",
|
||||||
"lint:docs:fix": "pnpm dlx --config.resolution-mode=highest markdownlint-cli2 --config config/markdownlint-cli2.jsonc --fix",
|
"lint:docs:fix": "pnpm dlx --config.resolution-mode=highest markdownlint-cli2 --config config/markdownlint-cli2.jsonc --fix",
|
||||||
"lint:extensions:no-deprecated-channel-access": "node --import tsx scripts/check-no-deprecated-channel-access.ts",
|
"lint:extensions:no-deprecated-channel-access": "node --import tsx scripts/check-no-deprecated-channel-access.ts",
|
||||||
"lint:extensions:telegram-grammy-types": "node scripts/check-telegram-grammy-types-imports.mjs",
|
"lint:extensions:telegram-grammy-types": "node --import tsx scripts/check-telegram-grammy-types-imports.mts",
|
||||||
"lint:extensions": "node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.extensions.json extensions",
|
"lint:extensions": "node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.extensions.json extensions",
|
||||||
"lint:extensions:no-guarded-wildcard-reexports": "node scripts/check-extension-wildcard-reexports.mjs",
|
"lint:extensions:no-guarded-wildcard-reexports": "node --import tsx scripts/check-extension-wildcard-reexports.mts",
|
||||||
"lint:extensions:no-plugin-sdk-internal": "node scripts/check-extension-plugin-sdk-boundary.mjs --mode=plugin-sdk-internal",
|
"lint:extensions:no-plugin-sdk-internal": "node --import tsx scripts/check-extension-plugin-sdk-boundary.mts --mode=plugin-sdk-internal",
|
||||||
"lint:extensions:no-plugin-sdk-wildcard-reexports": "node scripts/check-plugin-sdk-wildcard-reexports.mjs",
|
"lint:extensions:no-plugin-sdk-wildcard-reexports": "node --import tsx scripts/check-plugin-sdk-wildcard-reexports.mts",
|
||||||
"lint:extensions:no-relative-outside-package": "node scripts/check-extension-plugin-sdk-boundary.mjs --mode=relative-outside-package",
|
"lint:extensions:no-relative-outside-package": "node --import tsx scripts/check-extension-plugin-sdk-boundary.mts --mode=relative-outside-package",
|
||||||
"lint:extensions:no-src-outside-plugin-sdk": "node scripts/check-extension-plugin-sdk-boundary.mjs --mode=src-outside-plugin-sdk",
|
"lint:extensions:no-src-outside-plugin-sdk": "node --import tsx scripts/check-extension-plugin-sdk-boundary.mts --mode=src-outside-plugin-sdk",
|
||||||
"lint:fix": "node scripts/run-oxlint.mjs --fix && pnpm format",
|
"lint:fix": "node scripts/run-oxlint.mjs --fix && pnpm format",
|
||||||
"lint:plugins:no-extension-imports": "node scripts/check-plugin-extension-import-boundary.mjs",
|
"lint:plugins:no-extension-imports": "node --import tsx scripts/check-plugin-extension-import-boundary.mts",
|
||||||
"lint:plugins:no-extension-src-imports": "node --import tsx scripts/check-no-extension-src-imports.ts",
|
"lint:plugins:no-extension-src-imports": "node --import tsx scripts/check-no-extension-src-imports.ts",
|
||||||
"lint:plugins:no-extension-test-core-imports": "node --import tsx scripts/check-no-extension-test-core-imports.ts",
|
"lint:plugins:no-extension-test-core-imports": "node --import tsx scripts/check-no-extension-test-core-imports.ts",
|
||||||
"lint:plugins:no-monolithic-plugin-sdk-entry-imports": "node --import tsx scripts/check-no-monolithic-plugin-sdk-entry-imports.ts",
|
"lint:plugins:no-monolithic-plugin-sdk-entry-imports": "node --import tsx scripts/check-no-monolithic-plugin-sdk-entry-imports.ts",
|
||||||
"lint:plugins:no-register-http-handler": "node scripts/run-oxlint.mjs --openclaw-focused-config --config config/oxlint/boundary-guards.json src extensions",
|
"lint:plugins:no-register-http-handler": "node scripts/run-oxlint.mjs --openclaw-focused-config --config config/oxlint/boundary-guards.json src extensions",
|
||||||
"lint:plugins:plugin-sdk-subpaths-exported": "node scripts/check-plugin-sdk-subpath-exports.mjs",
|
"lint:plugins:plugin-sdk-subpaths-exported": "node --import tsx scripts/check-plugin-sdk-subpath-exports.mts",
|
||||||
"lint:scripts": "pnpm lint:docker-e2e && pnpm lint:tmp:no-raw-http2-imports && node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.scripts.json scripts",
|
"lint:scripts": "pnpm lint:docker-e2e && pnpm lint:tmp:no-raw-http2-imports && node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.scripts.json scripts",
|
||||||
"lint:swift": "./scripts/lint-swift.sh",
|
"lint:swift": "./scripts/lint-swift.sh",
|
||||||
"lint:tmp:channel-agnostic-boundaries": "node scripts/check-channel-agnostic-boundaries.mjs",
|
"lint:tmp:channel-agnostic-boundaries": "node --import tsx scripts/check-channel-agnostic-boundaries.mts",
|
||||||
"lint:tmp:dynamic-import-warts": "node scripts/check-dynamic-import-warts.mjs",
|
"lint:tmp:dynamic-import-warts": "node --import tsx scripts/check-dynamic-import-warts.mts",
|
||||||
"lint:tmp:no-random-messaging": "node scripts/check-no-random-messaging-tmp.mjs",
|
"lint:tmp:no-random-messaging": "node --import tsx scripts/check-no-random-messaging-tmp.mts",
|
||||||
"lint:tmp:no-raw-channel-fetch": "node scripts/check-no-raw-channel-fetch.mjs",
|
"lint:tmp:no-raw-channel-fetch": "node --import tsx scripts/check-no-raw-channel-fetch.mts",
|
||||||
"lint:tmp:no-raw-http2-imports": "node scripts/check-no-raw-http2-imports.mjs",
|
"lint:tmp:no-raw-http2-imports": "node --import tsx scripts/check-no-raw-http2-imports.mts",
|
||||||
"lint:tmp:session-accessor-boundary": "node scripts/check-session-accessor-boundary.mjs",
|
"lint:tmp:session-accessor-boundary": "node --import tsx scripts/check-session-accessor-boundary.mts",
|
||||||
"lint:tmp:session-accessor-boundary:gen": "node scripts/check-session-accessor-boundary.mjs --update-debt-baseline",
|
"lint:tmp:session-accessor-boundary:gen": "node --import tsx scripts/check-session-accessor-boundary.mts --update-debt-baseline",
|
||||||
"lint:tmp:session-transcript-reader-boundary": "node scripts/check-session-transcript-reader-boundary.mjs",
|
"lint:tmp:session-transcript-reader-boundary": "node --import tsx scripts/check-session-transcript-reader-boundary.mts",
|
||||||
"lint:tmp:sqlite-transaction-boundary": "node scripts/check-sqlite-transaction-boundary.mjs",
|
"lint:tmp:sqlite-transaction-boundary": "node --import tsx scripts/check-sqlite-transaction-boundary.mts",
|
||||||
"lint:tmp:tsgo-core-boundary": "node scripts/check-tsgo-core-boundary.mjs",
|
"lint:tmp:tsgo-core-boundary": "node --import tsx scripts/check-tsgo-core-boundary.mts",
|
||||||
"lint:ui:i18n": "pnpm ui:i18n:verify",
|
"lint:ui:i18n": "pnpm ui:i18n:verify",
|
||||||
"lint:ui:lit": "lit-analyzer \"ui/src/**/*.ts\" --quiet",
|
"lint:ui:lit": "lit-analyzer \"ui/src/**/*.ts\" --quiet",
|
||||||
"lint:ui:no-raw-window-open": "node scripts/run-oxlint.mjs --openclaw-focused-config --config config/oxlint/boundary-guards.json ui/src",
|
"lint:ui:no-raw-window-open": "node scripts/run-oxlint.mjs --openclaw-focused-config --config config/oxlint/boundary-guards.json ui/src",
|
||||||
"lint:ui:styles": "stylelint --config config/stylelint.config.mjs \"ui/src/**/*.css\" \"ui/src/**/*.ts\"",
|
"lint:ui:styles": "stylelint --config config/stylelint.config.mjs \"ui/src/**/*.css\" \"ui/src/**/*.ts\"",
|
||||||
"lint:web-fetch-provider-boundaries": "node scripts/check-web-fetch-provider-boundaries.mjs",
|
"lint:web-fetch-provider-boundaries": "node --import tsx scripts/check-web-fetch-provider-boundaries.mts",
|
||||||
"lint:web-search-provider-boundaries": "node scripts/check-web-search-provider-boundaries.mjs",
|
"lint:web-search-provider-boundaries": "node --import tsx scripts/check-web-search-provider-boundaries.mts",
|
||||||
"lint:webhook:no-low-level-body-read": "node scripts/check-webhook-auth-body-order.mjs",
|
"lint:webhook:no-low-level-body-read": "node --import tsx scripts/check-webhook-auth-body-order.mts",
|
||||||
"mac:open": "open dist/OpenClaw.app",
|
"mac:open": "open dist/OpenClaw.app",
|
||||||
"mac:package": "bash scripts/package-mac-app.sh",
|
"mac:package": "bash scripts/package-mac-app.sh",
|
||||||
"mac:restart": "bash scripts/restart-mac.sh",
|
"mac:restart": "bash scripts/restart-mac.sh",
|
||||||
@@ -1689,25 +1689,25 @@
|
|||||||
"openclaw": "node scripts/run-node.mjs",
|
"openclaw": "node scripts/run-node.mjs",
|
||||||
"openclaw:rpc": "node scripts/run-node.mjs agent --mode rpc --json",
|
"openclaw:rpc": "node scripts/run-node.mjs agent --mode rpc --json",
|
||||||
"perf:web-fetch": "node --import tsx scripts/bench-web-fetch.ts",
|
"perf:web-fetch": "node --import tsx scripts/bench-web-fetch.ts",
|
||||||
"perf:kova:summary": "node scripts/kova-ci-summary.mjs",
|
"perf:kova:summary": "node --import tsx scripts/kova-ci-summary.mts",
|
||||||
"perf:source:summary": "node scripts/openclaw-performance-source-summary.mjs",
|
"perf:source:summary": "node --import tsx scripts/openclaw-performance-source-summary.mts",
|
||||||
"plugin-sdk:api:check": "node --max-old-space-size=8192 --import tsx scripts/generate-plugin-sdk-api-baseline.ts --check",
|
"plugin-sdk:api:check": "node --max-old-space-size=8192 --import tsx scripts/generate-plugin-sdk-api-baseline.ts --check",
|
||||||
"plugin-sdk:api:gen": "node --max-old-space-size=8192 --import tsx scripts/generate-plugin-sdk-api-baseline.ts --write",
|
"plugin-sdk:api:gen": "node --max-old-space-size=8192 --import tsx scripts/generate-plugin-sdk-api-baseline.ts --write",
|
||||||
"plugin-sdk:check-exports": "node scripts/sync-plugin-sdk-exports.mjs --check",
|
"plugin-sdk:check-exports": "node --import tsx scripts/sync-plugin-sdk-exports.mts --check",
|
||||||
"plugin-sdk:surface": "node --max-old-space-size=8192 scripts/plugin-sdk-surface-report.mjs",
|
"plugin-sdk:surface": "node --max-old-space-size=8192 --import tsx scripts/plugin-sdk-surface-report.mts",
|
||||||
"plugin-sdk:surface:check": "node --max-old-space-size=8192 scripts/plugin-sdk-surface-report.mjs --check",
|
"plugin-sdk:surface:check": "node --max-old-space-size=8192 --import tsx scripts/plugin-sdk-surface-report.mts --check",
|
||||||
"plugin-sdk:sync-exports": "node scripts/sync-plugin-sdk-exports.mjs",
|
"plugin-sdk:sync-exports": "node --import tsx scripts/sync-plugin-sdk-exports.mts",
|
||||||
"plugin-sdk:usage": "node --max-old-space-size=8192 --import tsx scripts/analyze-plugin-sdk-usage.ts",
|
"plugin-sdk:usage": "node --max-old-space-size=8192 --import tsx scripts/analyze-plugin-sdk-usage.ts",
|
||||||
"policy:config-coverage": "node --import tsx scripts/check-policy-config-coverage.ts",
|
"policy:config-coverage": "node --import tsx scripts/check-policy-config-coverage.ts",
|
||||||
"plugins:boundary-report": "node --import tsx scripts/plugin-boundary-report.ts",
|
"plugins:boundary-report": "node --import tsx scripts/plugin-boundary-report.ts",
|
||||||
"plugins:boundary-report:ci": "node --import tsx scripts/plugin-boundary-report.ts --summary --fail-on-cross-owner --fail-on-unclassified-unused-reserved --fail-on-eligible-compat",
|
"plugins:boundary-report:ci": "node --import tsx scripts/plugin-boundary-report.ts --summary --fail-on-cross-owner --fail-on-unclassified-unused-reserved --fail-on-eligible-compat",
|
||||||
"plugins:boundary-report:json": "node --import tsx scripts/plugin-boundary-report.ts --json",
|
"plugins:boundary-report:json": "node --import tsx scripts/plugin-boundary-report.ts --json",
|
||||||
"plugins:boundary-report:summary": "node --import tsx scripts/plugin-boundary-report.ts --summary",
|
"plugins:boundary-report:summary": "node --import tsx scripts/plugin-boundary-report.ts --summary",
|
||||||
"plugins:assets:build": "node scripts/bundled-plugin-assets.mjs --phase build",
|
"plugins:assets:build": "node --import tsx scripts/bundled-plugin-assets.mts --phase build",
|
||||||
"plugins:assets:check": "node scripts/bundled-plugin-assets.mjs --phase build --check",
|
"plugins:assets:check": "node --import tsx scripts/bundled-plugin-assets.mts --phase build --check",
|
||||||
"plugins:assets:copy": "node scripts/bundled-plugin-assets.mjs --phase copy",
|
"plugins:assets:copy": "node --import tsx scripts/bundled-plugin-assets.mts --phase copy",
|
||||||
"plugins:inventory:check": "node scripts/generate-plugin-inventory-doc.mjs --check",
|
"plugins:inventory:check": "node --import tsx scripts/generate-plugin-inventory-doc.mts --check",
|
||||||
"plugins:inventory:gen": "node scripts/generate-plugin-inventory-doc.mjs --write",
|
"plugins:inventory:gen": "node --import tsx scripts/generate-plugin-inventory-doc.mts --write",
|
||||||
"plugins:sync": "node --import tsx scripts/sync-plugin-versions.ts",
|
"plugins:sync": "node --import tsx scripts/sync-plugin-versions.ts",
|
||||||
"plugins:sync:check": "node --import tsx scripts/sync-plugin-versions.ts --check",
|
"plugins:sync:check": "node --import tsx scripts/sync-plugin-versions.ts --check",
|
||||||
"postinstall": "node scripts/postinstall-bundled-plugins.mjs",
|
"postinstall": "node scripts/postinstall-bundled-plugins.mjs",
|
||||||
@@ -1720,16 +1720,16 @@
|
|||||||
"prompt:snapshots:check": "node --import tsx scripts/generate-prompt-snapshots.ts --check",
|
"prompt:snapshots:check": "node --import tsx scripts/generate-prompt-snapshots.ts --check",
|
||||||
"prompt:snapshots:gen": "node --import tsx scripts/generate-prompt-snapshots.ts --write",
|
"prompt:snapshots:gen": "node --import tsx scripts/generate-prompt-snapshots.ts --write",
|
||||||
"prompt:snapshots:sync-codex-model": "node --import tsx scripts/sync-codex-model-prompt-fixture.ts",
|
"prompt:snapshots:sync-codex-model": "node --import tsx scripts/sync-codex-model-prompt-fixture.ts",
|
||||||
"protocol:check": "pnpm protocol-registry:check && pnpm protocol:gen && pnpm protocol:check:swift && pnpm protocol:gen:kotlin && node scripts/check-protocol-since.mjs && git diff --exit-code -- dist/protocol.schema.json apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewayProtocol.kt apps/android/app/src/main/java/ai/openclaw/app/protocol/OpenClawProtocolConstants.kt",
|
"protocol:check": "pnpm protocol-registry:check && pnpm protocol:gen && pnpm protocol:check:swift && pnpm protocol:gen:kotlin && node --import tsx scripts/check-protocol-since.mts && git diff --exit-code -- dist/protocol.schema.json apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewayProtocol.kt apps/android/app/src/main/java/ai/openclaw/app/protocol/OpenClawProtocolConstants.kt",
|
||||||
"protocol:check:kotlin": "pnpm protocol:gen:kotlin && git diff --exit-code -- apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewayProtocol.kt apps/android/app/src/main/java/ai/openclaw/app/protocol/OpenClawProtocolConstants.kt",
|
"protocol:check:kotlin": "pnpm protocol:gen:kotlin && git diff --exit-code -- apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewayProtocol.kt apps/android/app/src/main/java/ai/openclaw/app/protocol/OpenClawProtocolConstants.kt",
|
||||||
"protocol:check:swift": "node --import tsx scripts/protocol-gen-swift.ts --check",
|
"protocol:check:swift": "node --import tsx scripts/protocol-gen-swift.ts --check",
|
||||||
"protocol:gen": "node --import tsx scripts/protocol-gen.ts",
|
"protocol:gen": "node --import tsx scripts/protocol-gen.ts",
|
||||||
"protocol:gen:kotlin": "node --import tsx scripts/protocol-gen-kotlin.ts",
|
"protocol:gen:kotlin": "node --import tsx scripts/protocol-gen-kotlin.ts",
|
||||||
"protocol:gen:swift": "node --import tsx scripts/protocol-gen-swift.ts",
|
"protocol:gen:swift": "node --import tsx scripts/protocol-gen-swift.ts",
|
||||||
"protocol-registry:check": "node --import tsx scripts/check-protocol-registry.mjs",
|
"protocol-registry:check": "node --import tsx scripts/check-protocol-registry.mts",
|
||||||
"proxy:coverage": "node scripts/run-node.mjs proxy coverage",
|
"proxy:coverage": "node scripts/run-node.mjs proxy coverage",
|
||||||
"proxy:gateway": "node scripts/run-node.mjs proxy run -- node scripts/run-node.mjs gateway",
|
"proxy:gateway": "node scripts/run-node.mjs proxy run -- node scripts/run-node.mjs gateway",
|
||||||
"proxy:install-ca": "node --import tsx scripts/proxy-install-ca.mjs",
|
"proxy:install-ca": "node --import tsx scripts/proxy-install-ca.mts",
|
||||||
"proxy:run": "node scripts/run-node.mjs proxy run",
|
"proxy:run": "node scripts/run-node.mjs proxy run",
|
||||||
"proxy:start": "node scripts/run-node.mjs proxy start",
|
"proxy:start": "node scripts/run-node.mjs proxy start",
|
||||||
"qa:e2e": "node --import tsx scripts/qa-e2e.ts",
|
"qa:e2e": "node --import tsx scripts/qa-e2e.ts",
|
||||||
@@ -1744,10 +1744,10 @@
|
|||||||
"qa:otel:smoke": "node --import tsx test/e2e/qa-lab/runtime/qa-otel-smoke-runtime.ts",
|
"qa:otel:smoke": "node --import tsx test/e2e/qa-lab/runtime/qa-otel-smoke-runtime.ts",
|
||||||
"qa:code-mode-models": "node --import tsx scripts/code-mode-model-matrix.ts",
|
"qa:code-mode-models": "node --import tsx scripts/code-mode-model-matrix.ts",
|
||||||
"qa:prometheus:smoke": "node scripts/run-node.mjs qa suite --provider-mode mock-openai --scenario docker-prometheus-smoke --concurrency 1 --fast",
|
"qa:prometheus:smoke": "node scripts/run-node.mjs qa suite --provider-mode mock-openai --scenario docker-prometheus-smoke --concurrency 1 --fast",
|
||||||
"release-metadata:check": "node scripts/check-release-metadata-only.mjs",
|
"release-metadata:check": "node --import tsx scripts/check-release-metadata-only.mts",
|
||||||
"release:beta": "node scripts/release-candidate-checklist.mjs",
|
"release:beta": "node --import tsx scripts/release-candidate-checklist.mts",
|
||||||
"release:beta-smoke": "node --import tsx scripts/release-beta-smoke.ts",
|
"release:beta-smoke": "node --import tsx scripts/release-beta-smoke.ts",
|
||||||
"release:candidate": "node scripts/release-candidate-checklist.mjs",
|
"release:candidate": "node --import tsx scripts/release-candidate-checklist.mts",
|
||||||
"release:check": "pnpm release:generated:check && node --import tsx scripts/release-check.ts",
|
"release:check": "pnpm release:generated:check && node --import tsx scripts/release-check.ts",
|
||||||
"release:fast-pretag-check": "bash scripts/release-fast-pretag-check.sh",
|
"release:fast-pretag-check": "bash scripts/release-fast-pretag-check.sh",
|
||||||
"release:generated:check": "node scripts/release-preflight.mjs --check",
|
"release:generated:check": "node scripts/release-preflight.mjs --check",
|
||||||
@@ -1767,17 +1767,17 @@
|
|||||||
"sqlite:sessions-schema:check": "node --import tsx scripts/generate-sqlite-session-schema-baseline.ts --check",
|
"sqlite:sessions-schema:check": "node --import tsx scripts/generate-sqlite-session-schema-baseline.ts --check",
|
||||||
"sqlite:sessions-schema:gen": "node --import tsx scripts/generate-sqlite-session-schema-baseline.ts --write",
|
"sqlite:sessions-schema:gen": "node --import tsx scripts/generate-sqlite-session-schema-baseline.ts --write",
|
||||||
"start": "node openclaw.mjs",
|
"start": "node openclaw.mjs",
|
||||||
"test": "node scripts/test-projects.mjs",
|
"test": "node --import tsx scripts/test-projects.mts",
|
||||||
"test:all": "pnpm lint && pnpm build && pnpm test && pnpm test:e2e && pnpm test:live && pnpm test:docker:all",
|
"test:all": "pnpm lint && pnpm build && pnpm test && pnpm test:e2e && pnpm test:live && pnpm test:docker:all",
|
||||||
"test:auth:compat": "node scripts/run-vitest.mjs run --config test/vitest/vitest.gateway.config.ts src/gateway/server.auth.compat-baseline.test.ts src/gateway/client.test.ts src/gateway/reconnect-gating.test.ts && node scripts/run-vitest.mjs run packages/gateway-protocol/src/connect-error-details.test.ts",
|
"test:auth:compat": "node scripts/run-vitest.mjs run --config test/vitest/vitest.gateway.config.ts src/gateway/server.auth.compat-baseline.test.ts src/gateway/client.test.ts src/gateway/reconnect-gating.test.ts && node scripts/run-vitest.mjs run packages/gateway-protocol/src/connect-error-details.test.ts",
|
||||||
"test:build:singleton": "node scripts/test-built-plugin-singleton.mjs",
|
"test:build:singleton": "node --import tsx scripts/test-built-plugin-singleton.mts",
|
||||||
"test:build:status-message-runtime": "node scripts/test-built-status-message-runtime.mjs",
|
"test:build:status-message-runtime": "node --import tsx scripts/test-built-status-message-runtime.mts",
|
||||||
"test:bundled": "node scripts/run-vitest.mjs run --config test/vitest/vitest.bundled.config.ts",
|
"test:bundled": "node scripts/run-vitest.mjs run --config test/vitest/vitest.bundled.config.ts",
|
||||||
"test:changed": "node scripts/test-projects.mjs --changed origin/main",
|
"test:changed": "node --import tsx scripts/test-projects.mts --changed origin/main",
|
||||||
"test:changed:max": "node scripts/test-projects-max.mjs --changed origin/main",
|
"test:changed:max": "node --import tsx scripts/test-projects-max.mts --changed origin/main",
|
||||||
"test:channels": "node scripts/run-vitest.mjs run --config test/vitest/vitest.channels.config.ts",
|
"test:channels": "node scripts/run-vitest.mjs run --config test/vitest/vitest.channels.config.ts",
|
||||||
"test:contracts": "pnpm test:contracts:channels && pnpm test:contracts:plugins",
|
"test:contracts": "pnpm test:contracts:channels && pnpm test:contracts:plugins",
|
||||||
"test:contracts:channels": "node scripts/test-projects.mjs --maxWorkers=1 test/vitest/vitest.contracts-channel-surface.config.ts test/vitest/vitest.contracts-channel-config.config.ts test/vitest/vitest.contracts-channel-registry.config.ts test/vitest/vitest.contracts-channel-session.config.ts",
|
"test:contracts:channels": "node --import tsx scripts/test-projects.mts --maxWorkers=1 test/vitest/vitest.contracts-channel-surface.config.ts test/vitest/vitest.contracts-channel-config.config.ts test/vitest/vitest.contracts-channel-registry.config.ts test/vitest/vitest.contracts-channel-session.config.ts",
|
||||||
"test:contracts:plugins": "node scripts/run-vitest.mjs run --config test/vitest/vitest.contracts-plugin.config.ts --maxWorkers=1",
|
"test:contracts:plugins": "node scripts/run-vitest.mjs run --config test/vitest/vitest.contracts-plugin.config.ts --maxWorkers=1",
|
||||||
"test:coverage": "node scripts/run-vitest.mjs run --config test/vitest/vitest.unit.config.ts --coverage",
|
"test:coverage": "node scripts/run-vitest.mjs run --config test/vitest/vitest.unit.config.ts --coverage",
|
||||||
"test:coverage:changed": "node scripts/run-vitest.mjs run --config test/vitest/vitest.unit.config.ts --coverage --changed origin/main",
|
"test:coverage:changed": "node scripts/run-vitest.mjs run --config test/vitest/vitest.unit.config.ts --coverage --changed origin/main",
|
||||||
@@ -1829,8 +1829,8 @@
|
|||||||
"test:docker:live-models": "bash scripts/test-live-models-docker.sh",
|
"test:docker:live-models": "bash scripts/test-live-models-docker.sh",
|
||||||
"test:docker:live-models:claude": "OPENCLAW_LIVE_PROVIDERS=claude-cli OPENCLAW_LIVE_MODELS=claude-cli/claude-sonnet-4-6 bash scripts/test-live-models-docker.sh",
|
"test:docker:live-models:claude": "OPENCLAW_LIVE_PROVIDERS=claude-cli OPENCLAW_LIVE_MODELS=claude-cli/claude-sonnet-4-6 bash scripts/test-live-models-docker.sh",
|
||||||
"test:docker:live-models:gemini": "OPENCLAW_LIVE_PROVIDERS=google-gemini-cli OPENCLAW_LIVE_MODELS=google-gemini-cli/gemini-3.1-pro-preview bash scripts/test-live-models-docker.sh",
|
"test:docker:live-models:gemini": "OPENCLAW_LIVE_PROVIDERS=google-gemini-cli OPENCLAW_LIVE_MODELS=google-gemini-cli/gemini-3.1-pro-preview bash scripts/test-live-models-docker.sh",
|
||||||
"test:docker:live:all": "node scripts/run-with-env.mjs OPENCLAW_DOCKER_ALL_LIVE_MODE=only -- node scripts/test-docker-all.mjs",
|
"test:docker:live:all": "node --import tsx scripts/run-with-env.mts OPENCLAW_DOCKER_ALL_LIVE_MODE=only -- node scripts/test-docker-all.mjs",
|
||||||
"test:docker:local:all": "node scripts/run-with-env.mjs OPENCLAW_DOCKER_ALL_LIVE_MODE=skip -- node scripts/test-docker-all.mjs",
|
"test:docker:local:all": "node --import tsx scripts/run-with-env.mts OPENCLAW_DOCKER_ALL_LIVE_MODE=skip -- node scripts/test-docker-all.mjs",
|
||||||
"test:docker:cli-installer-distribution": "bash scripts/e2e/cli-installer-distribution-docker.sh",
|
"test:docker:cli-installer-distribution": "bash scripts/e2e/cli-installer-distribution-docker.sh",
|
||||||
"test:docker:mcp-channels": "bash scripts/e2e/mcp-channels-docker.sh",
|
"test:docker:mcp-channels": "bash scripts/e2e/mcp-channels-docker.sh",
|
||||||
"test:docker:mcp-code-mode-gateway": "bash scripts/e2e/mcp-code-mode-gateway-docker.sh",
|
"test:docker:mcp-code-mode-gateway": "bash scripts/e2e/mcp-code-mode-gateway-docker.sh",
|
||||||
@@ -1857,12 +1857,12 @@
|
|||||||
"test:docker:plugins": "bash scripts/e2e/plugins-docker.sh",
|
"test:docker:plugins": "bash scripts/e2e/plugins-docker.sh",
|
||||||
"test:docker:published-upgrade-survivor": "env OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE=1 OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC=${OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC:-openclaw@latest} OPENCLAW_UPGRADE_SURVIVOR_DOCKER_RUN_TIMEOUT=${OPENCLAW_UPGRADE_SURVIVOR_DOCKER_RUN_TIMEOUT:-1500s} bash scripts/e2e/upgrade-survivor-docker.sh",
|
"test:docker:published-upgrade-survivor": "env OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE=1 OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC=${OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC:-openclaw@latest} OPENCLAW_UPGRADE_SURVIVOR_DOCKER_RUN_TIMEOUT=${OPENCLAW_UPGRADE_SURVIVOR_DOCKER_RUN_TIMEOUT:-1500s} bash scripts/e2e/upgrade-survivor-docker.sh",
|
||||||
"test:docker:qr": "bash scripts/e2e/qr-import-docker.sh",
|
"test:docker:qr": "bash scripts/e2e/qr-import-docker.sh",
|
||||||
"test:docker:rerun": "node scripts/docker-e2e-rerun.mjs",
|
"test:docker:rerun": "node --import tsx scripts/docker-e2e-rerun.mts",
|
||||||
"test:docker:root-managed-vps-upgrade": "env OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE=1 OPENCLAW_UPGRADE_SURVIVOR_ROOT_MANAGED_VPS=1 OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC=${OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC:-openclaw@2026.5.7} OPENCLAW_UPGRADE_SURVIVOR_DOCKER_RUN_TIMEOUT=${OPENCLAW_UPGRADE_SURVIVOR_DOCKER_RUN_TIMEOUT:-1500s} bash scripts/e2e/upgrade-survivor-docker.sh",
|
"test:docker:root-managed-vps-upgrade": "env OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE=1 OPENCLAW_UPGRADE_SURVIVOR_ROOT_MANAGED_VPS=1 OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC=${OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC:-openclaw@2026.5.7} OPENCLAW_UPGRADE_SURVIVOR_DOCKER_RUN_TIMEOUT=${OPENCLAW_UPGRADE_SURVIVOR_DOCKER_RUN_TIMEOUT:-1500s} bash scripts/e2e/upgrade-survivor-docker.sh",
|
||||||
"test:docker:selected-plugins": "bash scripts/e2e/docker-selected-plugins.sh",
|
"test:docker:selected-plugins": "bash scripts/e2e/docker-selected-plugins.sh",
|
||||||
"test:docker:session-runtime-context": "bash scripts/e2e/session-runtime-context-docker.sh",
|
"test:docker:session-runtime-context": "bash scripts/e2e/session-runtime-context-docker.sh",
|
||||||
"test:docker:skill-install": "bash scripts/e2e/skill-install-docker.sh",
|
"test:docker:skill-install": "bash scripts/e2e/skill-install-docker.sh",
|
||||||
"test:docker:timings": "node scripts/docker-e2e-timings.mjs",
|
"test:docker:timings": "node --import tsx scripts/docker-e2e-timings.mts",
|
||||||
"test:docker:update-channel-switch": "bash scripts/e2e/update-channel-switch-docker.sh",
|
"test:docker:update-channel-switch": "bash scripts/e2e/update-channel-switch-docker.sh",
|
||||||
"test:docker:update-corrupt-plugin": "bash scripts/e2e/update-corrupt-plugin-docker.sh",
|
"test:docker:update-corrupt-plugin": "bash scripts/e2e/update-corrupt-plugin-docker.sh",
|
||||||
"test:docker:update-migration": "env OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE=1 OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC=${OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC:-openclaw@2026.4.23} OPENCLAW_UPGRADE_SURVIVOR_SCENARIO=${OPENCLAW_UPGRADE_SURVIVOR_SCENARIO:-plugin-deps-cleanup} bash scripts/e2e/upgrade-survivor-docker.sh",
|
"test:docker:update-migration": "env OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE=1 OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC=${OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC:-openclaw@2026.4.23} OPENCLAW_UPGRADE_SURVIVOR_SCENARIO=${OPENCLAW_UPGRADE_SURVIVOR_SCENARIO:-plugin-deps-cleanup} bash scripts/e2e/upgrade-survivor-docker.sh",
|
||||||
@@ -1874,40 +1874,40 @@
|
|||||||
"test:type-suppression-inventory:report": "node --import tsx scripts/type-suppression-inventory.ts",
|
"test:type-suppression-inventory:report": "node --import tsx scripts/type-suppression-inventory.ts",
|
||||||
"test:e2e": "pnpm test:e2e:gateway && pnpm test:e2e:agent-plugin-gateway && pnpm test:ui:e2e",
|
"test:e2e": "pnpm test:e2e:gateway && pnpm test:e2e:agent-plugin-gateway && pnpm test:ui:e2e",
|
||||||
"test:e2e:agent-plugin-gateway": "node --import tsx scripts/agent-plugin-gateway-e2e.ts",
|
"test:e2e:agent-plugin-gateway": "node --import tsx scripts/agent-plugin-gateway-e2e.ts",
|
||||||
"test:e2e:browser-copilot": "node scripts/run-with-env.mjs PLAYWRIGHT_BROWSERS_PATH=.artifacts/playwright-browsers -- node scripts/ensure-playwright-chromium.mjs --require-playwright-chromium && node scripts/run-with-env.mjs PLAYWRIGHT_BROWSERS_PATH=.artifacts/playwright-browsers OPENCLAW_BROWSER_COPILOT_E2E=1 OPENCLAW_E2E_WORKERS=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts extensions/browser/chrome-extension/page-share.e2e.test.ts extensions/browser/chrome-extension/sidepanel.e2e.test.ts",
|
"test:e2e:browser-copilot": "node --import tsx scripts/run-with-env.mts PLAYWRIGHT_BROWSERS_PATH=.artifacts/playwright-browsers -- node --import tsx scripts/ensure-playwright-chromium.mts --require-playwright-chromium && node --import tsx scripts/run-with-env.mts PLAYWRIGHT_BROWSERS_PATH=.artifacts/playwright-browsers OPENCLAW_BROWSER_COPILOT_E2E=1 OPENCLAW_E2E_WORKERS=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts extensions/browser/chrome-extension/page-share.e2e.test.ts extensions/browser/chrome-extension/sidepanel.e2e.test.ts",
|
||||||
"test:e2e:gateway": "node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts",
|
"test:e2e:gateway": "node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts",
|
||||||
"test:e2e:openshell": "node scripts/run-with-env.mjs OPENCLAW_E2E_OPENSHELL=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts extensions/openshell/src/backend.e2e.test.ts",
|
"test:e2e:openshell": "node --import tsx scripts/run-with-env.mts OPENCLAW_E2E_OPENSHELL=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts extensions/openshell/src/backend.e2e.test.ts",
|
||||||
"test:e2e:status-corrupt-plugin-deps": "bash scripts/e2e/status-corrupt-plugin-deps.sh",
|
"test:e2e:status-corrupt-plugin-deps": "bash scripts/e2e/status-corrupt-plugin-deps.sh",
|
||||||
"test:extension": "node scripts/test-extension.mjs",
|
"test:extension": "node --import tsx scripts/test-extension.mts",
|
||||||
"test:extensions": "node scripts/test-projects.mjs extensions",
|
"test:extensions": "node --import tsx scripts/test-projects.mts extensions",
|
||||||
"test:extensions:batch": "node scripts/test-extension-batch.mjs",
|
"test:extensions:batch": "node --import tsx scripts/test-extension-batch.mts",
|
||||||
"test:extensions:memory": "node scripts/profile-extension-memory.mjs",
|
"test:extensions:memory": "node --import tsx scripts/profile-extension-memory.mts",
|
||||||
"test:extensions:package-boundary": "node scripts/check-extension-package-tsc-boundary.mjs",
|
"test:extensions:package-boundary": "node --import tsx scripts/check-extension-package-tsc-boundary.mts",
|
||||||
"test:extensions:package-boundary:canary": "node scripts/check-extension-package-tsc-boundary.mjs --mode=canary",
|
"test:extensions:package-boundary:canary": "node --import tsx scripts/check-extension-package-tsc-boundary.mts --mode=canary",
|
||||||
"test:extensions:package-boundary:compile": "node scripts/check-extension-package-tsc-boundary.mjs --mode=compile",
|
"test:extensions:package-boundary:compile": "node --import tsx scripts/check-extension-package-tsc-boundary.mts --mode=compile",
|
||||||
"test:fast": "node scripts/run-vitest.mjs run --config test/vitest/vitest.unit.config.ts",
|
"test:fast": "node scripts/run-vitest.mjs run --config test/vitest/vitest.unit.config.ts",
|
||||||
"test:force": "node --import tsx scripts/test-force.ts",
|
"test:force": "node --import tsx scripts/test-force.ts",
|
||||||
"test:gateway": "node scripts/run-with-env.mjs OPENCLAW_GATEWAY_PROJECT_SHARDS=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.gateway.config.ts",
|
"test:gateway": "node --import tsx scripts/run-with-env.mts OPENCLAW_GATEWAY_PROJECT_SHARDS=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.gateway.config.ts",
|
||||||
"test:gateway:cpu-scenarios": "node scripts/check-gateway-cpu-scenarios.mjs",
|
"test:gateway:cpu-scenarios": "node --import tsx scripts/check-gateway-cpu-scenarios.mts",
|
||||||
"test:gateway:concurrency": "node scripts/bench-gateway-concurrency.ts",
|
"test:gateway:concurrency": "node scripts/bench-gateway-concurrency.ts",
|
||||||
"test:gateway:memory-fd-repro": "node scripts/check-memory-fd-repro.mjs",
|
"test:gateway:memory-fd-repro": "node --import tsx scripts/check-memory-fd-repro.mts",
|
||||||
"test:gateway:watch-regression": "node scripts/check-gateway-watch-regression.mjs",
|
"test:gateway:watch-regression": "node --import tsx scripts/check-gateway-watch-regression.mts",
|
||||||
"test:install:e2e": "bash scripts/test-install-sh-e2e-docker.sh",
|
"test:install:e2e": "bash scripts/test-install-sh-e2e-docker.sh",
|
||||||
"test:install:e2e:anthropic": "OPENCLAW_E2E_MODELS=anthropic bash scripts/test-install-sh-e2e-docker.sh",
|
"test:install:e2e:anthropic": "OPENCLAW_E2E_MODELS=anthropic bash scripts/test-install-sh-e2e-docker.sh",
|
||||||
"test:install:e2e:openai": "OPENCLAW_E2E_MODELS=openai bash scripts/test-install-sh-e2e-docker.sh",
|
"test:install:e2e:openai": "OPENCLAW_E2E_MODELS=openai bash scripts/test-install-sh-e2e-docker.sh",
|
||||||
"test:install:smoke": "bash scripts/test-install-sh-docker.sh",
|
"test:install:smoke": "bash scripts/test-install-sh-docker.sh",
|
||||||
"test:live": "node scripts/test-live.mjs",
|
"test:live": "node --import tsx scripts/test-live.mts",
|
||||||
"test:live:cache": "node scripts/run-with-env.mjs OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_CACHE_TEST=1 -- node --import tsx scripts/check-live-cache.ts",
|
"test:live:cache": "node --import tsx scripts/run-with-env.mts OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_CACHE_TEST=1 -- node --import tsx scripts/check-live-cache.ts",
|
||||||
"test:live:codex-harness": "node scripts/test-live.mjs --codex-harness -- src/gateway/gateway-codex-harness.live.test.ts",
|
"test:live:codex-harness": "node --import tsx scripts/test-live.mts --codex-harness -- src/gateway/gateway-codex-harness.live.test.ts",
|
||||||
"test:live:system-agent-rescue-channel": "node scripts/run-with-env.mjs OPENCLAW_LIVE_SYSTEM_AGENT_RESCUE_CHANNEL=1 -- node scripts/test-live.mjs -- src/system-agent/rescue-channel.live.test.ts",
|
"test:live:system-agent-rescue-channel": "node --import tsx scripts/run-with-env.mts OPENCLAW_LIVE_SYSTEM_AGENT_RESCUE_CHANNEL=1 -- node --import tsx scripts/test-live.mts -- src/system-agent/rescue-channel.live.test.ts",
|
||||||
"test:live:gateway-profiles": "node scripts/test-live.mjs -- src/gateway/gateway-models.profiles.live.test.ts",
|
"test:live:gateway-profiles": "node --import tsx scripts/test-live.mts -- src/gateway/gateway-models.profiles.live.test.ts",
|
||||||
"test:live:media": "node --import tsx test/e2e/qa-lab/media/hosted-media-provider-live.ts",
|
"test:live:media": "node --import tsx test/e2e/qa-lab/media/hosted-media-provider-live.ts",
|
||||||
"test:live:media:image": "node --import tsx test/e2e/qa-lab/media/hosted-media-provider-live.ts image",
|
"test:live:media:image": "node --import tsx test/e2e/qa-lab/media/hosted-media-provider-live.ts image",
|
||||||
"test:live:media:music": "node --import tsx test/e2e/qa-lab/media/hosted-media-provider-live.ts music",
|
"test:live:media:music": "node --import tsx test/e2e/qa-lab/media/hosted-media-provider-live.ts music",
|
||||||
"test:live:media:video": "node --import tsx test/e2e/qa-lab/media/hosted-media-provider-live.ts video",
|
"test:live:media:video": "node --import tsx test/e2e/qa-lab/media/hosted-media-provider-live.ts video",
|
||||||
"test:live:models-profiles": "node scripts/test-live.mjs -- src/agents/models.profiles.live.test.ts",
|
"test:live:models-profiles": "node --import tsx scripts/test-live.mts -- src/agents/models.profiles.live.test.ts",
|
||||||
"test:macos:ci": "node scripts/test-projects.mjs src/daemon/launchd.test.ts src/daemon/runtime-paths.test.ts src/daemon/runtime-binary.test.ts src/gateway/worker-environments/workspace-rsync-path.test.ts src/infra/brew.test.ts src/infra/stable-node-path.test.ts test/scripts/vitest-process-group.test.ts test/scripts/package-mac-app.test.ts test/scripts/package-mac-dist.test.ts test/scripts/create-dmg.test.ts test/scripts/codesign-mac-app.test.ts test/scripts/notarize-mac-artifact.test.ts",
|
"test:macos:ci": "node --import tsx scripts/test-projects.mts src/daemon/launchd.test.ts src/daemon/runtime-paths.test.ts src/daemon/runtime-binary.test.ts src/gateway/worker-environments/workspace-rsync-path.test.ts src/infra/brew.test.ts src/infra/stable-node-path.test.ts test/scripts/vitest-process-group.test.ts test/scripts/package-mac-app.test.ts test/scripts/package-mac-dist.test.ts test/scripts/create-dmg.test.ts test/scripts/codesign-mac-app.test.ts test/scripts/notarize-mac-artifact.test.ts",
|
||||||
"test:max": "node scripts/test-projects-max.mjs",
|
"test:max": "node --import tsx scripts/test-projects-max.mts",
|
||||||
"test:parallels:linux": "bash scripts/e2e/parallels-linux-smoke.sh",
|
"test:parallels:linux": "bash scripts/e2e/parallels-linux-smoke.sh",
|
||||||
"test:parallels:macos": "bash scripts/e2e/parallels-macos-smoke.sh",
|
"test:parallels:macos": "bash scripts/e2e/parallels-macos-smoke.sh",
|
||||||
"test:parallels:npm-update": "bash scripts/e2e/parallels-npm-update-smoke.sh",
|
"test:parallels:npm-update": "bash scripts/e2e/parallels-npm-update-smoke.sh",
|
||||||
@@ -1915,43 +1915,43 @@
|
|||||||
"test:parallels:windows": "bash scripts/e2e/parallels-windows-smoke.sh",
|
"test:parallels:windows": "bash scripts/e2e/parallels-windows-smoke.sh",
|
||||||
"test:agents:concurrency": "node --import tsx scripts/bench-agent-concurrency.ts",
|
"test:agents:concurrency": "node --import tsx scripts/bench-agent-concurrency.ts",
|
||||||
"test:tasks:sqlite-churn": "node --import tsx scripts/bench-task-registry-sqlite.ts",
|
"test:tasks:sqlite-churn": "node --import tsx scripts/bench-task-registry-sqlite.ts",
|
||||||
"test:perf:budget": "node scripts/test-perf-budget.mjs",
|
"test:perf:budget": "node --import tsx scripts/test-perf-budget.mts",
|
||||||
"test:perf:changed:bench": "node scripts/bench-test-changed.mjs",
|
"test:perf:changed:bench": "node --import tsx scripts/bench-test-changed.mts",
|
||||||
"test:perf:groups": "node scripts/test-group-report.mjs",
|
"test:perf:groups": "node --import tsx scripts/test-group-report.mts",
|
||||||
"test:perf:groups:compare": "node scripts/test-group-report.mjs --compare",
|
"test:perf:groups:compare": "node --import tsx scripts/test-group-report.mts --compare",
|
||||||
"test:perf:hotspots": "node scripts/test-hotspots.mjs",
|
"test:perf:hotspots": "node --import tsx scripts/test-hotspots.mts",
|
||||||
"test:perf:imports": "node scripts/test-projects-imports.mjs",
|
"test:perf:imports": "node --import tsx scripts/test-projects-imports.mts",
|
||||||
"test:perf:imports:changed": "node scripts/test-projects-imports.mjs --changed origin/main",
|
"test:perf:imports:changed": "node --import tsx scripts/test-projects-imports.mts --changed origin/main",
|
||||||
"test:perf:profile:main": "node scripts/run-vitest-profile.mjs main",
|
"test:perf:profile:main": "node --import tsx scripts/run-vitest-profile.mts main",
|
||||||
"test:perf:profile:runner": "node scripts/run-vitest-profile.mjs runner",
|
"test:perf:profile:runner": "node --import tsx scripts/run-vitest-profile.mts runner",
|
||||||
"test:sqlite:perf": "node --import tsx scripts/bench-sqlite-state.ts --profile default --output .artifacts/sqlite-perf/default.json",
|
"test:sqlite:perf": "node --import tsx scripts/bench-sqlite-state.ts --profile default --output .artifacts/sqlite-perf/default.json",
|
||||||
"test:sqlite:perf:large": "node --import tsx scripts/bench-sqlite-state.ts --profile large --output .artifacts/sqlite-perf/large.json",
|
"test:sqlite:perf:large": "node --import tsx scripts/bench-sqlite-state.ts --profile large --output .artifacts/sqlite-perf/large.json",
|
||||||
"test:sqlite:perf:smoke": "node --import tsx scripts/bench-sqlite-state.ts --profile smoke --output .artifacts/sqlite-perf/smoke.json",
|
"test:sqlite:perf:smoke": "node --import tsx scripts/bench-sqlite-state.ts --profile smoke --output .artifacts/sqlite-perf/smoke.json",
|
||||||
"test:plugins:gateway-gauntlet": "node scripts/check-plugin-gateway-gauntlet.mjs",
|
"test:plugins:gateway-gauntlet": "node --import tsx scripts/check-plugin-gateway-gauntlet.mts",
|
||||||
"test:plugins:init-provider-scaffold": "node --import tsx scripts/validate-plugin-init-provider-scaffold.ts",
|
"test:plugins:init-provider-scaffold": "node --import tsx scripts/validate-plugin-init-provider-scaffold.ts",
|
||||||
"test:plugins:kitchen-sink-live": "bash -lc 'if [ -x \"$HOME/.local/bin/openclaw-testbox-env\" ]; then exec \"$HOME/.local/bin/openclaw-testbox-env\" pnpm openclaw qa suite --provider-mode live-frontier --scenario kitchen-sink-live-openai; fi; exec pnpm openclaw qa suite --provider-mode live-frontier --scenario kitchen-sink-live-openai'",
|
"test:plugins:kitchen-sink-live": "bash -lc 'if [ -x \"$HOME/.local/bin/openclaw-testbox-env\" ]; then exec \"$HOME/.local/bin/openclaw-testbox-env\" pnpm openclaw qa suite --provider-mode live-frontier --scenario kitchen-sink-live-openai; fi; exec pnpm openclaw qa suite --provider-mode live-frontier --scenario kitchen-sink-live-openai'",
|
||||||
"test:plugins:kitchen-sink-rpc": "node --import tsx scripts/e2e/kitchen-sink-rpc-walk.mjs",
|
"test:plugins:kitchen-sink-rpc": "node --import tsx scripts/e2e/kitchen-sink-rpc-walk.mts",
|
||||||
"test:sectriage": "node scripts/run-with-env.mjs OPENCLAW_GATEWAY_PROJECT_SHARDS=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.gateway.config.ts && node scripts/run-vitest.mjs run --config test/vitest/vitest.unit.config.ts --exclude src/process/exec.test.ts",
|
"test:sectriage": "node --import tsx scripts/run-with-env.mts OPENCLAW_GATEWAY_PROJECT_SHARDS=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.gateway.config.ts && node scripts/run-vitest.mjs run --config test/vitest/vitest.unit.config.ts --exclude src/process/exec.test.ts",
|
||||||
"test:serial": "node scripts/test-projects-serial.mjs",
|
"test:serial": "node --import tsx scripts/test-projects-serial.mts",
|
||||||
"test:stability:gateway": "node scripts/run-with-env.mjs OPENCLAW_VITEST_MAX_WORKERS=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.gateway.config.ts src/gateway/gateway-stability.test.ts && node scripts/run-with-env.mjs OPENCLAW_VITEST_MAX_WORKERS=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.logging.config.ts src/logging/diagnostic-stability-bundle.test.ts && node scripts/run-with-env.mjs OPENCLAW_VITEST_MAX_WORKERS=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.infra.config.ts src/infra/fatal-error-hooks.test.ts",
|
"test:stability:gateway": "node --import tsx scripts/run-with-env.mts OPENCLAW_VITEST_MAX_WORKERS=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.gateway.config.ts src/gateway/gateway-stability.test.ts && node --import tsx scripts/run-with-env.mts OPENCLAW_VITEST_MAX_WORKERS=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.logging.config.ts src/logging/diagnostic-stability-bundle.test.ts && node --import tsx scripts/run-with-env.mts OPENCLAW_VITEST_MAX_WORKERS=1 -- node scripts/run-vitest.mjs run --config test/vitest/vitest.infra.config.ts src/infra/fatal-error-hooks.test.ts",
|
||||||
"test:cli-response:contract": "node scripts/build-all.mjs cliStartup && node scripts/test-cli-startup-bench-budget.mjs --preset response --runs 1 --warmup 0 --timeout-ms 10000 --skip-baseline",
|
"test:cli-response:contract": "node --import tsx scripts/build-all.mts cliStartup && node --import tsx scripts/test-cli-startup-bench-budget.mts --preset response --runs 1 --warmup 0 --timeout-ms 10000 --skip-baseline",
|
||||||
"test:startup:bench": "node --import tsx scripts/bench-cli-startup.ts",
|
"test:startup:bench": "node --import tsx scripts/bench-cli-startup.ts",
|
||||||
"test:startup:bench:check": "node scripts/test-cli-startup-bench-budget.mjs",
|
"test:startup:bench:check": "node --import tsx scripts/test-cli-startup-bench-budget.mts",
|
||||||
"test:startup:bench:save": "node --import tsx scripts/bench-cli-startup.ts --preset all --runs 5 --warmup 1 --output .artifacts/cli-startup-bench-all.json",
|
"test:startup:bench:save": "node --import tsx scripts/bench-cli-startup.ts --preset all --runs 5 --warmup 1 --output .artifacts/cli-startup-bench-all.json",
|
||||||
"test:startup:bench:smoke": "node scripts/ensure-cli-startup-build.mjs && node --import tsx scripts/bench-cli-startup.ts --preset real --case gatewayStatusJson --runs 1 --warmup 0 --output .artifacts/cli-startup-bench-smoke.json",
|
"test:startup:bench:smoke": "node --import tsx scripts/ensure-cli-startup-build.mts && node --import tsx scripts/bench-cli-startup.ts --preset real --case gatewayStatusJson --runs 1 --warmup 0 --output .artifacts/cli-startup-bench-smoke.json",
|
||||||
"test:startup:bench:update": "node scripts/test-update-cli-startup-bench.mjs",
|
"test:startup:bench:update": "node --import tsx scripts/test-update-cli-startup-bench.mts",
|
||||||
"test:startup:gateway": "node --import tsx scripts/bench-gateway-startup.ts",
|
"test:startup:gateway": "node --import tsx scripts/bench-gateway-startup.ts",
|
||||||
"test:restart:gateway": "node --import tsx scripts/bench-gateway-restart.ts",
|
"test:restart:gateway": "node --import tsx scripts/bench-gateway-restart.ts",
|
||||||
"test:startup:memory": "node scripts/ensure-cli-startup-build.mjs && node scripts/check-cli-startup-memory.mjs",
|
"test:startup:memory": "node --import tsx scripts/ensure-cli-startup-build.mts && node scripts/check-cli-startup-memory.mjs",
|
||||||
"test:ui": "pnpm lint:ui:no-raw-window-open && node scripts/ensure-playwright-chromium.mjs && pnpm --dir ui test",
|
"test:ui": "pnpm lint:ui:no-raw-window-open && node --import tsx scripts/ensure-playwright-chromium.mts && pnpm --dir ui test",
|
||||||
"test:ui:e2e": "node scripts/ensure-playwright-chromium.mjs && node scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner",
|
"test:ui:e2e": "node --import tsx scripts/ensure-playwright-chromium.mts && node scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner",
|
||||||
"test:unit": "pnpm test:unit:fast && node scripts/run-vitest.mjs run --config test/vitest/vitest.unit.config.ts",
|
"test:unit": "pnpm test:unit:fast && node scripts/run-vitest.mjs run --config test/vitest/vitest.unit.config.ts",
|
||||||
"test:unit:fast": "node scripts/run-vitest.mjs run --config test/vitest/vitest.unit-fast.config.ts",
|
"test:unit:fast": "node scripts/run-vitest.mjs run --config test/vitest/vitest.unit-fast.config.ts",
|
||||||
"test:unit:fast:audit": "node scripts/test-unit-fast-audit.mjs",
|
"test:unit:fast:audit": "node --import tsx scripts/test-unit-fast-audit.mts",
|
||||||
"test:voicecall:closedloop": "node scripts/test-voicecall-closedloop.mjs",
|
"test:voicecall:closedloop": "node --import tsx scripts/test-voicecall-closedloop.mts",
|
||||||
"test:watch": "node scripts/test-projects.mjs --watch",
|
"test:watch": "node --import tsx scripts/test-projects.mts --watch",
|
||||||
"test:windows:ci": "node scripts/test-projects.mjs src/shared/runtime-import.test.ts src/config/sessions/session-accessor.sqlite-archive.worker.test.ts src/commands/doctor-gateway-auth-token.windows.test.ts src/infra/sqlite-snapshot.test.ts src/infra/ssh-client.windows.test.ts src/infra/update-managed-service-handoff-command.test.ts src/infra/update-managed-service-handoff-lifecycle.test.ts src/infra/exec-allowlist-pattern.test.ts src/infra/fs-safe-remove.test.ts src/snapshot/local-repository.windows.test.ts src/state/openclaw-database-paths.windows.test.ts src/commands/backup-verify.test.ts src/infra/state-migrations.legacy-session-store.test.ts src/test-utils/openclaw-test-state.test.ts src/agents/sessions/windows-git-bash-path.test.ts src/agents/bash-tools.exec.script-preflight.test.ts src/process/exec.windows.test.ts src/process/exec.windows.integration.test.ts src/process/windows-command.test.ts src/infra/windows-install-roots.test.ts src/node-host/invoke-system-run-allowlist.test.ts src/daemon/schtasks.startup-fallback.test.ts extensions/lobster/src/lobster-runner.test.ts extensions/mxc/test/mxc-backend.test.ts extensions/mxc/test/sandbox-policy-loader.test.ts test/e2e/qa-lab/runtime/doctor-auth-secretref-checks.e2e.test.ts test/e2e/qa-lab/runtime/package-openclaw-for-docker.e2e.test.ts test/scripts/direct-run-entrypoints.test.ts test/scripts/format-generated-module.test.ts test/scripts/npm-runner.test.ts test/scripts/openclaw-cross-os-installer.windows.test.ts test/scripts/openclaw-cross-os-release-workflow.test.ts test/scripts/pnpm-runner.test.ts test/scripts/run-with-env.test.ts test/scripts/ts-topology.test.ts test/scripts/ui.test.ts test/scripts/vitest-process-group.test.ts",
|
"test:windows:ci": "node --import tsx scripts/test-projects.mts src/shared/runtime-import.test.ts src/config/sessions/session-accessor.sqlite-archive.worker.test.ts src/commands/doctor-gateway-auth-token.windows.test.ts src/infra/sqlite-snapshot.test.ts src/infra/ssh-client.windows.test.ts src/infra/update-managed-service-handoff-command.test.ts src/infra/update-managed-service-handoff-lifecycle.test.ts src/infra/exec-allowlist-pattern.test.ts src/infra/fs-safe-remove.test.ts src/snapshot/local-repository.windows.test.ts src/state/openclaw-database-paths.windows.test.ts src/commands/backup-verify.test.ts src/infra/state-migrations.legacy-session-store.test.ts src/test-utils/openclaw-test-state.test.ts src/agents/sessions/windows-git-bash-path.test.ts src/agents/bash-tools.exec.script-preflight.test.ts src/process/exec.windows.test.ts src/process/exec.windows.integration.test.ts src/process/windows-command.test.ts src/infra/windows-install-roots.test.ts src/node-host/invoke-system-run-allowlist.test.ts src/daemon/schtasks.startup-fallback.test.ts extensions/lobster/src/lobster-runner.test.ts extensions/mxc/test/mxc-backend.test.ts extensions/mxc/test/sandbox-policy-loader.test.ts test/e2e/qa-lab/runtime/doctor-auth-secretref-checks.e2e.test.ts test/e2e/qa-lab/runtime/package-openclaw-for-docker.e2e.test.ts test/scripts/direct-run-entrypoints.test.ts test/scripts/format-generated-module.test.ts test/scripts/npm-runner.test.ts test/scripts/openclaw-cross-os-installer.windows.test.ts test/scripts/openclaw-cross-os-release-workflow.test.ts test/scripts/pnpm-runner.test.ts test/scripts/run-with-env.test.ts test/scripts/ts-topology.test.ts test/scripts/ui.test.ts test/scripts/vitest-process-group.test.ts",
|
||||||
"test:windows:schtasks:integration": "node scripts/run-with-env.mjs CI_WINDOWS_SCHTASKS_INTEGRATION=1 OPENCLAW_E2E_VERBOSE=1 OPENCLAW_VITEST_MAX_WORKERS=1 -- node scripts/run-vitest.mjs src/daemon/schtasks.integration.e2e.test.ts",
|
"test:windows:schtasks:integration": "node --import tsx scripts/run-with-env.mts CI_WINDOWS_SCHTASKS_INTEGRATION=1 OPENCLAW_E2E_VERBOSE=1 OPENCLAW_VITEST_MAX_WORKERS=1 -- node scripts/run-vitest.mjs src/daemon/schtasks.integration.e2e.test.ts",
|
||||||
"tool-display:check": "node --import tsx scripts/tool-display.ts --check",
|
"tool-display:check": "node --import tsx scripts/tool-display.ts --check",
|
||||||
"tool-display:write": "node --import tsx scripts/tool-display.ts --write",
|
"tool-display:write": "node --import tsx scripts/tool-display.ts --write",
|
||||||
"ts-topology": "node --import tsx scripts/ts-topology.ts",
|
"ts-topology": "node --import tsx scripts/ts-topology.ts",
|
||||||
@@ -1964,7 +1964,7 @@
|
|||||||
"tsgo:extensions:all": "node scripts/run-tsgo.mjs -b tsconfig.extensions.projects.json",
|
"tsgo:extensions:all": "node scripts/run-tsgo.mjs -b tsconfig.extensions.projects.json",
|
||||||
"tsgo:extensions:test": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.extensions.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions-test.tsbuildinfo",
|
"tsgo:extensions:test": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.extensions.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions-test.tsbuildinfo",
|
||||||
"tsgo:prod": "pnpm tsgo:core && pnpm tsgo:ui && pnpm tsgo:extensions",
|
"tsgo:prod": "pnpm tsgo:core && pnpm tsgo:ui && pnpm tsgo:extensions",
|
||||||
"tsgo:profile": "node scripts/profile-tsgo.mjs",
|
"tsgo:profile": "node --import tsx scripts/profile-tsgo.mts",
|
||||||
"tsgo:scripts": "node scripts/run-tsgo.mjs -p tsconfig.scripts.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/scripts.tsbuildinfo",
|
"tsgo:scripts": "node scripts/run-tsgo.mjs -p tsconfig.scripts.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/scripts.tsbuildinfo",
|
||||||
"tsgo:test": "pnpm tsgo:core:test && pnpm tsgo:extensions:test && pnpm tsgo:test:root",
|
"tsgo:test": "pnpm tsgo:core:test && pnpm tsgo:extensions:test && pnpm tsgo:test:root",
|
||||||
"tsgo:test:extensions": "pnpm tsgo:extensions:test",
|
"tsgo:test:extensions": "pnpm tsgo:extensions:test",
|
||||||
@@ -1974,12 +1974,12 @@
|
|||||||
"tsgo:test:ui": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.test.ui.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/test-ui.tsbuildinfo",
|
"tsgo:test:ui": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.test.ui.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/test-ui.tsbuildinfo",
|
||||||
"tsgo:ui": "node scripts/run-tsgo.mjs -p tsconfig.ui.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/ui.tsbuildinfo",
|
"tsgo:ui": "node scripts/run-tsgo.mjs -p tsconfig.ui.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/ui.tsbuildinfo",
|
||||||
"tui": "node scripts/run-node.mjs tui",
|
"tui": "node scripts/run-node.mjs tui",
|
||||||
"tui:dev": "node scripts/run-with-env.mjs OPENCLAW_PROFILE=dev -- node scripts/run-node.mjs --dev tui",
|
"tui:dev": "node --import tsx scripts/run-with-env.mts OPENCLAW_PROFILE=dev -- node scripts/run-node.mjs --dev tui",
|
||||||
"tui:pty:test:watch": "node --import tsx scripts/dev/tui-pty-test-watch.ts",
|
"tui:pty:test:watch": "node --import tsx scripts/dev/tui-pty-test-watch.ts",
|
||||||
"tui:pty:test:watch:all": "node --import tsx scripts/dev/tui-pty-test-watch.ts --mode all",
|
"tui:pty:test:watch:all": "node --import tsx scripts/dev/tui-pty-test-watch.ts --mode all",
|
||||||
"tui:pty:test:watch:fake": "node --import tsx scripts/dev/tui-pty-test-watch.ts --mode fake",
|
"tui:pty:test:watch:fake": "node --import tsx scripts/dev/tui-pty-test-watch.ts --mode fake",
|
||||||
"tui:pty:test:watch:local": "node --import tsx scripts/dev/tui-pty-test-watch.ts --mode local",
|
"tui:pty:test:watch:local": "node --import tsx scripts/dev/tui-pty-test-watch.ts --mode local",
|
||||||
"ui:build": "node scripts/ui.js build && node scripts/check-control-ui-precompressed-assets.mjs && node scripts/check-control-ui-performance.mjs",
|
"ui:build": "node scripts/ui.js build && node --import tsx scripts/check-control-ui-precompressed-assets.mts && node --import tsx scripts/check-control-ui-performance.mts",
|
||||||
"ui:dev": "node scripts/ui.js dev",
|
"ui:dev": "node scripts/ui.js dev",
|
||||||
"ui:i18n:baseline": "node --import tsx scripts/control-ui-i18n-verify.ts baseline",
|
"ui:i18n:baseline": "node --import tsx scripts/control-ui-i18n-verify.ts baseline",
|
||||||
"ui:i18n:check": "node --import tsx scripts/control-ui-i18n.ts check",
|
"ui:i18n:check": "node --import tsx scripts/control-ui-i18n.ts check",
|
||||||
@@ -1993,7 +1993,7 @@
|
|||||||
"android:i18n:check": "node --import tsx scripts/android-app-i18n.ts check",
|
"android:i18n:check": "node --import tsx scripts/android-app-i18n.ts check",
|
||||||
"apple:i18n:check": "node --import tsx scripts/apple-app-i18n.ts check",
|
"apple:i18n:check": "node --import tsx scripts/apple-app-i18n.ts check",
|
||||||
"ui:install": "node scripts/ui.js install",
|
"ui:install": "node scripts/ui.js install",
|
||||||
"verify": "node scripts/verify.mjs"
|
"verify": "node --import tsx scripts/verify.mts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@agentclientprotocol/sdk": "1.3.0",
|
"@agentclientprotocol/sdk": "1.3.0",
|
||||||
@@ -2075,6 +2075,7 @@
|
|||||||
"@types/express": "5.0.6",
|
"@types/express": "5.0.6",
|
||||||
"@types/hosted-git-info": "3.0.5",
|
"@types/hosted-git-info": "3.0.5",
|
||||||
"@types/markdown-it": "14.1.2",
|
"@types/markdown-it": "14.1.2",
|
||||||
|
"@types/mdast": "4.0.4",
|
||||||
"@types/ms": "2.1.0",
|
"@types/ms": "2.1.0",
|
||||||
"@types/node": "26.1.2",
|
"@types/node": "26.1.2",
|
||||||
"@types/semver": "7.7.1",
|
"@types/semver": "7.7.1",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { spawn, type SpawnOptionsWithoutStdio } from "node:child_process";
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { resolveNpmRunner } from "../../../scripts/npm-runner.mjs";
|
import { resolveNpmRunner } from "../../../scripts/npm-runner.mts";
|
||||||
import { createNodeEvalArgs } from "../../../src/test-utils/node-process.js";
|
import { createNodeEvalArgs } from "../../../src/test-utils/node-process.js";
|
||||||
import { useAutoCleanupTempDirTracker } from "../../../test/helpers/temp-dir.js";
|
import { useAutoCleanupTempDirTracker } from "../../../test/helpers/temp-dir.js";
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import fs from "node:fs/promises";
|
|||||||
import { createServer, type Server } from "node:http";
|
import { createServer, type Server } from "node:http";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { resolveNpmRunner } from "../../../scripts/npm-runner.mjs";
|
import { resolveNpmRunner } from "../../../scripts/npm-runner.mts";
|
||||||
import { createPnpmRunnerSpawnSpec } from "../../../scripts/pnpm-runner.mjs";
|
import { createPnpmRunnerSpawnSpec } from "../../../scripts/pnpm-runner.mts";
|
||||||
import { type FileLockOptions, withFileLock } from "../../../src/infra/file-lock.js";
|
import { type FileLockOptions, withFileLock } from "../../../src/infra/file-lock.js";
|
||||||
import { getWindowsSystem32ExePath } from "../../../src/infra/windows-install-roots.js";
|
import { getWindowsSystem32ExePath } from "../../../src/infra/windows-install-roots.js";
|
||||||
import { createNodeEvalArgs } from "../../../src/test-utils/node-process.js";
|
import { createNodeEvalArgs } from "../../../src/test-utils/node-process.js";
|
||||||
|
|||||||
Generated
+3
@@ -278,6 +278,9 @@ importers:
|
|||||||
'@types/markdown-it':
|
'@types/markdown-it':
|
||||||
specifier: 14.1.2
|
specifier: 14.1.2
|
||||||
version: 14.1.2
|
version: 14.1.2
|
||||||
|
'@types/mdast':
|
||||||
|
specifier: 4.0.4
|
||||||
|
version: 4.0.4
|
||||||
'@types/ms':
|
'@types/ms':
|
||||||
specifier: 2.1.0
|
specifier: 2.1.0
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ scenario:
|
|||||||
- src/docker-healthcheck.ts
|
- src/docker-healthcheck.ts
|
||||||
- src/gateway/server-http.ts
|
- src/gateway/server-http.ts
|
||||||
- scripts/e2e/compose-setup.sh
|
- scripts/e2e/compose-setup.sh
|
||||||
- scripts/lib/docker-e2e-scenarios.mjs
|
- scripts/lib/docker-e2e-scenarios.mts
|
||||||
- scripts/package-openclaw-for-docker.mjs
|
- scripts/package-openclaw-for-docker.mts
|
||||||
- test/e2e/qa-lab/runtime/docker-artifact-proof.ts
|
- test/e2e/qa-lab/runtime/docker-artifact-proof.ts
|
||||||
execution:
|
execution:
|
||||||
kind: script
|
kind: script
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ scenario:
|
|||||||
- docs/help/testing.md
|
- docs/help/testing.md
|
||||||
codeRefs:
|
codeRefs:
|
||||||
- scripts/e2e/gateway-network-docker.sh
|
- scripts/e2e/gateway-network-docker.sh
|
||||||
- scripts/e2e/lib/gateway-network/client.mjs
|
- scripts/e2e/lib/gateway-network/client.mts
|
||||||
execution:
|
execution:
|
||||||
kind: script
|
kind: script
|
||||||
path: test/e2e/qa-lab/runtime/docker-e2e-lane.ts
|
path: test/e2e/qa-lab/runtime/docker-e2e-lane.ts
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ scenario:
|
|||||||
- docs/help/testing.md
|
- docs/help/testing.md
|
||||||
- docs/concepts/qa-e2e-automation.md
|
- docs/concepts/qa-e2e-automation.md
|
||||||
codeRefs:
|
codeRefs:
|
||||||
- scripts/package-openclaw-for-docker.mjs
|
- scripts/package-openclaw-for-docker.mts
|
||||||
- scripts/e2e/docker-package-install.sh
|
- scripts/e2e/docker-package-install.sh
|
||||||
- scripts/lib/docker-e2e-scenarios.mjs
|
- scripts/lib/docker-e2e-scenarios.mts
|
||||||
- test/e2e/qa-lab/runtime/docker-artifact-proof.ts
|
- test/e2e/qa-lab/runtime/docker-artifact-proof.ts
|
||||||
execution:
|
execution:
|
||||||
kind: script
|
kind: script
|
||||||
|
|||||||
+2
-2
@@ -13,7 +13,7 @@ This directory owns local tooling, script wrappers, and generated-artifact helpe
|
|||||||
|
|
||||||
## Local Heavy-Check Lock
|
## Local Heavy-Check Lock
|
||||||
|
|
||||||
- Respect the local heavy-check lock behavior in `scripts/lib/local-heavy-check-runtime.mjs`.
|
- Respect the local heavy-check lock behavior in `scripts/lib/local-heavy-check-runtime.mts`.
|
||||||
- Do not bypass that lock for real heavy commands just to make a local loop look faster.
|
- Do not bypass that lock for real heavy commands just to make a local loop look faster.
|
||||||
- Metadata-only or explicitly narrow commands may skip the lock when the existing helper logic says that is safe.
|
- Metadata-only or explicitly narrow commands may skip the lock when the existing helper logic says that is safe.
|
||||||
- If you change the lock heuristics, add or update the narrow tests under `test/scripts/`.
|
- If you change the lock heuristics, add or update the narrow tests under `test/scripts/`.
|
||||||
@@ -21,7 +21,7 @@ This directory owns local tooling, script wrappers, and generated-artifact helpe
|
|||||||
## PR Prepare Gates
|
## PR Prepare Gates
|
||||||
|
|
||||||
- `scripts/pr` serializes review, prepare, and merge operations per PR across linked worktrees; `scripts/pr gc` skips active or indeterminate locks. Its subcommand classification table is the canonical wrapper trust boundary: a mismatched local wrapper may run only a classified `advisory` subcommand with `--dev-wrapper` or `OPENCLAW_PR_DEV_WRAPPER=1`; classified `landing` subcommands always require canonical/origin-main wrapper code. A successful command return is the trusted synchronous-completion contract: every PR-state-mutating child must be joined before returning, and such work must never daemonize or explicitly escape both the operation group and lock-notification FD. A failed command auto-releases only while its explicit pre-side-effect validation marker remains active; failures after mutation/tool launch, interruptions, and controller loss stay locked because detached children cannot be disproved. After verifying no child tools remain, use the reported exact-OID `scripts/pr lock-recover` command. Never bypass or delete these refs manually.
|
- `scripts/pr` serializes review, prepare, and merge operations per PR across linked worktrees; `scripts/pr gc` skips active or indeterminate locks. Its subcommand classification table is the canonical wrapper trust boundary: a mismatched local wrapper may run only a classified `advisory` subcommand with `--dev-wrapper` or `OPENCLAW_PR_DEV_WRAPPER=1`; classified `landing` subcommands always require canonical/origin-main wrapper code. A successful command return is the trusted synchronous-completion contract: every PR-state-mutating child must be joined before returning, and such work must never daemonize or explicitly escape both the operation group and lock-notification FD. A failed command auto-releases only while its explicit pre-side-effect validation marker remains active; failures after mutation/tool launch, interruptions, and controller loss stay locked because detached children cannot be disproved. After verifying no child tools remain, use the reported exact-OID `scripts/pr lock-recover` command. Never bypass or delete these refs manually.
|
||||||
- `scripts/pr prepare-gates` holds the heavy-check lock for its whole local gate block (`scripts/pr-gates-lock.mjs`), so concurrent gate runs across `.worktrees` queue as units instead of dying on child lock timeouts or vitest no-output watchdog kills.
|
- `scripts/pr prepare-gates` holds the heavy-check lock for its whole local gate block (`scripts/pr-gates-lock.mts`), so concurrent gate runs across `.worktrees` queue as units instead of dying on child lock timeouts or vitest no-output watchdog kills.
|
||||||
- `OPENCLAW_PR_GATES_REMOTE=testbox` runs the full-suite `pnpm test` gate on a Blacksmith Testbox through `scripts/crabbox-wrapper.mjs` (same delegation as `check:changed`); `pnpm build`/`pnpm check` stay local. The `tbx_` lease id and Actions run URL land in `.local/gates.env` (`REMOTE_GATES_*`) and `.local/prep.md`. Use it for reviewed trusted code when a loaded host makes the local 88-shard run stall-kill; contributor/fork code stays on secretless CI or sanitized AWS unless a maintainer explicitly approves credentialed execution.
|
- `OPENCLAW_PR_GATES_REMOTE=testbox` runs the full-suite `pnpm test` gate on a Blacksmith Testbox through `scripts/crabbox-wrapper.mjs` (same delegation as `check:changed`); `pnpm build`/`pnpm check` stay local. The `tbx_` lease id and Actions run URL land in `.local/gates.env` (`REMOTE_GATES_*`) and `.local/prep.md`. Use it for reviewed trusted code when a loaded host makes the local 88-shard run stall-kill; contributor/fork code stays on secretless CI or sanitized AWS unless a maintainer explicitly approves credentialed execution.
|
||||||
|
|
||||||
## Generated Outputs
|
## Generated Outputs
|
||||||
|
|||||||
+5
-5
@@ -29,10 +29,10 @@ new directory taxonomy.
|
|||||||
|
|
||||||
| Area | Prefer | Notes |
|
| Area | Prefer | Notes |
|
||||||
| --------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
|
| --------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
|
||||||
| Build | `pnpm build` | Runs `scripts/build-all.mjs`; use specific build scripts only when debugging a build stage. |
|
| Build | `pnpm build` | Runs `scripts/build-all.mts`; use specific build scripts only when debugging a build stage. |
|
||||||
| Changed checks | `pnpm changed:lanes --json`, `pnpm check:changed` | Lane classification lives in `scripts/changed-lanes.mjs`; changed-file checks live in `scripts/check-changed.mjs`. |
|
| Changed checks | `pnpm changed:lanes --json`, `pnpm check:changed` | Lane classification lives in `scripts/changed-lanes.mjs`; changed-file checks live in `scripts/check-changed.mjs`. |
|
||||||
| Docs | `pnpm docs:list`, `pnpm docs:check-mdx`, `pnpm docs:check-links` | Backed by `scripts/docs-list.js`, `scripts/check-docs-mdx.mjs`, and `scripts/docs-link-audit.mjs`. |
|
| Docs | `pnpm docs:list`, `pnpm docs:check-mdx`, `pnpm docs:check-links` | Backed by `scripts/docs-list.js`, `scripts/check-docs-mdx.mjs`, and `scripts/docs-link-audit.mjs`. |
|
||||||
| Formatting docs | `pnpm format:docs:check` | Uses `scripts/format-docs.mjs`; use write mode only when intentionally formatting docs. |
|
| Formatting docs | `pnpm format:docs:check` | Uses `scripts/format-docs.mts`; use write mode only when intentionally formatting docs. |
|
||||||
| Lint | `pnpm lint`, `pnpm lint:core`, `pnpm lint:all` | Wrapper scripts keep oxlint behavior aligned with repo config. |
|
| Lint | `pnpm lint`, `pnpm lint:core`, `pnpm lint:all` | Wrapper scripts keep oxlint behavior aligned with repo config. |
|
||||||
| Targeted tests | `pnpm test <path-or-filter>` or `node scripts/run-vitest.mjs <path-or-filter>` | Avoid bare `vitest`; it can start watch mode. |
|
| Targeted tests | `pnpm test <path-or-filter>` or `node scripts/run-vitest.mjs <path-or-filter>` | Avoid bare `vitest`; it can start watch mode. |
|
||||||
| Changed tests | `pnpm test:changed` | Uses the repo's changed-test resolver instead of a broad Vitest run. |
|
| Changed tests | `pnpm test:changed` | Uses the repo's changed-test resolver instead of a broad Vitest run. |
|
||||||
@@ -45,11 +45,11 @@ new directory taxonomy.
|
|||||||
|
|
||||||
## Script Families
|
## Script Families
|
||||||
|
|
||||||
- `check-*.mjs` / `check-*.ts`: guardrails for architecture, docs, package
|
- `check-*.mts` / `check-*.ts` / retained `check-*.mjs`: guardrails for architecture, docs, package
|
||||||
contents, boundaries, workflows, and generated artifacts.
|
contents, boundaries, workflows, and generated artifacts.
|
||||||
- `run-*.mjs`: wrappers around repo runtimes or tools, such as Node, Vitest,
|
- `run-*.mjs` / `run-*.mts`: stable wrappers and typed implementations for Node, Vitest,
|
||||||
oxlint, tsgo, and environment setup.
|
oxlint, tsgo, and environment setup.
|
||||||
- `test-*.mjs` / `test-*.sh` / `test-*.ts`: test planners, Docker lanes, live
|
- `test-*.mts` / retained `test-*.mjs` / `test-*.sh` / `test-*.ts`: test planners, Docker lanes, live
|
||||||
checks, and focused validation helpers.
|
checks, and focused validation helpers.
|
||||||
- `docs-*` and `check-docs-*`: docs listing, link auditing, MDX checks,
|
- `docs-*` and `check-docs-*`: docs listing, link auditing, MDX checks,
|
||||||
spellcheck, sync, and i18n glossary checks.
|
spellcheck, sync, and i18n glossary checks.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import fs from "node:fs";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
import { runAndroidSigningCommandSync } from "./lib/android-release-signing-process.mjs";
|
import { runAndroidSigningCommandSync } from "./lib/android-release-signing-process.mjs";
|
||||||
import { parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs";
|
import { parseFlagArgs, stringFlag } from "./lib/arg-utils.runtime.mjs";
|
||||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||||
const rootDir = resolveRepoRoot(import.meta.url);
|
const rootDir = resolveRepoRoot(import.meta.url);
|
||||||
const defaultManifestPath = path.join(rootDir, "apps", "android", "Config", "ReleaseSigning.json");
|
const defaultManifestPath = path.join(rootDir, "apps", "android", "Config", "ReleaseSigning.json");
|
||||||
@@ -99,8 +99,27 @@ function requireString(value, key) {
|
|||||||
return value.trim();
|
return value.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This release entrypoint runs before dependencies are installed.
|
||||||
|
function asRecord(value) {
|
||||||
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireGradlePropertyNames(value) {
|
||||||
|
if (
|
||||||
|
!Array.isArray(value) ||
|
||||||
|
!value.every((name) => typeof name === "string") ||
|
||||||
|
value.length !== requiredPropertyNames.length ||
|
||||||
|
!requiredPropertyNames.every((name) => value.includes(name))
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`Android release signing manifest must list Gradle properties: ${requiredPropertyNames.join(", ")}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
function readManifest(manifestPath) {
|
function readManifest(manifestPath) {
|
||||||
const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
const parsed = asRecord(JSON.parse(fs.readFileSync(manifestPath, "utf8")));
|
||||||
const manifest = {
|
const manifest = {
|
||||||
signingRepo: requireString(parsed.signingRepo, "signingRepo"),
|
signingRepo: requireString(parsed.signingRepo, "signingRepo"),
|
||||||
signingBranch: requireString(parsed.signingBranch, "signingBranch"),
|
signingBranch: requireString(parsed.signingBranch, "signingBranch"),
|
||||||
@@ -115,18 +134,8 @@ function readManifest(manifestPath) {
|
|||||||
),
|
),
|
||||||
apkCertificateSha256: requireString(parsed.apkCertificateSha256, "apkCertificateSha256"),
|
apkCertificateSha256: requireString(parsed.apkCertificateSha256, "apkCertificateSha256"),
|
||||||
materializedRoot: requireString(parsed.materializedRoot, "materializedRoot"),
|
materializedRoot: requireString(parsed.materializedRoot, "materializedRoot"),
|
||||||
gradlePropertyNames: parsed.gradlePropertyNames,
|
gradlePropertyNames: requireGradlePropertyNames(parsed.gradlePropertyNames),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
|
||||||
!Array.isArray(manifest.gradlePropertyNames) ||
|
|
||||||
manifest.gradlePropertyNames.length !== requiredPropertyNames.length ||
|
|
||||||
!requiredPropertyNames.every((name) => manifest.gradlePropertyNames.includes(name))
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
`Android release signing manifest must list Gradle properties: ${requiredPropertyNames.join(", ")}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!/^[a-f0-9]{64}$/u.test(manifest.apkCertificateSha256)) {
|
if (!/^[a-f0-9]{64}$/u.test(manifest.apkCertificateSha256)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Android release signing manifest apkCertificateSha256 must be 64 lowercase hex digits.",
|
"Android release signing manifest apkCertificateSha256 must be 64 lowercase hex digits.",
|
||||||
@@ -472,6 +481,6 @@ try {
|
|||||||
throw new Error(`Unknown mode: ${options.mode}`);
|
throw new Error(`Unknown mode: ${options.mode}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
process.stderr.write(`${error.message}\n`);
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
export function describeSeamKinds(relativePath: unknown, source: unknown): string[];
|
|
||||||
export function determineSeamTestStatus(
|
|
||||||
seamKinds: unknown,
|
|
||||||
relatedTestMatches: unknown,
|
|
||||||
): {
|
|
||||||
status: string;
|
|
||||||
reason: string;
|
|
||||||
};
|
|
||||||
export function main(argv?: string[]): Promise<void>;
|
|
||||||
export const HELP_TEXT: "Usage: node scripts/audit-seams.mjs [--help]\n\nAudit repo seam inventory and emit JSON to stdout.\n\nSections:\n duplicatedSeamFamilies Plugin SDK seam families imported from multiple production files\n overlapFiles Production files that touch multiple seam families\n optionalClusterStaticLeaks Optional extension/plugin clusters referenced from the static graph\n missingPackages Workspace packages whose deps are not mirrored at the root\n seamTestInventory High-signal seam candidates with nearby-test gap signals,\n including cron orchestration seams for agent handoff,\n outbound/media delivery, heartbeat/followup handoff,\n and scheduler state crossings, plus subagent seams\n for spawn/session handoff, announce delivery,\n lifecycle registry, cleanup, and parent streaming\n\nNotes:\n - Output is JSON only.\n - For clean redirected JSON through package scripts, prefer:\n pnpm --silent audit:seams > seam-inventory.json\n";
|
|
||||||
@@ -13,15 +13,47 @@ import { visitModuleSpecifiers } from "./lib/guard-inventory-utils.mjs";
|
|||||||
import { optionalBundledClusterSet } from "./lib/optional-bundled-clusters.mjs";
|
import { optionalBundledClusterSet } from "./lib/optional-bundled-clusters.mjs";
|
||||||
import { escapeRegExp } from "./lib/regexp.mjs";
|
import { escapeRegExp } from "./lib/regexp.mjs";
|
||||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||||
import { toLine } from "./lib/ts-guard-utils.mjs";
|
import { toLine } from "./lib/ts-guard-utils.mts";
|
||||||
|
|
||||||
|
type ImportEntry = {
|
||||||
|
family: string;
|
||||||
|
file: string;
|
||||||
|
kind: string;
|
||||||
|
line: number;
|
||||||
|
resolvedPath: string;
|
||||||
|
specifier: string;
|
||||||
|
};
|
||||||
|
type OptionalClusterImportEntry = Omit<ImportEntry, "family"> & { cluster: string };
|
||||||
|
type ModuleSpecifierVisit = {
|
||||||
|
kind: string;
|
||||||
|
specifierNode: ts.Node;
|
||||||
|
specifier: string;
|
||||||
|
};
|
||||||
|
const MATCH_QUALITY_RANK = {
|
||||||
|
"exact-stem": 0,
|
||||||
|
"path-nearby": 1,
|
||||||
|
"direct-import": 2,
|
||||||
|
"dir-token": 3,
|
||||||
|
} as const satisfies Record<string, number>;
|
||||||
|
type MatchQuality = keyof typeof MATCH_QUALITY_RANK;
|
||||||
|
type RelatedTestMatch = { file: string; matchQuality: MatchQuality };
|
||||||
|
type PackageJson = {
|
||||||
|
dependencies?: Record<string, string>;
|
||||||
|
optionalDependencies?: Record<string, string>;
|
||||||
|
devDependencies?: Record<string, string>;
|
||||||
|
name?: string;
|
||||||
|
private?: boolean;
|
||||||
|
openclaw?: { install?: { npmSpec?: string } };
|
||||||
|
};
|
||||||
|
|
||||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||||
const srcRoot = path.join(repoRoot, "src");
|
const srcRoot = path.join(repoRoot, "src");
|
||||||
const extensionsRoot = path.join(repoRoot, BUNDLED_PLUGIN_ROOT_DIR);
|
const extensionsRoot = path.join(repoRoot, BUNDLED_PLUGIN_ROOT_DIR);
|
||||||
const testRoot = path.join(repoRoot, "test");
|
const testRoot = path.join(repoRoot, "test");
|
||||||
const workspacePackagePaths = ["ui/package.json"];
|
const workspacePackagePaths = ["ui/package.json"];
|
||||||
const MAX_SCAN_BYTES = 2 * 1024 * 1024;
|
const MAX_SCAN_BYTES = 2 * 1024 * 1024;
|
||||||
const compareStrings = (left, right) => left.localeCompare(right);
|
const compareStrings = (left: string, right: string) => left.localeCompare(right);
|
||||||
export const HELP_TEXT = `Usage: node scripts/audit-seams.mjs [--help]
|
export const HELP_TEXT = `Usage: node --import tsx scripts/audit-seams.mts [--help]
|
||||||
|
|
||||||
Audit repo seam inventory and emit JSON to stdout.
|
Audit repo seam inventory and emit JSON to stdout.
|
||||||
|
|
||||||
@@ -52,11 +84,11 @@ async function collectWorkspacePackagePaths() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePath(filePath) {
|
function normalizePath(filePath: string) {
|
||||||
return path.relative(repoRoot, filePath).split(path.sep).join("/");
|
return path.relative(repoRoot, filePath).split(path.sep).join("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readScannableText(filePath, maxBytes = MAX_SCAN_BYTES) {
|
async function readScannableText(filePath: string, maxBytes = MAX_SCAN_BYTES) {
|
||||||
const stat = await fs.stat(filePath);
|
const stat = await fs.stat(filePath);
|
||||||
if (stat.size <= maxBytes) {
|
if (stat.size <= maxBytes) {
|
||||||
return fs.readFile(filePath, "utf8");
|
return fs.readFile(filePath, "utf8");
|
||||||
@@ -71,7 +103,7 @@ async function readScannableText(filePath, maxBytes = MAX_SCAN_BYTES) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function redactNpmSpec(npmSpec) {
|
function redactNpmSpec(npmSpec: unknown) {
|
||||||
if (typeof npmSpec !== "string") {
|
if (typeof npmSpec !== "string") {
|
||||||
return npmSpec ?? null;
|
return npmSpec ?? null;
|
||||||
}
|
}
|
||||||
@@ -80,11 +112,11 @@ function redactNpmSpec(npmSpec) {
|
|||||||
.replace(/(https?:\/\/)([^/\s:@]+)@/gi, "$1***@");
|
.replace(/(https?:\/\/)([^/\s:@]+)@/gi, "$1***@");
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCodeFile(fileName) {
|
function isCodeFile(fileName: string) {
|
||||||
return /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(fileName);
|
return /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTestLikePath(relativePath) {
|
function isTestLikePath(relativePath: string) {
|
||||||
return (
|
return (
|
||||||
/(^|\/)(__tests__|fixtures|test-utils|test-fixtures)\//.test(relativePath) ||
|
/(^|\/)(__tests__|fixtures|test-utils|test-fixtures)\//.test(relativePath) ||
|
||||||
/(?:^|\/)[^/]*(?:[.-](?:test|spec))(?:[.-][^/]+)?\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(
|
/(?:^|\/)[^/]*(?:[.-](?:test|spec))(?:[.-][^/]+)?\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(
|
||||||
@@ -93,13 +125,13 @@ function isTestLikePath(relativePath) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isProductionLikeFile(relativePath) {
|
function isProductionLikeFile(relativePath: string) {
|
||||||
return !isTestLikePath(relativePath);
|
return !isTestLikePath(relativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function walkCodeFiles(rootDir) {
|
async function walkCodeFiles(rootDir: string) {
|
||||||
const out = [];
|
const out: string[] = [];
|
||||||
async function walk(dir) {
|
async function walk(dir: string): Promise<void> {
|
||||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (entry.name === "dist" || entry.name === "node_modules") {
|
if (entry.name === "dist" || entry.name === "node_modules") {
|
||||||
@@ -124,11 +156,11 @@ async function walkCodeFiles(rootDir) {
|
|||||||
return out.toSorted((left, right) => normalizePath(left).localeCompare(normalizePath(right)));
|
return out.toSorted((left, right) => normalizePath(left).localeCompare(normalizePath(right)));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function walkAllCodeFiles(rootDir, options = {}) {
|
async function walkAllCodeFiles(rootDir: string, options: { includeTests?: boolean } = {}) {
|
||||||
const out = [];
|
const out: string[] = [];
|
||||||
const includeTests = options.includeTests === true;
|
const includeTests = options.includeTests === true;
|
||||||
|
|
||||||
async function walk(dir) {
|
async function walk(dir: string): Promise<void> {
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = await fs.readdir(dir, { withFileTypes: true });
|
entries = await fs.readdir(dir, { withFileTypes: true });
|
||||||
@@ -159,31 +191,31 @@ async function walkAllCodeFiles(rootDir, options = {}) {
|
|||||||
return out.toSorted((left, right) => normalizePath(left).localeCompare(normalizePath(right)));
|
return out.toSorted((left, right) => normalizePath(left).localeCompare(normalizePath(right)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveRelativeSpecifier(specifier, importerFile) {
|
function resolveRelativeSpecifier(specifier: string, importerFile: string) {
|
||||||
if (!specifier.startsWith(".")) {
|
if (!specifier.startsWith(".")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return normalizePath(path.resolve(path.dirname(importerFile), specifier));
|
return normalizePath(path.resolve(path.dirname(importerFile), specifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePluginSdkFamily(resolvedPath) {
|
function normalizePluginSdkFamily(resolvedPath: string) {
|
||||||
const relative = resolvedPath.replace(/^src\/plugin-sdk\//, "");
|
const relative = resolvedPath.replace(/^src\/plugin-sdk\//, "");
|
||||||
return relative.replace(/\.(m|c)?[jt]sx?$/, "");
|
return relative.replace(/\.(m|c)?[jt]sx?$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveOptionalClusterFromPath(resolvedPath) {
|
function resolveOptionalClusterFromPath(resolvedPath: string) {
|
||||||
if (resolvedPath.startsWith(BUNDLED_PLUGIN_PATH_PREFIX)) {
|
if (resolvedPath.startsWith(BUNDLED_PLUGIN_PATH_PREFIX)) {
|
||||||
const cluster = resolvedPath.split("/")[1];
|
const [, cluster] = resolvedPath.split("/");
|
||||||
return optionalBundledClusterSet.has(cluster) ? cluster : null;
|
return cluster && optionalBundledClusterSet.has(cluster) ? cluster : null;
|
||||||
}
|
}
|
||||||
if (resolvedPath.startsWith("src/plugin-sdk/")) {
|
if (resolvedPath.startsWith("src/plugin-sdk/")) {
|
||||||
const cluster = normalizePluginSdkFamily(resolvedPath).split("/")[0];
|
const [cluster] = normalizePluginSdkFamily(resolvedPath).split("/");
|
||||||
return optionalBundledClusterSet.has(cluster) ? cluster : null;
|
return cluster && optionalBundledClusterSet.has(cluster) ? cluster : null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function compareImports(left, right) {
|
function compareImports(left: ImportEntry, right: ImportEntry) {
|
||||||
return (
|
return (
|
||||||
left.family.localeCompare(right.family) ||
|
left.family.localeCompare(right.family) ||
|
||||||
left.file.localeCompare(right.file) ||
|
left.file.localeCompare(right.file) ||
|
||||||
@@ -193,10 +225,10 @@ function compareImports(left, right) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectPluginSdkImports(filePath, sourceFile) {
|
function collectPluginSdkImports(filePath: string, sourceFile: ts.SourceFile): ImportEntry[] {
|
||||||
const entries = [];
|
const entries: ImportEntry[] = [];
|
||||||
|
|
||||||
function push(kind, specifierNode, specifier) {
|
function push(kind: string, specifierNode: ts.Node, specifier: string) {
|
||||||
const resolvedPath = resolveRelativeSpecifier(specifier, filePath);
|
const resolvedPath = resolveRelativeSpecifier(specifier, filePath);
|
||||||
if (!resolvedPath?.startsWith("src/plugin-sdk/")) {
|
if (!resolvedPath?.startsWith("src/plugin-sdk/")) {
|
||||||
return;
|
return;
|
||||||
@@ -211,15 +243,15 @@ function collectPluginSdkImports(filePath, sourceFile) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
visitModuleSpecifiers(ts, sourceFile, ({ kind, specifierNode, specifier }) => {
|
const visit = ({ kind, specifierNode, specifier }: ModuleSpecifierVisit) =>
|
||||||
push(kind, specifierNode, specifier);
|
push(kind, specifierNode, specifier);
|
||||||
});
|
visitModuleSpecifiers(ts, sourceFile, visit);
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function collectCorePluginSdkImports() {
|
async function collectCorePluginSdkImports() {
|
||||||
const files = await walkCodeFiles(srcRoot);
|
const files = await walkCodeFiles(srcRoot);
|
||||||
const inventory = [];
|
const inventory: ImportEntry[] = [];
|
||||||
for (const filePath of files) {
|
for (const filePath of files) {
|
||||||
if (normalizePath(filePath).startsWith("src/plugin-sdk/")) {
|
if (normalizePath(filePath).startsWith("src/plugin-sdk/")) {
|
||||||
continue;
|
continue;
|
||||||
@@ -231,10 +263,13 @@ async function collectCorePluginSdkImports() {
|
|||||||
return inventory.toSorted(compareImports);
|
return inventory.toSorted(compareImports);
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectOptionalClusterStaticImports(filePath, sourceFile) {
|
function collectOptionalClusterStaticImports(
|
||||||
const entries = [];
|
filePath: string,
|
||||||
|
sourceFile: ts.SourceFile,
|
||||||
|
): OptionalClusterImportEntry[] {
|
||||||
|
const entries: OptionalClusterImportEntry[] = [];
|
||||||
|
|
||||||
function push(kind, specifierNode, specifier) {
|
function push(kind: string, specifierNode: ts.Node, specifier: string) {
|
||||||
if (!specifier.startsWith(".")) {
|
if (!specifier.startsWith(".")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -256,17 +291,18 @@ function collectOptionalClusterStaticImports(filePath, sourceFile) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
visitModuleSpecifiers(ts, sourceFile, ({ kind, specifierNode, specifier }) => {
|
const visit = ({ kind, specifierNode, specifier }: ModuleSpecifierVisit) => {
|
||||||
if (kind !== "dynamic-import") {
|
if (kind !== "dynamic-import") {
|
||||||
push(kind, specifierNode, specifier);
|
push(kind, specifierNode, specifier);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
visitModuleSpecifiers(ts, sourceFile, visit);
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function collectOptionalClusterStaticLeaks() {
|
async function collectOptionalClusterStaticLeaks() {
|
||||||
const files = await walkCodeFiles(srcRoot);
|
const files = await walkCodeFiles(srcRoot);
|
||||||
const inventory = [];
|
const inventory: OptionalClusterImportEntry[] = [];
|
||||||
for (const filePath of files) {
|
for (const filePath of files) {
|
||||||
const relativePath = normalizePath(filePath);
|
const relativePath = normalizePath(filePath);
|
||||||
if (relativePath.startsWith("src/plugin-sdk/")) {
|
if (relativePath.startsWith("src/plugin-sdk/")) {
|
||||||
@@ -287,8 +323,8 @@ async function collectOptionalClusterStaticLeaks() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildDuplicatedSeamFamilies(inventory) {
|
function buildDuplicatedSeamFamilies(inventory: ImportEntry[]) {
|
||||||
const grouped = new Map();
|
const grouped = new Map<string, ImportEntry[]>();
|
||||||
for (const entry of inventory) {
|
for (const entry of inventory) {
|
||||||
const bucket = grouped.get(entry.family) ?? [];
|
const bucket = grouped.get(entry.family) ?? [];
|
||||||
bucket.push(entry);
|
bucket.push(entry);
|
||||||
@@ -307,7 +343,7 @@ function buildDuplicatedSeamFamilies(inventory) {
|
|||||||
files,
|
files,
|
||||||
imports: entries,
|
imports: entries,
|
||||||
},
|
},
|
||||||
];
|
] as const;
|
||||||
})
|
})
|
||||||
.filter(([, value]) => value.files.length > 1)
|
.filter(([, value]) => value.files.length > 1)
|
||||||
.toSorted((left, right) => {
|
.toSorted((left, right) => {
|
||||||
@@ -322,8 +358,8 @@ function buildDuplicatedSeamFamilies(inventory) {
|
|||||||
return duplicated;
|
return duplicated;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildOverlapFiles(inventory) {
|
function buildOverlapFiles(inventory: ImportEntry[]) {
|
||||||
const byFile = new Map();
|
const byFile = new Map<string, ImportEntry[]>();
|
||||||
for (const entry of inventory) {
|
for (const entry of inventory) {
|
||||||
const bucket = byFile.get(entry.file) ?? [];
|
const bucket = byFile.get(entry.file) ?? [];
|
||||||
bucket.push(entry);
|
bucket.push(entry);
|
||||||
@@ -349,8 +385,8 @@ function buildOverlapFiles(inventory) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildOptionalClusterStaticLeaks(inventory) {
|
function buildOptionalClusterStaticLeaks(inventory: OptionalClusterImportEntry[]) {
|
||||||
const grouped = new Map();
|
const grouped = new Map<string, OptionalClusterImportEntry[]>();
|
||||||
for (const entry of inventory) {
|
for (const entry of inventory) {
|
||||||
const bucket = grouped.get(entry.cluster) ?? [];
|
const bucket = grouped.get(entry.cluster) ?? [];
|
||||||
bucket.push(entry);
|
bucket.push(entry);
|
||||||
@@ -359,21 +395,24 @@ function buildOptionalClusterStaticLeaks(inventory) {
|
|||||||
|
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
[...grouped.entries()]
|
[...grouped.entries()]
|
||||||
.map(([cluster, entries]) => [
|
.map(
|
||||||
cluster,
|
([cluster, entries]) =>
|
||||||
{
|
[
|
||||||
count: entries.length,
|
cluster,
|
||||||
files: [...new Set(entries.map((entry) => entry.file))].toSorted(compareStrings),
|
{
|
||||||
imports: entries,
|
count: entries.length,
|
||||||
},
|
files: [...new Set(entries.map((entry) => entry.file))].toSorted(compareStrings),
|
||||||
])
|
imports: entries,
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
)
|
||||||
.toSorted((left, right) => {
|
.toSorted((left, right) => {
|
||||||
return right[1].count - left[1].count || left[0].localeCompare(right[0]);
|
return right[1].count - left[1].count || left[0].localeCompare(right[0]);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function packageClusterMeta(relativePackagePath) {
|
function packageClusterMeta(relativePackagePath: string) {
|
||||||
if (relativePackagePath === "ui/package.json") {
|
if (relativePackagePath === "ui/package.json") {
|
||||||
return {
|
return {
|
||||||
cluster: "ui",
|
cluster: "ui",
|
||||||
@@ -382,7 +421,7 @@ function packageClusterMeta(relativePackagePath) {
|
|||||||
reachability: "workspace-ui",
|
reachability: "workspace-ui",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const cluster = relativePackagePath.split("/")[1];
|
const cluster = path.basename(path.dirname(relativePackagePath));
|
||||||
return {
|
return {
|
||||||
cluster,
|
cluster,
|
||||||
packageName: null,
|
packageName: null,
|
||||||
@@ -393,7 +432,11 @@ function packageClusterMeta(relativePackagePath) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function classifyMissingPackageCluster(params) {
|
function classifyMissingPackageCluster(params: {
|
||||||
|
cluster: string;
|
||||||
|
pluginSdkEntries: string[];
|
||||||
|
hasStaticLeak: boolean;
|
||||||
|
}) {
|
||||||
if (params.hasStaticLeak) {
|
if (params.hasStaticLeak) {
|
||||||
return {
|
return {
|
||||||
decision: "required",
|
decision: "required",
|
||||||
@@ -429,8 +472,10 @@ function classifyMissingPackageCluster(params) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildMissingPackages(params = {}) {
|
async function buildMissingPackages(params: { staticLeakClusters?: Set<string> } = {}) {
|
||||||
const rootPackage = JSON.parse(await fs.readFile(path.join(repoRoot, "package.json"), "utf8"));
|
const rootPackage: PackageJson = JSON.parse(
|
||||||
|
await fs.readFile(path.join(repoRoot, "package.json"), "utf8"),
|
||||||
|
);
|
||||||
const rootDeps = new Set([
|
const rootDeps = new Set([
|
||||||
...Object.keys(rootPackage.dependencies ?? {}),
|
...Object.keys(rootPackage.dependencies ?? {}),
|
||||||
...Object.keys(rootPackage.optionalDependencies ?? {}),
|
...Object.keys(rootPackage.optionalDependencies ?? {}),
|
||||||
@@ -438,12 +483,15 @@ async function buildMissingPackages(params = {}) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const pluginSdkEntrySources = await walkCodeFiles(path.join(repoRoot, "src", "plugin-sdk"));
|
const pluginSdkEntrySources = await walkCodeFiles(path.join(repoRoot, "src", "plugin-sdk"));
|
||||||
const pluginSdkReachability = new Map();
|
const pluginSdkReachability = new Map<string, Set<string>>();
|
||||||
for (const filePath of pluginSdkEntrySources) {
|
for (const filePath of pluginSdkEntrySources) {
|
||||||
const source = await fs.readFile(filePath, "utf8");
|
const source = await fs.readFile(filePath, "utf8");
|
||||||
const matches = [...source.matchAll(/from\s+"(\.\.\/\.\.\/extensions\/([^/]+)\/[^"]+)"/g)];
|
const matches = [...source.matchAll(/from\s+"(\.\.\/\.\.\/extensions\/([^/]+)\/[^"]+)"/g)];
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
const cluster = match[2];
|
const cluster = match[2];
|
||||||
|
if (!cluster) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const bucket = pluginSdkReachability.get(cluster) ?? new Set();
|
const bucket = pluginSdkReachability.get(cluster) ?? new Set();
|
||||||
bucket.add(normalizePath(filePath));
|
bucket.add(normalizePath(filePath));
|
||||||
pluginSdkReachability.set(cluster, bucket);
|
pluginSdkReachability.set(cluster, bucket);
|
||||||
@@ -453,7 +501,7 @@ async function buildMissingPackages(params = {}) {
|
|||||||
const output = [];
|
const output = [];
|
||||||
for (const relativePackagePath of workspacePackagePaths.toSorted(compareStrings)) {
|
for (const relativePackagePath of workspacePackagePaths.toSorted(compareStrings)) {
|
||||||
const packagePath = path.join(repoRoot, relativePackagePath);
|
const packagePath = path.join(repoRoot, relativePackagePath);
|
||||||
let pkg;
|
let pkg: PackageJson;
|
||||||
try {
|
try {
|
||||||
pkg = JSON.parse(await fs.readFile(packagePath, "utf8"));
|
pkg = JSON.parse(await fs.readFile(packagePath, "utf8"));
|
||||||
} catch {
|
} catch {
|
||||||
@@ -493,33 +541,33 @@ async function buildMissingPackages(params = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function stemFromRelativePath(relativePath) {
|
function stemFromRelativePath(relativePath: string) {
|
||||||
return relativePath.replace(/\.(m|c)?[jt]sx?$/, "");
|
return relativePath.replace(/\.(m|c)?[jt]sx?$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function splitNameTokens(name) {
|
function splitNameTokens(name: string) {
|
||||||
return name
|
return name
|
||||||
.split(/[^a-zA-Z0-9]+/)
|
.split(/[^a-zA-Z0-9]+/)
|
||||||
.map((token) => token.trim().toLowerCase())
|
.map((token) => token.trim().toLowerCase())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasImportSource(source, specifier) {
|
function hasImportSource(source: string, specifier: string) {
|
||||||
const escaped = escapeRegExp(specifier);
|
const escaped = escapeRegExp(specifier);
|
||||||
return new RegExp(`from\\s+["']${escaped}["']|import\\s*\\(\\s*["']${escaped}["']\\s*\\)`).test(
|
return new RegExp(`from\\s+["']${escaped}["']|import\\s*\\(\\s*["']${escaped}["']\\s*\\)`).test(
|
||||||
source,
|
source,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasAnyImportSource(source, specifiers) {
|
function hasAnyImportSource(source: string, specifiers: string[]) {
|
||||||
return specifiers.some((specifier) => hasImportSource(source, specifier));
|
return specifiers.some((specifier) => hasImportSource(source, specifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCronProductionPath(relativePath) {
|
function isCronProductionPath(relativePath: string) {
|
||||||
return relativePath.startsWith("src/cron/") && isProductionLikeFile(relativePath);
|
return relativePath.startsWith("src/cron/") && isProductionLikeFile(relativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSubagentProductionPath(relativePath) {
|
function isSubagentProductionPath(relativePath: string) {
|
||||||
return (
|
return (
|
||||||
(relativePath.startsWith("src/agents/") || relativePath.startsWith("src/cron/")) &&
|
(relativePath.startsWith("src/agents/") || relativePath.startsWith("src/cron/")) &&
|
||||||
isProductionLikeFile(relativePath) &&
|
isProductionLikeFile(relativePath) &&
|
||||||
@@ -529,7 +577,7 @@ function isSubagentProductionPath(relativePath) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function describeCronSeamKinds(relativePath, source) {
|
function describeCronSeamKinds(relativePath: string, source: string) {
|
||||||
if (!isCronProductionPath(relativePath)) {
|
if (!isCronProductionPath(relativePath)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -630,7 +678,7 @@ function describeCronSeamKinds(relativePath, source) {
|
|||||||
return seamKinds;
|
return seamKinds;
|
||||||
}
|
}
|
||||||
|
|
||||||
function describeSubagentSeamKinds(relativePath, source) {
|
function describeSubagentSeamKinds(relativePath: string, source: string) {
|
||||||
if (!isSubagentProductionPath(relativePath)) {
|
if (!isSubagentProductionPath(relativePath)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -726,7 +774,7 @@ function describeSubagentSeamKinds(relativePath, source) {
|
|||||||
return seamKinds;
|
return seamKinds;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function describeSeamKinds(relativePath, source) {
|
export function describeSeamKinds(relativePath: string, source: string) {
|
||||||
const seamKinds = [];
|
const seamKinds = [];
|
||||||
const isReplyDeliveryPath =
|
const isReplyDeliveryPath =
|
||||||
/reply-delivery|reply-dispatcher|deliver-reply|reply\/.*delivery|monitor\/(?:replies|deliver|native-command)|outbound\/deliver|outbound\/message/.test(
|
/reply-delivery|reply-dispatcher|deliver-reply|reply\/.*delivery|monitor\/(?:replies|deliver|native-command)|outbound\/deliver|outbound\/message/.test(
|
||||||
@@ -770,7 +818,7 @@ export function describeSeamKinds(relativePath, source) {
|
|||||||
return [...new Set(seamKinds)].toSorted(compareStrings);
|
return [...new Set(seamKinds)].toSorted(compareStrings);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildTestIndex(testFiles) {
|
async function buildTestIndex(testFiles: string[]) {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
testFiles.map(async (filePath) => {
|
testFiles.map(async (filePath) => {
|
||||||
const relativePath = normalizePath(filePath);
|
const relativePath = normalizePath(filePath);
|
||||||
@@ -790,7 +838,9 @@ async function buildTestIndex(testFiles) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasExecutableImportReference(source, importPath) {
|
type TestIndexEntry = Awaited<ReturnType<typeof buildTestIndex>>[number];
|
||||||
|
|
||||||
|
function hasExecutableImportReference(source: string, importPath: string) {
|
||||||
const escapedImportPath = escapeRegExp(importPath);
|
const escapedImportPath = escapeRegExp(importPath);
|
||||||
const suffix = String.raw`(?:\.[^"'\\\`]+)?`;
|
const suffix = String.raw`(?:\.[^"'\\\`]+)?`;
|
||||||
const patterns = [
|
const patterns = [
|
||||||
@@ -802,7 +852,7 @@ function hasExecutableImportReference(source, importPath) {
|
|||||||
return patterns.some((pattern) => pattern.test(source));
|
return patterns.some((pattern) => pattern.test(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasModuleMockReference(source, importPath) {
|
function hasModuleMockReference(source: string, importPath: string) {
|
||||||
const escapedImportPath = escapeRegExp(importPath);
|
const escapedImportPath = escapeRegExp(importPath);
|
||||||
const suffix = String.raw`(?:\.[^"'\\\`]+)?`;
|
const suffix = String.raw`(?:\.[^"'\\\`]+)?`;
|
||||||
const patterns = [
|
const patterns = [
|
||||||
@@ -812,29 +862,16 @@ function hasModuleMockReference(source, importPath) {
|
|||||||
return patterns.some((pattern) => pattern.test(source));
|
return patterns.some((pattern) => pattern.test(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchQualityRank(quality) {
|
const matchQualityRank = (quality: MatchQuality) => MATCH_QUALITY_RANK[quality] ?? 4;
|
||||||
switch (quality) {
|
|
||||||
case "exact-stem":
|
|
||||||
return 0;
|
|
||||||
case "path-nearby":
|
|
||||||
return 1;
|
|
||||||
case "direct-import":
|
|
||||||
return 2;
|
|
||||||
case "dir-token":
|
|
||||||
return 3;
|
|
||||||
default:
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function findRelatedTests(relativePath, testIndex) {
|
function findRelatedTests(relativePath: string, testIndex: TestIndexEntry[]): RelatedTestMatch[] {
|
||||||
const stem = stemFromRelativePath(relativePath);
|
const stem = stemFromRelativePath(relativePath);
|
||||||
const baseName = path.basename(stem);
|
const baseName = path.basename(stem);
|
||||||
const dirName = path.dirname(relativePath);
|
const dirName = path.dirname(relativePath);
|
||||||
const normalizedDir = dirName.split(path.sep).join("/");
|
const normalizedDir = dirName.split(path.sep).join("/");
|
||||||
const baseTokens = new Set(splitNameTokens(baseName).filter((token) => token.length >= 7));
|
const baseTokens = new Set(splitNameTokens(baseName).filter((token) => token.length >= 7));
|
||||||
|
|
||||||
const matches = testIndex.flatMap((entry) => {
|
const matches: RelatedTestMatch[] = testIndex.flatMap((entry): RelatedTestMatch[] => {
|
||||||
if (entry.stem === stem) {
|
if (entry.stem === stem) {
|
||||||
return [{ file: entry.relativePath, matchQuality: "exact-stem" }];
|
return [{ file: entry.relativePath, matchQuality: "exact-stem" }];
|
||||||
}
|
}
|
||||||
@@ -864,7 +901,7 @@ function findRelatedTests(relativePath, testIndex) {
|
|||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
const byFile = new Map();
|
const byFile = new Map<string, RelatedTestMatch>();
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
const existing = byFile.get(match.file);
|
const existing = byFile.get(match.file);
|
||||||
if (
|
if (
|
||||||
@@ -883,7 +920,10 @@ function findRelatedTests(relativePath, testIndex) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function determineSeamTestStatus(seamKinds, relatedTestMatches) {
|
export function determineSeamTestStatus(
|
||||||
|
seamKinds: string[],
|
||||||
|
relatedTestMatches: RelatedTestMatch[],
|
||||||
|
) {
|
||||||
if (relatedTestMatches.length === 0) {
|
if (relatedTestMatches.length === 0) {
|
||||||
return {
|
return {
|
||||||
status: "gap",
|
status: "gap",
|
||||||
@@ -962,7 +1002,7 @@ async function buildSeamTestInventory() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function main(argv = process.argv.slice(2)) {
|
export async function main(argv: string[] = process.argv.slice(2)) {
|
||||||
const args = new Set(argv);
|
const args = new Set(argv);
|
||||||
if (args.has("--help") || args.has("-h")) {
|
if (args.has("--help") || args.has("-h")) {
|
||||||
process.stdout.write(`${HELP_TEXT}\n`);
|
process.stdout.write(`${HELP_TEXT}\n`);
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
export function parseArgs(argv: unknown): {
|
|
||||||
maxWorkers?: unknown;
|
|
||||||
cwd: string;
|
|
||||||
mode: unknown;
|
|
||||||
ref: unknown;
|
|
||||||
rss: unknown;
|
|
||||||
};
|
|
||||||
export function parseMaxRssBytes(output: unknown): number | null;
|
|
||||||
export function formatRss(valueBytes: unknown): string;
|
|
||||||
export function resolveBenchRssResult({
|
|
||||||
label,
|
|
||||||
output,
|
|
||||||
rss,
|
|
||||||
status,
|
|
||||||
}: {
|
|
||||||
label: unknown;
|
|
||||||
output: unknown;
|
|
||||||
rss: unknown;
|
|
||||||
status: unknown;
|
|
||||||
}): {
|
|
||||||
maxRssBytes: number | null;
|
|
||||||
output: unknown;
|
|
||||||
status: unknown;
|
|
||||||
};
|
|
||||||
@@ -2,10 +2,25 @@
|
|||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { pathToFileURL } from "node:url";
|
import { pathToFileURL } from "node:url";
|
||||||
import { parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs";
|
import { parseFlagArgs, stringFlag } from "./lib/arg-utils.mts";
|
||||||
import { formatMs } from "./lib/vitest-report-cli-utils.mjs";
|
import { formatMs } from "./lib/vitest-report-cli-utils.mts";
|
||||||
|
|
||||||
function parsePositiveInteger(raw, label) {
|
type BenchOptions = {
|
||||||
|
cwd: string;
|
||||||
|
maxWorkers?: number;
|
||||||
|
mode: "ref" | "worktree";
|
||||||
|
ref: string;
|
||||||
|
rss: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BenchResult = ReturnType<typeof runBenchCommand>;
|
||||||
|
|
||||||
|
type BenchCommandParams = Pick<BenchOptions, "cwd" | "maxWorkers" | "rss"> & {
|
||||||
|
command: [string, ...string[]];
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function parsePositiveInteger(raw: string | undefined, label: string) {
|
||||||
const value = raw?.trim();
|
const value = raw?.trim();
|
||||||
if (!value) {
|
if (!value) {
|
||||||
throw new Error(`${label} requires a value`);
|
throw new Error(`${label} requires a value`);
|
||||||
@@ -20,9 +35,9 @@ function parsePositiveInteger(raw, label) {
|
|||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
function positiveIntegerFlag(flag, key) {
|
function positiveIntegerFlag(flag: string, key: "maxWorkers") {
|
||||||
return {
|
return {
|
||||||
consume(argv, index) {
|
consume(argv: readonly string[], index: number) {
|
||||||
if (argv[index] !== flag) {
|
if (argv[index] !== flag) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -34,7 +49,7 @@ function positiveIntegerFlag(flag, key) {
|
|||||||
flag,
|
flag,
|
||||||
nextIndex: index + 1,
|
nextIndex: index + 1,
|
||||||
repeatable: false,
|
repeatable: false,
|
||||||
apply(target) {
|
apply(target: BenchOptions) {
|
||||||
target[key] = parsePositiveInteger(rawValue, flag);
|
target[key] = parsePositiveInteger(rawValue, flag);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -42,7 +57,7 @@ function positiveIntegerFlag(flag, key) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseArgs(argv) {
|
export function parseArgs(argv: string[]): BenchOptions {
|
||||||
const args = parseFlagArgs(
|
const args = parseFlagArgs(
|
||||||
argv,
|
argv,
|
||||||
{
|
{
|
||||||
@@ -57,7 +72,7 @@ export function parseArgs(argv) {
|
|||||||
positiveIntegerFlag("--max-workers", "maxWorkers"),
|
positiveIntegerFlag("--max-workers", "maxWorkers"),
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
onUnhandledArg(arg, target) {
|
onUnhandledArg(arg: string, target: BenchOptions) {
|
||||||
if (arg === "--no-rss") {
|
if (arg === "--no-rss") {
|
||||||
target.rss = false;
|
target.rss = false;
|
||||||
return "handled";
|
return "handled";
|
||||||
@@ -79,11 +94,11 @@ export function parseArgs(argv) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function quoteArg(arg) {
|
function quoteArg(arg: string) {
|
||||||
return /[^A-Za-z0-9_./:-]/.test(arg) ? JSON.stringify(arg) : arg;
|
return /[^A-Za-z0-9_./:-]/.test(arg) ? JSON.stringify(arg) : arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
function runGitList(args, cwd) {
|
function runGitList(args: string[], cwd: string) {
|
||||||
const result = spawnSync("git", args, {
|
const result = spawnSync("git", args, {
|
||||||
cwd,
|
cwd,
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
@@ -97,7 +112,7 @@ function runGitList(args, cwd) {
|
|||||||
.filter((line) => line.length > 0);
|
.filter((line) => line.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function listChangedPaths(opts) {
|
function listChangedPaths(opts: BenchOptions) {
|
||||||
if (opts.mode === "worktree") {
|
if (opts.mode === "worktree") {
|
||||||
return [
|
return [
|
||||||
...new Set([
|
...new Set([
|
||||||
@@ -109,7 +124,7 @@ function listChangedPaths(opts) {
|
|||||||
return runGitList(["diff", "--name-only", `${opts.ref}...HEAD`], opts.cwd);
|
return runGitList(["diff", "--name-only", `${opts.ref}...HEAD`], opts.cwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseMaxRssBytes(output) {
|
export function parseMaxRssBytes(output: string) {
|
||||||
const match = output.match(
|
const match = output.match(
|
||||||
/(?:^|\n)[^\S\r\n]*(\d+)[^\S\r\n]+maximum resident set size[^\S\r\n]*(?:\r?\n|$)/u,
|
/(?:^|\n)[^\S\r\n]*(\d+)[^\S\r\n]+maximum resident set size[^\S\r\n]*(?:\r?\n|$)/u,
|
||||||
);
|
);
|
||||||
@@ -120,14 +135,24 @@ export function parseMaxRssBytes(output) {
|
|||||||
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;
|
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatRss(valueBytes) {
|
export function formatRss(valueBytes: number | null) {
|
||||||
if (valueBytes === null) {
|
if (valueBytes === null) {
|
||||||
return "n/a";
|
return "n/a";
|
||||||
}
|
}
|
||||||
return `${(valueBytes / 1024 / 1024).toFixed(1)}MB`;
|
return `${(valueBytes / 1024 / 1024).toFixed(1)}MB`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveBenchRssResult({ label, output, rss, status }) {
|
export function resolveBenchRssResult({
|
||||||
|
label,
|
||||||
|
output,
|
||||||
|
rss,
|
||||||
|
status,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
output: string;
|
||||||
|
rss: boolean;
|
||||||
|
status: number;
|
||||||
|
}) {
|
||||||
if (!rss) {
|
if (!rss) {
|
||||||
return { maxRssBytes: null, output, status };
|
return { maxRssBytes: null, output, status };
|
||||||
}
|
}
|
||||||
@@ -142,7 +167,7 @@ export function resolveBenchRssResult({ label, output, rss, status }) {
|
|||||||
return { maxRssBytes, output, status };
|
return { maxRssBytes, output, status };
|
||||||
}
|
}
|
||||||
|
|
||||||
function runBenchCommand(params) {
|
function runBenchCommand(params: BenchCommandParams) {
|
||||||
const env = { ...process.env };
|
const env = { ...process.env };
|
||||||
if (typeof params.maxWorkers === "number") {
|
if (typeof params.maxWorkers === "number") {
|
||||||
env.OPENCLAW_VITEST_MAX_WORKERS = String(params.maxWorkers);
|
env.OPENCLAW_VITEST_MAX_WORKERS = String(params.maxWorkers);
|
||||||
@@ -150,7 +175,7 @@ function runBenchCommand(params) {
|
|||||||
const startedAt = process.hrtime.bigint();
|
const startedAt = process.hrtime.bigint();
|
||||||
const commandArgs = params.rss ? ["-l", ...params.command] : params.command;
|
const commandArgs = params.rss ? ["-l", ...params.command] : params.command;
|
||||||
const result = spawnSync(
|
const result = spawnSync(
|
||||||
params.rss ? "/usr/bin/time" : commandArgs[0],
|
params.rss ? "/usr/bin/time" : params.command[0],
|
||||||
params.rss ? commandArgs : commandArgs.slice(1),
|
params.rss ? commandArgs : commandArgs.slice(1),
|
||||||
{
|
{
|
||||||
cwd: params.cwd,
|
cwd: params.cwd,
|
||||||
@@ -175,7 +200,7 @@ function runBenchCommand(params) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function printRunSummary(label, result) {
|
function printRunSummary(label: string, result: BenchResult) {
|
||||||
console.log(
|
console.log(
|
||||||
`${label.padEnd(8, " ")} wall=${formatMs(result.elapsedMs).padStart(9, " ")} rss=${formatRss(
|
`${label.padEnd(8, " ")} wall=${formatMs(result.elapsedMs).padStart(9, " ")} rss=${formatRss(
|
||||||
result.maxRssBytes,
|
result.maxRssBytes,
|
||||||
@@ -211,11 +236,11 @@ function main() {
|
|||||||
console.log(`- ${changedPath}`);
|
console.log(`- ${changedPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const routedCommand =
|
const routedCommand: [string, ...string[]] =
|
||||||
opts.mode === "worktree"
|
opts.mode === "worktree"
|
||||||
? [process.execPath, "scripts/test-projects.mjs", ...changedPaths]
|
? [process.execPath, "--import", "tsx", "scripts/test-projects.mts", ...changedPaths]
|
||||||
: [process.execPath, "scripts/test-projects.mjs", "--changed", opts.ref];
|
: [process.execPath, "--import", "tsx", "scripts/test-projects.mts", "--changed", opts.ref];
|
||||||
const rootCommand = [
|
const rootCommand: [string, ...string[]] = [
|
||||||
process.execPath,
|
process.execPath,
|
||||||
"scripts/run-vitest.mjs",
|
"scripts/run-vitest.mjs",
|
||||||
"run",
|
"run",
|
||||||
@@ -7,7 +7,7 @@ import { createWebFetchTool } from "../src/agents/tools/web-fetch.js";
|
|||||||
import type { OpenClawConfig } from "../src/config/types.openclaw.js";
|
import type { OpenClawConfig } from "../src/config/types.openclaw.js";
|
||||||
import type { LookupFn } from "../src/infra/net/ssrf.js";
|
import type { LookupFn } from "../src/infra/net/ssrf.js";
|
||||||
import { extractReadableContent } from "../src/web-fetch/content-extractors.runtime.js";
|
import { extractReadableContent } from "../src/web-fetch/content-extractors.runtime.js";
|
||||||
import { stripLeadingPackageManagerSeparator } from "./lib/arg-utils.mjs";
|
import { stripLeadingPackageManagerSeparator } from "./lib/arg-utils.mts";
|
||||||
|
|
||||||
type BenchmarkCaseId =
|
type BenchmarkCaseId =
|
||||||
| "tool-create"
|
| "tool-create"
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
import type fs from "node:fs";
|
|
||||||
|
|
||||||
export type BuildCacheEntry =
|
|
||||||
| string
|
|
||||||
| {
|
|
||||||
path: string;
|
|
||||||
excludeDirectories?: string[];
|
|
||||||
extensions?: string[];
|
|
||||||
recursive?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BuildAllStep = {
|
|
||||||
label: string;
|
|
||||||
kind?: "node" | "pnpm";
|
|
||||||
args?: string[];
|
|
||||||
pnpmArgs?: string[];
|
|
||||||
env?: NodeJS.ProcessEnv;
|
|
||||||
windowsNodeOptions?: string;
|
|
||||||
cache?: {
|
|
||||||
env?: string[];
|
|
||||||
inputs: BuildCacheEntry[];
|
|
||||||
outputs: BuildCacheEntry[];
|
|
||||||
requiredOutputs?: string[] | ((env: NodeJS.ProcessEnv) => string[]);
|
|
||||||
restore?: "always";
|
|
||||||
runOnHit?: {
|
|
||||||
env?: NodeJS.ProcessEnv;
|
|
||||||
finalize?: "refresh";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BuildAllCacheState = {
|
|
||||||
cacheable: boolean;
|
|
||||||
fresh: boolean;
|
|
||||||
restorable?: boolean;
|
|
||||||
reason: string;
|
|
||||||
signature?: string;
|
|
||||||
outputRoot?: string;
|
|
||||||
stampPath?: string;
|
|
||||||
inputFiles?: number;
|
|
||||||
outputFiles?: number;
|
|
||||||
relativeOutputFiles?: string[];
|
|
||||||
stampedOutputs?: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BUILD_ALL_STEPS: BuildAllStep[];
|
|
||||||
export const BUILD_ALL_PROFILES: Record<string, string[]>;
|
|
||||||
export const BUILD_ALL_PROFILE_STEP_ENV: Record<string, Record<string, NodeJS.ProcessEnv>>;
|
|
||||||
|
|
||||||
export function buildAllUsage(): string;
|
|
||||||
export function parseBuildAllArgs(argv: string[]): { help: boolean; profile: string };
|
|
||||||
export function resolveBuildAllSteps(profile?: string): BuildAllStep[];
|
|
||||||
export function resolveBuildAllEnvironment(
|
|
||||||
env?: NodeJS.ProcessEnv,
|
|
||||||
now?: () => Date,
|
|
||||||
readGitCommit?: () => string | null,
|
|
||||||
): { [key: string]: string | undefined; OPENCLAW_BUILD_TIMESTAMP: string };
|
|
||||||
export function resolveBuildAllStep(
|
|
||||||
step: BuildAllStep,
|
|
||||||
params?: {
|
|
||||||
platform?: NodeJS.Platform;
|
|
||||||
env?: NodeJS.ProcessEnv;
|
|
||||||
nodeExecPath?: string;
|
|
||||||
npmExecPath?: string;
|
|
||||||
comSpec?: string;
|
|
||||||
},
|
|
||||||
): {
|
|
||||||
command: string;
|
|
||||||
args: string[];
|
|
||||||
options: {
|
|
||||||
stdio: "inherit";
|
|
||||||
env: NodeJS.ProcessEnv;
|
|
||||||
shell?: boolean;
|
|
||||||
windowsVerbatimArguments?: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
export function resolveBuildAllStepOnCacheHit(step: BuildAllStep): BuildAllStep | null;
|
|
||||||
export function resolveBuildAllStepCacheState(
|
|
||||||
step: BuildAllStep,
|
|
||||||
params?: { rootDir?: string; fs?: typeof fs; env?: NodeJS.ProcessEnv },
|
|
||||||
): BuildAllCacheState;
|
|
||||||
export function writeBuildAllStepCacheStamp(
|
|
||||||
step: BuildAllStep,
|
|
||||||
cacheState: BuildAllCacheState,
|
|
||||||
params?: { rootDir?: string; fs?: typeof fs; env?: NodeJS.ProcessEnv },
|
|
||||||
): void;
|
|
||||||
export function resolveBuildAllStepCacheStampState(
|
|
||||||
step: BuildAllStep,
|
|
||||||
cacheState: BuildAllCacheState,
|
|
||||||
params?: { rootDir?: string; fs?: typeof fs },
|
|
||||||
): BuildAllCacheState;
|
|
||||||
export function restoreBuildAllStepCacheOutputs(
|
|
||||||
cacheState: BuildAllCacheState,
|
|
||||||
params?: { rootDir?: string; fs?: typeof fs },
|
|
||||||
): boolean;
|
|
||||||
export function finalizeBuildAllStepCache(
|
|
||||||
step: BuildAllStep,
|
|
||||||
cacheState: BuildAllCacheState,
|
|
||||||
params?: {
|
|
||||||
rootDir?: string;
|
|
||||||
fs?: typeof fs;
|
|
||||||
env?: NodeJS.ProcessEnv;
|
|
||||||
reusedCache?: boolean;
|
|
||||||
},
|
|
||||||
): boolean;
|
|
||||||
export function formatBuildAllDuration(durationMs: number): string;
|
|
||||||
export function formatBuildAllTimingSummary(
|
|
||||||
timings: Array<{ label: string; durationMs: number; status: string }>,
|
|
||||||
): string;
|
|
||||||
@@ -1,28 +1,64 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
// Builds OpenClaw packages and plugin SDK artifacts with cache-aware orchestration.
|
// Builds OpenClaw packages and plugin SDK artifacts with cache-aware orchestration.
|
||||||
|
|
||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync, type SpawnSyncOptions } from "node:child_process";
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { performance } from "node:perf_hooks";
|
import { performance } from "node:perf_hooks";
|
||||||
import { pathToFileURL } from "node:url";
|
import { pathToFileURL } from "node:url";
|
||||||
|
import { asRecord } from "@openclaw/normalization-core/record-coerce";
|
||||||
import prettyMilliseconds from "pretty-ms";
|
import prettyMilliseconds from "pretty-ms";
|
||||||
import {
|
import {
|
||||||
listPluginSdkDeclarationOutputs,
|
listPluginSdkDeclarationOutputs,
|
||||||
pluginSdkEntrypoints,
|
pluginSdkEntrypoints,
|
||||||
} from "./lib/plugin-sdk-entries.mjs";
|
} from "./lib/plugin-sdk-entries.mts";
|
||||||
import {
|
import {
|
||||||
TSDOWN_PACKAGE_CONFIG_GROUP,
|
TSDOWN_PACKAGE_CONFIG_GROUP,
|
||||||
TSDOWN_UNIFIED_CONFIG_GROUP,
|
TSDOWN_UNIFIED_CONFIG_GROUP,
|
||||||
} from "./lib/tsdown-config-groups.mjs";
|
} from "./lib/tsdown-config-groups.mts";
|
||||||
import {
|
import {
|
||||||
TSDOWN_PACKAGE_OUTPUT_ROOTS,
|
TSDOWN_PACKAGE_OUTPUT_ROOTS,
|
||||||
tsdownPackageOutputRoot,
|
tsdownPackageOutputRoot,
|
||||||
} from "./lib/tsdown-output-roots.mjs";
|
} from "./lib/tsdown-output-roots.mts";
|
||||||
import { resolvePnpmRunner } from "./pnpm-runner.mjs";
|
import { resolvePnpmRunner } from "./pnpm-runner.mts";
|
||||||
|
|
||||||
const nodeBin = process.execPath;
|
const nodeBin = process.execPath;
|
||||||
|
type BuildCachePath = {
|
||||||
|
path: string;
|
||||||
|
excludeDirectories?: string[];
|
||||||
|
extensions?: string[];
|
||||||
|
recursive?: boolean;
|
||||||
|
};
|
||||||
|
export type BuildCacheEntry = string | BuildCachePath;
|
||||||
|
|
||||||
|
type BuildCache = {
|
||||||
|
env?: string[];
|
||||||
|
inputs: BuildCacheEntry[];
|
||||||
|
outputs: BuildCacheEntry[];
|
||||||
|
requiredOutputs?: string[] | ((env: NodeJS.ProcessEnv) => string[]);
|
||||||
|
restore?: "always";
|
||||||
|
runOnHit?: { env?: NodeJS.ProcessEnv; finalize?: "refresh" };
|
||||||
|
};
|
||||||
|
|
||||||
|
type BuildCacheStep = { label: string; env?: NodeJS.ProcessEnv; cache?: BuildCache };
|
||||||
|
|
||||||
|
export type BuildAllStep = BuildCacheStep &
|
||||||
|
(
|
||||||
|
| { kind: "pnpm"; args?: never; pnpmArgs: string[]; windowsNodeOptions?: string }
|
||||||
|
| { kind?: "node"; args: string[]; pnpmArgs?: never; windowsNodeOptions?: string }
|
||||||
|
);
|
||||||
|
|
||||||
|
type BuildAllTiming = { label: string; durationMs: number; status: string };
|
||||||
|
type BuildAllFs = typeof fs;
|
||||||
|
type BuildAllStepParams = {
|
||||||
|
platform?: NodeJS.Platform;
|
||||||
|
env?: NodeJS.ProcessEnv;
|
||||||
|
nodeExecPath?: string;
|
||||||
|
npmExecPath?: string;
|
||||||
|
comSpec?: string;
|
||||||
|
};
|
||||||
|
type BuildAllCacheParams = { rootDir?: string; fs?: BuildAllFs; env?: NodeJS.ProcessEnv };
|
||||||
const FULL_GIT_COMMIT_RE = /^[0-9a-f]{40}$/iu;
|
const FULL_GIT_COMMIT_RE = /^[0-9a-f]{40}$/iu;
|
||||||
const BUILD_CACHE_VERSION = 4;
|
const BUILD_CACHE_VERSION = 4;
|
||||||
const TSDOWN_DECLARATION_EXTENSIONS = [".d.ts", ".d.mts", ".d.cts"];
|
const TSDOWN_DECLARATION_EXTENSIONS = [".d.ts", ".d.mts", ".d.cts"];
|
||||||
@@ -48,17 +84,17 @@ const TSDOWN_DECLARATION_TOOL_INPUTS = [
|
|||||||
"package.json",
|
"package.json",
|
||||||
"pnpm-lock.yaml",
|
"pnpm-lock.yaml",
|
||||||
"tsconfig.json",
|
"tsconfig.json",
|
||||||
"scripts/tsdown-build.mjs",
|
"scripts/tsdown-build.mts",
|
||||||
"scripts/lib/bundled-plugin-build-entries.mjs",
|
"scripts/lib/bundled-plugin-build-entries.mjs",
|
||||||
"scripts/lib/bundled-plugin-paths.mjs",
|
"scripts/lib/bundled-plugin-paths.mjs",
|
||||||
"scripts/lib/optional-bundled-clusters.mjs",
|
"scripts/lib/optional-bundled-clusters.mjs",
|
||||||
"scripts/lib/plugin-sdk-entries.mjs",
|
"scripts/lib/plugin-sdk-entries.mts",
|
||||||
"scripts/lib/plugin-sdk-entrypoints.json",
|
"scripts/lib/plugin-sdk-entrypoints.json",
|
||||||
"scripts/lib/plugin-sdk-private-local-only-subpaths.json",
|
"scripts/lib/plugin-sdk-private-local-only-subpaths.json",
|
||||||
"scripts/lib/plugin-sdk-deprecated-public-subpaths.json",
|
"scripts/lib/plugin-sdk-deprecated-public-subpaths.json",
|
||||||
"scripts/lib/plugin-sdk-deprecated-barrel-subpaths.json",
|
"scripts/lib/plugin-sdk-deprecated-barrel-subpaths.json",
|
||||||
"scripts/lib/tsdown-config-groups.mjs",
|
"scripts/lib/tsdown-config-groups.mts",
|
||||||
"scripts/lib/tsdown-output-roots.mjs",
|
"scripts/lib/tsdown-output-roots.mts",
|
||||||
];
|
];
|
||||||
const TSDOWN_PACKAGES_CACHE_INPUT = {
|
const TSDOWN_PACKAGES_CACHE_INPUT = {
|
||||||
path: "packages",
|
path: "packages",
|
||||||
@@ -78,7 +114,7 @@ const TSDOWN_UNIFIED_CACHE_INPUTS = [
|
|||||||
},
|
},
|
||||||
TSDOWN_PACKAGES_CACHE_INPUT,
|
TSDOWN_PACKAGES_CACHE_INPUT,
|
||||||
];
|
];
|
||||||
const declarationCacheOutputs = (roots) =>
|
const declarationCacheOutputs = (roots: string[]) =>
|
||||||
roots.map((root) => ({ path: root, extensions: TSDOWN_DECLARATION_EXTENSIONS }));
|
roots.map((root) => ({ path: root, extensions: TSDOWN_DECLARATION_EXTENSIONS }));
|
||||||
const PLUGIN_SDK_ENTRY_DTS_CACHE_ENV = [
|
const PLUGIN_SDK_ENTRY_DTS_CACHE_ENV = [
|
||||||
"OPENCLAW_BUILD_PRIVATE_QA",
|
"OPENCLAW_BUILD_PRIVATE_QA",
|
||||||
@@ -86,7 +122,7 @@ const PLUGIN_SDK_ENTRY_DTS_CACHE_ENV = [
|
|||||||
];
|
];
|
||||||
const PLUGIN_SDK_ENTRY_DTS_SHARED_CACHE_INPUTS = [
|
const PLUGIN_SDK_ENTRY_DTS_SHARED_CACHE_INPUTS = [
|
||||||
"scripts/write-plugin-sdk-entry-dts.ts",
|
"scripts/write-plugin-sdk-entry-dts.ts",
|
||||||
"scripts/lib/plugin-sdk-entries.mjs",
|
"scripts/lib/plugin-sdk-entries.mts",
|
||||||
"scripts/lib/plugin-sdk-entrypoints.json",
|
"scripts/lib/plugin-sdk-entrypoints.json",
|
||||||
"scripts/lib/plugin-sdk-private-local-only-subpaths.json",
|
"scripts/lib/plugin-sdk-private-local-only-subpaths.json",
|
||||||
"scripts/lib/plugin-sdk-deprecated-public-subpaths.json",
|
"scripts/lib/plugin-sdk-deprecated-public-subpaths.json",
|
||||||
@@ -121,18 +157,24 @@ const PLUGIN_SDK_SELF_BUILT_ENTRY_DTS_CACHE_OUTPUTS = [
|
|||||||
{ path: "dist/plugin-sdk", extensions: [".d.ts"], recursive: false },
|
{ path: "dist/plugin-sdk", extensions: [".d.ts"], recursive: false },
|
||||||
...PLUGIN_SDK_ENTRY_DTS_CACHE_OUTPUTS,
|
...PLUGIN_SDK_ENTRY_DTS_CACHE_OUTPUTS,
|
||||||
];
|
];
|
||||||
|
const tsxScript = (script: string, ...args: string[]) => ["--import", "tsx", script, ...args];
|
||||||
|
const nodeStep = (label: string, args: string[]): Extract<BuildAllStep, { kind?: "node" }> => ({
|
||||||
|
label,
|
||||||
|
kind: "node",
|
||||||
|
args,
|
||||||
|
});
|
||||||
|
const tsxStep = (label: string, script: string, ...args: string[]) =>
|
||||||
|
nodeStep(label, tsxScript(script, ...args));
|
||||||
const PNPM_STEP_NODE_FALLBACKS = new Map([
|
const PNPM_STEP_NODE_FALLBACKS = new Map([
|
||||||
["plugins:assets:build", ["scripts/bundled-plugin-assets.mjs", "--phase", "build"]],
|
["plugins:assets:build", tsxScript("scripts/bundled-plugin-assets.mts", "--phase", "build")],
|
||||||
["plugins:assets:copy", ["scripts/bundled-plugin-assets.mjs", "--phase", "copy"]],
|
["plugins:assets:copy", tsxScript("scripts/bundled-plugin-assets.mts", "--phase", "copy")],
|
||||||
["ui:build", ["scripts/ui.js", "build"]],
|
["ui:build", ["scripts/ui.js", "build"]],
|
||||||
]);
|
]);
|
||||||
export const BUILD_ALL_STEPS = [
|
export const BUILD_ALL_STEPS: BuildAllStep[] = [
|
||||||
{ label: "plugins:assets:build", kind: "pnpm", pnpmArgs: ["plugins:assets:build"] },
|
{ label: "plugins:assets:build", kind: "pnpm", pnpmArgs: ["plugins:assets:build"] },
|
||||||
{ label: "tsdown", kind: "node", args: ["scripts/tsdown-build.mjs"] },
|
tsxStep("tsdown", "scripts/tsdown-build.mts"),
|
||||||
{
|
{
|
||||||
label: "tsdown-ai",
|
...tsxStep("tsdown-ai", "scripts/tsdown-build.mts", "--config", "tsdown.ai.config.ts"),
|
||||||
kind: "node",
|
|
||||||
args: ["scripts/tsdown-build.mjs", "--config", "tsdown.ai.config.ts"],
|
|
||||||
cache: {
|
cache: {
|
||||||
env: ["OPENCLAW_RUN_NODE_SKIP_DTS_BUILD"],
|
env: ["OPENCLAW_RUN_NODE_SKIP_DTS_BUILD"],
|
||||||
inputs: [
|
inputs: [
|
||||||
@@ -148,15 +190,14 @@ export const BUILD_ALL_STEPS = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "tsdown-packages",
|
...tsxStep(
|
||||||
kind: "node",
|
"tsdown-packages",
|
||||||
args: [
|
"scripts/tsdown-build.mts",
|
||||||
"scripts/tsdown-build.mjs",
|
|
||||||
"--config",
|
"--config",
|
||||||
"tsdown.config.ts",
|
"tsdown.config.ts",
|
||||||
"--filter",
|
"--filter",
|
||||||
TSDOWN_PACKAGE_CONFIG_GROUP,
|
TSDOWN_PACKAGE_CONFIG_GROUP,
|
||||||
],
|
),
|
||||||
cache: {
|
cache: {
|
||||||
env: ["OPENCLAW_RUN_NODE_SKIP_DTS_BUILD"],
|
env: ["OPENCLAW_RUN_NODE_SKIP_DTS_BUILD"],
|
||||||
inputs: [...TSDOWN_DECLARATION_TOOL_INPUTS, "tsdown.config.ts", TSDOWN_PACKAGES_CACHE_INPUT],
|
inputs: [...TSDOWN_DECLARATION_TOOL_INPUTS, "tsdown.config.ts", TSDOWN_PACKAGES_CACHE_INPUT],
|
||||||
@@ -168,15 +209,14 @@ export const BUILD_ALL_STEPS = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "tsdown-unified",
|
...tsxStep(
|
||||||
kind: "node",
|
"tsdown-unified",
|
||||||
args: [
|
"scripts/tsdown-build.mts",
|
||||||
"scripts/tsdown-build.mjs",
|
|
||||||
"--config",
|
"--config",
|
||||||
"tsdown.config.ts",
|
"tsdown.config.ts",
|
||||||
"--filter",
|
"--filter",
|
||||||
TSDOWN_UNIFIED_CONFIG_GROUP,
|
TSDOWN_UNIFIED_CONFIG_GROUP,
|
||||||
],
|
),
|
||||||
cache: {
|
cache: {
|
||||||
env: ["OPENCLAW_BUILD_PRIVATE_QA", "OPENCLAW_RUN_NODE_SKIP_DTS_BUILD"],
|
env: ["OPENCLAW_BUILD_PRIVATE_QA", "OPENCLAW_RUN_NODE_SKIP_DTS_BUILD"],
|
||||||
inputs: [
|
inputs: [
|
||||||
@@ -195,27 +235,17 @@ export const BUILD_ALL_STEPS = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
tsxStep("check-cli-bootstrap-imports", "scripts/check-cli-bootstrap-imports.mts"),
|
||||||
label: "check-cli-bootstrap-imports",
|
|
||||||
kind: "node",
|
|
||||||
args: ["scripts/check-cli-bootstrap-imports.mjs"],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "plugins:assets:copy",
|
label: "plugins:assets:copy",
|
||||||
kind: "pnpm",
|
kind: "pnpm",
|
||||||
pnpmArgs: ["plugins:assets:copy"],
|
pnpmArgs: ["plugins:assets:copy"],
|
||||||
},
|
},
|
||||||
{ label: "runtime-postbuild", kind: "node", args: ["scripts/runtime-postbuild.mjs"] },
|
nodeStep("runtime-postbuild", ["scripts/runtime-postbuild.mjs"]),
|
||||||
{ label: "build-stamp", kind: "node", args: ["scripts/build-stamp.mjs"] },
|
tsxStep("build-stamp", "scripts/build-stamp.mts"),
|
||||||
|
tsxStep("runtime-postbuild-stamp", "scripts/runtime-postbuild-stamp.mts"),
|
||||||
{
|
{
|
||||||
label: "runtime-postbuild-stamp",
|
...tsxStep("write-plugin-sdk-entry-dts", "scripts/write-plugin-sdk-entry-dts.ts"),
|
||||||
kind: "node",
|
|
||||||
args: ["scripts/runtime-postbuild-stamp.mjs"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "write-plugin-sdk-entry-dts",
|
|
||||||
kind: "node",
|
|
||||||
args: ["--import", "tsx", "scripts/write-plugin-sdk-entry-dts.ts"],
|
|
||||||
env: {
|
env: {
|
||||||
OPENCLAW_PLUGIN_SDK_CANONICAL_DTS: "1",
|
OPENCLAW_PLUGIN_SDK_CANONICAL_DTS: "1",
|
||||||
},
|
},
|
||||||
@@ -226,16 +256,8 @@ export const BUILD_ALL_STEPS = [
|
|||||||
restore: "always",
|
restore: "always",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
tsxStep("check-plugin-sdk-exports", "scripts/check-plugin-sdk-exports.mts"),
|
||||||
label: "check-plugin-sdk-exports",
|
tsxStep("copy-hook-metadata", "scripts/copy-hook-metadata.ts"),
|
||||||
kind: "node",
|
|
||||||
args: ["scripts/check-plugin-sdk-exports.mjs"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "copy-hook-metadata",
|
|
||||||
kind: "node",
|
|
||||||
args: ["--import", "tsx", "scripts/copy-hook-metadata.ts"],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "ui:build",
|
label: "ui:build",
|
||||||
kind: "pnpm",
|
kind: "pnpm",
|
||||||
@@ -246,15 +268,9 @@ export const BUILD_ALL_STEPS = [
|
|||||||
// warm hit could restore stale service-worker/app cache metadata.
|
// warm hit could restore stale service-worker/app cache metadata.
|
||||||
cache: undefined,
|
cache: undefined,
|
||||||
},
|
},
|
||||||
|
tsxStep("write-build-info", "scripts/write-build-info.ts"),
|
||||||
{
|
{
|
||||||
label: "write-build-info",
|
...tsxStep("write-cli-startup-metadata", "scripts/write-cli-startup-metadata.ts"),
|
||||||
kind: "node",
|
|
||||||
args: ["--import", "tsx", "scripts/write-build-info.ts"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "write-cli-startup-metadata",
|
|
||||||
kind: "node",
|
|
||||||
args: ["--import", "tsx", "scripts/write-cli-startup-metadata.ts"],
|
|
||||||
cache: {
|
cache: {
|
||||||
inputs: [
|
inputs: [
|
||||||
"scripts/write-cli-startup-metadata.ts",
|
"scripts/write-cli-startup-metadata.ts",
|
||||||
@@ -267,7 +283,7 @@ export const BUILD_ALL_STEPS = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const BUILD_ALL_PROFILES = {
|
export const BUILD_ALL_PROFILES: Record<string, string[]> = {
|
||||||
full: [
|
full: [
|
||||||
"plugins:assets:build",
|
"plugins:assets:build",
|
||||||
"tsdown-ai",
|
"tsdown-ai",
|
||||||
@@ -337,7 +353,7 @@ export const BUILD_ALL_PROFILES = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BUILD_ALL_PROFILE_STEP_ENV = {
|
export const BUILD_ALL_PROFILE_STEP_ENV: Record<string, Record<string, NodeJS.ProcessEnv>> = {
|
||||||
full: {
|
full: {
|
||||||
"tsdown-unified": {
|
"tsdown-unified": {
|
||||||
OPENCLAW_PRESERVE_CLI_STARTUP_METADATA: "1",
|
OPENCLAW_PRESERVE_CLI_STARTUP_METADATA: "1",
|
||||||
@@ -388,7 +404,7 @@ export const BUILD_ALL_PROFILE_STEP_ENV = {
|
|||||||
|
|
||||||
export function buildAllUsage() {
|
export function buildAllUsage() {
|
||||||
return [
|
return [
|
||||||
"Usage: node scripts/build-all.mjs [profile]",
|
"Usage: node --import tsx scripts/build-all.mts [profile]",
|
||||||
"",
|
"",
|
||||||
"Builds OpenClaw artifacts for the selected profile.",
|
"Builds OpenClaw artifacts for the selected profile.",
|
||||||
"",
|
"",
|
||||||
@@ -400,7 +416,7 @@ export function buildAllUsage() {
|
|||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseBuildAllArgs(argv) {
|
export function parseBuildAllArgs(argv: string[]) {
|
||||||
const args = {
|
const args = {
|
||||||
help: false,
|
help: false,
|
||||||
profile: "full",
|
profile: "full",
|
||||||
@@ -424,7 +440,7 @@ export function parseBuildAllArgs(argv) {
|
|||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveBuildAllSteps(profile = "full") {
|
export function resolveBuildAllSteps(profile = "full"): BuildAllStep[] {
|
||||||
const labels = BUILD_ALL_PROFILES[profile];
|
const labels = BUILD_ALL_PROFILES[profile];
|
||||||
if (!labels) {
|
if (!labels) {
|
||||||
throw new Error(`Unknown build profile: ${profile}`);
|
throw new Error(`Unknown build profile: ${profile}`);
|
||||||
@@ -435,28 +451,30 @@ export function resolveBuildAllSteps(profile = "full") {
|
|||||||
throw new Error(`Build profile ${profile} references unknown steps: ${missing.join(", ")}`);
|
throw new Error(`Build profile ${profile} references unknown steps: ${missing.join(", ")}`);
|
||||||
}
|
}
|
||||||
const envOverrides = BUILD_ALL_PROFILE_STEP_ENV[profile] ?? {};
|
const envOverrides = BUILD_ALL_PROFILE_STEP_ENV[profile] ?? {};
|
||||||
return selected.map((step) => {
|
return selected
|
||||||
const env = envOverrides[step.label];
|
.filter((step): step is NonNullable<typeof step> => step !== undefined)
|
||||||
if (!env) {
|
.map((step) => {
|
||||||
return step;
|
const env = envOverrides[step.label];
|
||||||
}
|
if (!env) {
|
||||||
const mergedEnv = Object.assign({}, step.env, env);
|
return step;
|
||||||
const merged = Object.assign({}, step, { env: mergedEnv });
|
}
|
||||||
// Self-built declarations need both the complete repository-owned type
|
const mergedEnv = Object.assign({}, "env" in step ? step.env : undefined, env);
|
||||||
// graph and the flat declarations that this step generates after tsdown
|
const merged: BuildAllStep = Object.assign({}, step, { env: mergedEnv });
|
||||||
// clears dist. Canonical mode keeps its narrower generated-dts cache.
|
// Self-built declarations need both the complete repository-owned type
|
||||||
if (
|
// graph and the flat declarations that this step generates after tsdown
|
||||||
step.label === "write-plugin-sdk-entry-dts" &&
|
// clears dist. Canonical mode keeps its narrower generated-dts cache.
|
||||||
mergedEnv.OPENCLAW_PLUGIN_SDK_CANONICAL_DTS !== "1"
|
if (
|
||||||
) {
|
step.label === "write-plugin-sdk-entry-dts" &&
|
||||||
merged.cache = {
|
mergedEnv.OPENCLAW_PLUGIN_SDK_CANONICAL_DTS !== "1"
|
||||||
...step.cache,
|
) {
|
||||||
inputs: PLUGIN_SDK_SELF_BUILT_ENTRY_DTS_CACHE_INPUTS,
|
merged.cache = {
|
||||||
outputs: PLUGIN_SDK_SELF_BUILT_ENTRY_DTS_CACHE_OUTPUTS,
|
...step.cache,
|
||||||
};
|
inputs: PLUGIN_SDK_SELF_BUILT_ENTRY_DTS_CACHE_INPUTS,
|
||||||
}
|
outputs: PLUGIN_SDK_SELF_BUILT_ENTRY_DTS_CACHE_OUTPUTS,
|
||||||
return merged;
|
};
|
||||||
});
|
}
|
||||||
|
return merged;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function readCurrentGitCommit() {
|
function readCurrentGitCommit() {
|
||||||
@@ -469,9 +487,9 @@ function readCurrentGitCommit() {
|
|||||||
|
|
||||||
/** Pin one source identity for every child process that contributes to this build. */
|
/** Pin one source identity for every child process that contributes to this build. */
|
||||||
export function resolveBuildAllEnvironment(
|
export function resolveBuildAllEnvironment(
|
||||||
env = process.env,
|
env: NodeJS.ProcessEnv = process.env,
|
||||||
now = () => new Date(),
|
now: () => Date = () => new Date(),
|
||||||
readGitCommit = readCurrentGitCommit,
|
readGitCommit: () => string | null = readCurrentGitCommit,
|
||||||
) {
|
) {
|
||||||
const explicitTimestamp = env.OPENCLAW_BUILD_TIMESTAMP?.trim();
|
const explicitTimestamp = env.OPENCLAW_BUILD_TIMESTAMP?.trim();
|
||||||
const explicitCommit = env.GIT_COMMIT?.trim() || env.GIT_SHA?.trim();
|
const explicitCommit = env.GIT_COMMIT?.trim() || env.GIT_SHA?.trim();
|
||||||
@@ -481,17 +499,14 @@ export function resolveBuildAllEnvironment(
|
|||||||
if (commit && !FULL_GIT_COMMIT_RE.test(commit)) {
|
if (commit && !FULL_GIT_COMMIT_RE.test(commit)) {
|
||||||
throw new Error("build commit must be a full 40-character hexadecimal SHA");
|
throw new Error("build commit must be a full 40-character hexadecimal SHA");
|
||||||
}
|
}
|
||||||
const buildEnv = {
|
return {
|
||||||
...env,
|
...env,
|
||||||
OPENCLAW_BUILD_TIMESTAMP: explicitTimestamp || now().toISOString(),
|
OPENCLAW_BUILD_TIMESTAMP: explicitTimestamp || now().toISOString(),
|
||||||
|
...(commit ? { GIT_COMMIT: commit.toLowerCase() } : {}),
|
||||||
};
|
};
|
||||||
if (commit) {
|
|
||||||
buildEnv.GIT_COMMIT = commit.toLowerCase();
|
|
||||||
}
|
|
||||||
return buildEnv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveStepEnv(step, env, platform) {
|
function resolveStepEnv(step: BuildAllStep, env: NodeJS.ProcessEnv, platform: NodeJS.Platform) {
|
||||||
const stepEnv = step.env ? Object.assign({}, env, step.env) : env;
|
const stepEnv = step.env ? Object.assign({}, env, step.env) : env;
|
||||||
if (platform !== "win32" || !step.windowsNodeOptions) {
|
if (platform !== "win32" || !step.windowsNodeOptions) {
|
||||||
return stepEnv;
|
return stepEnv;
|
||||||
@@ -508,7 +523,7 @@ function resolveStepEnv(step, env, platform) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveBuildAllStep(step, params = {}) {
|
export function resolveBuildAllStep(step: BuildAllStep, params: BuildAllStepParams = {}) {
|
||||||
const platform = params.platform ?? process.platform;
|
const platform = params.platform ?? process.platform;
|
||||||
const env = resolveStepEnv(step, params.env ?? process.env, platform);
|
const env = resolveStepEnv(step, params.env ?? process.env, platform);
|
||||||
if (step.kind === "pnpm") {
|
if (step.kind === "pnpm") {
|
||||||
@@ -521,7 +536,7 @@ export function resolveBuildAllStep(step, params = {}) {
|
|||||||
options: {
|
options: {
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
env,
|
env,
|
||||||
},
|
} satisfies SpawnSyncOptions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const runner = resolvePnpmRunner({
|
const runner = resolvePnpmRunner({
|
||||||
@@ -540,7 +555,7 @@ export function resolveBuildAllStep(step, params = {}) {
|
|||||||
env,
|
env,
|
||||||
shell: runner.shell,
|
shell: runner.shell,
|
||||||
windowsVerbatimArguments: runner.windowsVerbatimArguments,
|
windowsVerbatimArguments: runner.windowsVerbatimArguments,
|
||||||
},
|
} satisfies SpawnSyncOptions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -549,11 +564,11 @@ export function resolveBuildAllStep(step, params = {}) {
|
|||||||
options: {
|
options: {
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
env,
|
env,
|
||||||
},
|
} satisfies SpawnSyncOptions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveBuildAllStepOnCacheHit(step) {
|
export function resolveBuildAllStepOnCacheHit(step: BuildAllStep) {
|
||||||
if (!step.cache?.runOnHit) {
|
if (!step.cache?.runOnHit) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -563,25 +578,18 @@ export function resolveBuildAllStepOnCacheHit(step) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeCacheEntry(entry) {
|
function cacheEntryIncludesFile(entry: BuildCachePath, filePath: string) {
|
||||||
if (typeof entry === "string") {
|
|
||||||
return { path: entry };
|
|
||||||
}
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cacheEntryIncludesFile(entry, filePath) {
|
|
||||||
if (!entry.extensions?.length) {
|
if (!entry.extensions?.length) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return entry.extensions.some((extension) => filePath.endsWith(extension));
|
return entry.extensions.some((extension) => filePath.endsWith(extension));
|
||||||
}
|
}
|
||||||
|
|
||||||
function cacheEntryExcludesDirectory(entry, name) {
|
function listFilesRecursively(
|
||||||
return entry.excludeDirectories?.includes(name) ?? false;
|
rootPath: string,
|
||||||
}
|
fsImpl: BuildAllFs,
|
||||||
|
cacheEntry: BuildCachePath = { path: rootPath },
|
||||||
function listFilesRecursively(rootPath, fsImpl, cacheEntry = { path: rootPath }) {
|
) {
|
||||||
let stat;
|
let stat;
|
||||||
try {
|
try {
|
||||||
stat = fsImpl.statSync(rootPath);
|
stat = fsImpl.statSync(rootPath);
|
||||||
@@ -594,7 +602,7 @@ function listFilesRecursively(rootPath, fsImpl, cacheEntry = { path: rootPath })
|
|||||||
if (!stat.isDirectory()) {
|
if (!stat.isDirectory()) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const out = [];
|
const out: string[] = [];
|
||||||
const entries = fsImpl.readdirSync(rootPath, { withFileTypes: true });
|
const entries = fsImpl.readdirSync(rootPath, { withFileTypes: true });
|
||||||
const recursive = cacheEntry.recursive !== false;
|
const recursive = cacheEntry.recursive !== false;
|
||||||
for (const dirent of entries) {
|
for (const dirent of entries) {
|
||||||
@@ -602,7 +610,7 @@ function listFilesRecursively(rootPath, fsImpl, cacheEntry = { path: rootPath })
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const entryPath = path.join(rootPath, dirent.name);
|
const entryPath = path.join(rootPath, dirent.name);
|
||||||
if (dirent.isDirectory() && cacheEntryExcludesDirectory(cacheEntry, dirent.name)) {
|
if (dirent.isDirectory() && cacheEntry.excludeDirectories?.includes(dirent.name)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (dirent.isDirectory() && recursive) {
|
if (dirent.isDirectory() && recursive) {
|
||||||
@@ -614,22 +622,22 @@ function listFilesRecursively(rootPath, fsImpl, cacheEntry = { path: rootPath })
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
function listCacheFiles(rootDir, entries, fsImpl) {
|
function listCacheFiles(rootDir: string, entries: BuildCacheEntry[], fsImpl: BuildAllFs) {
|
||||||
return entries
|
return entries
|
||||||
.map(normalizeCacheEntry)
|
.map((entry) => (typeof entry === "string" ? { path: entry } : entry))
|
||||||
.flatMap((entry) => listFilesRecursively(path.resolve(rootDir, entry.path), fsImpl, entry))
|
.flatMap((entry) => listFilesRecursively(path.resolve(rootDir, entry.path), fsImpl, entry))
|
||||||
.toSorted();
|
.toSorted();
|
||||||
}
|
}
|
||||||
|
|
||||||
function portableRelativePath(rootDir, filePath) {
|
function portableRelativePath(rootDir: string, filePath: string) {
|
||||||
return path.relative(rootDir, filePath).split(path.sep).join("/");
|
return path.relative(rootDir, filePath).split(path.sep).join("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePortablePath(filePath) {
|
function normalizePortablePath(filePath: string) {
|
||||||
return filePath.replaceAll("\\", "/");
|
return filePath.replaceAll("\\", "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveCacheRequiredOutputs(cache, env) {
|
function resolveCacheRequiredOutputs(cache: BuildCache, env: NodeJS.ProcessEnv) {
|
||||||
const outputs =
|
const outputs =
|
||||||
typeof cache.requiredOutputs === "function"
|
typeof cache.requiredOutputs === "function"
|
||||||
? cache.requiredOutputs(env)
|
? cache.requiredOutputs(env)
|
||||||
@@ -637,7 +645,7 @@ function resolveCacheRequiredOutputs(cache, env) {
|
|||||||
return outputs.map((output) => normalizePortablePath(output));
|
return outputs.map((output) => normalizePortablePath(output));
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveBuildCacheRoot(rootDir, env) {
|
function resolveBuildCacheRoot(rootDir: string, env: NodeJS.ProcessEnv) {
|
||||||
// Dev update preflight and final builds run in separate worktrees. A shared
|
// Dev update preflight and final builds run in separate worktrees. A shared
|
||||||
// root lets content signatures decide reuse without relocating built trees.
|
// root lets content signatures decide reuse without relocating built trees.
|
||||||
const configuredRoot = env?.BUILD_ALL_CACHE_ROOT?.trim();
|
const configuredRoot = env?.BUILD_ALL_CACHE_ROOT?.trim();
|
||||||
@@ -649,7 +657,7 @@ function resolveBuildCacheRoot(rootDir, env) {
|
|||||||
: path.resolve(rootDir, configuredRoot);
|
: path.resolve(rootDir, configuredRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveCachePaths(rootDir, step, env) {
|
function resolveCachePaths(rootDir: string, step: BuildCacheStep, env: NodeJS.ProcessEnv) {
|
||||||
const safeLabel = step.label.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
const safeLabel = step.label.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
||||||
const cacheDir = path.join(resolveBuildCacheRoot(rootDir, env), safeLabel);
|
const cacheDir = path.join(resolveBuildCacheRoot(rootDir, env), safeLabel);
|
||||||
return {
|
return {
|
||||||
@@ -659,7 +667,13 @@ function resolveCachePaths(rootDir, step, env) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function hashInputFiles(rootDir, files, fsImpl, envEntries = [], env = process.env) {
|
function hashInputFiles(
|
||||||
|
rootDir: string,
|
||||||
|
files: string[],
|
||||||
|
fsImpl: BuildAllFs,
|
||||||
|
envEntries: string[] = [],
|
||||||
|
env: NodeJS.ProcessEnv = process.env,
|
||||||
|
) {
|
||||||
const hash = createHash("sha256");
|
const hash = createHash("sha256");
|
||||||
hash.update(`v${BUILD_CACHE_VERSION}\0`);
|
hash.update(`v${BUILD_CACHE_VERSION}\0`);
|
||||||
for (const name of envEntries.toSorted((left, right) => left.localeCompare(right))) {
|
for (const name of envEntries.toSorted((left, right) => left.localeCompare(right))) {
|
||||||
@@ -677,15 +691,24 @@ function hashInputFiles(rootDir, files, fsImpl, envEntries = [], env = process.e
|
|||||||
return hash.digest("hex");
|
return hash.digest("hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
function readCacheStamp(stampPath, fsImpl) {
|
function readCacheStamp(stampPath: string, fsImpl: BuildAllFs) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(fsImpl.readFileSync(stampPath, "utf8"));
|
const stamp = asRecord(JSON.parse(fsImpl.readFileSync(stampPath, "utf8")));
|
||||||
|
const { version, signature, outputs: rawOutputs } = stamp;
|
||||||
|
const outputs = Array.isArray(rawOutputs)
|
||||||
|
? rawOutputs.filter((entry) => typeof entry === "string")
|
||||||
|
: undefined;
|
||||||
|
return {
|
||||||
|
version: typeof version === "number" ? version : undefined,
|
||||||
|
signature: typeof signature === "string" ? signature : undefined,
|
||||||
|
outputs,
|
||||||
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasAllFiles(rootDir, relativeFiles, fsImpl) {
|
function hasAllFiles(rootDir: string, relativeFiles: string[], fsImpl: BuildAllFs) {
|
||||||
return relativeFiles.every((relativeFile) => {
|
return relativeFiles.every((relativeFile) => {
|
||||||
try {
|
try {
|
||||||
return fsImpl.statSync(path.resolve(rootDir, relativeFile)).isFile();
|
return fsImpl.statSync(path.resolve(rootDir, relativeFile)).isFile();
|
||||||
@@ -695,12 +718,15 @@ function hasAllFiles(rootDir, relativeFiles, fsImpl) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyFileSync(fsImpl, sourcePath, targetPath) {
|
function copyFileSync(fsImpl: BuildAllFs, sourcePath: string, targetPath: string) {
|
||||||
fsImpl.mkdirSync(path.dirname(targetPath), { recursive: true });
|
fsImpl.mkdirSync(path.dirname(targetPath), { recursive: true });
|
||||||
fsImpl.copyFileSync(sourcePath, targetPath);
|
fsImpl.copyFileSync(sourcePath, targetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveBuildAllStepCacheState(step, params = {}) {
|
export function resolveBuildAllStepCacheState(
|
||||||
|
step: BuildCacheStep,
|
||||||
|
params: BuildAllCacheParams = {},
|
||||||
|
) {
|
||||||
if (!step.cache) {
|
if (!step.cache) {
|
||||||
return { cacheable: false, fresh: false, reason: "no-cache" };
|
return { cacheable: false, fresh: false, reason: "no-cache" };
|
||||||
}
|
}
|
||||||
@@ -721,9 +747,7 @@ export function resolveBuildAllStepCacheState(step, params = {}) {
|
|||||||
const stamp = readCacheStamp(stampPath, fsImpl);
|
const stamp = readCacheStamp(stampPath, fsImpl);
|
||||||
const outputFiles = listCacheFiles(rootDir, step.cache.outputs, fsImpl);
|
const outputFiles = listCacheFiles(rootDir, step.cache.outputs, fsImpl);
|
||||||
const relativeOutputFiles = outputFiles.map((file) => portableRelativePath(rootDir, file));
|
const relativeOutputFiles = outputFiles.map((file) => portableRelativePath(rootDir, file));
|
||||||
const stampedOutputs = Array.isArray(stamp?.outputs)
|
const stampedOutputs = stamp?.outputs?.map((entry) => normalizePortablePath(entry)) ?? [];
|
||||||
? stamp.outputs.map((entry) => normalizePortablePath(entry))
|
|
||||||
: [];
|
|
||||||
const requiredOutputs = resolveCacheRequiredOutputs(step.cache, params.env ?? process.env);
|
const requiredOutputs = resolveCacheRequiredOutputs(step.cache, params.env ?? process.env);
|
||||||
const stampedOutputSet = new Set(stampedOutputs);
|
const stampedOutputSet = new Set(stampedOutputs);
|
||||||
// Restore trusts the stamp inventory, so legacy partial stamps must name the
|
// Restore trusts the stamp inventory, so legacy partial stamps must name the
|
||||||
@@ -759,8 +783,15 @@ export function resolveBuildAllStepCacheState(step, params = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function writeBuildAllStepCacheStamp(step, cacheState, params = {}) {
|
type BuildAllCacheState = ReturnType<typeof resolveBuildAllStepCacheState>;
|
||||||
|
|
||||||
|
export function writeBuildAllStepCacheStamp(
|
||||||
|
step: BuildCacheStep,
|
||||||
|
cacheState: BuildAllCacheState,
|
||||||
|
params: Pick<BuildAllCacheParams, "rootDir" | "fs" | "env"> = {},
|
||||||
|
) {
|
||||||
if (
|
if (
|
||||||
|
!step.cache ||
|
||||||
!cacheState.cacheable ||
|
!cacheState.cacheable ||
|
||||||
!cacheState.signature ||
|
!cacheState.signature ||
|
||||||
!cacheState.stampPath ||
|
!cacheState.stampPath ||
|
||||||
@@ -802,7 +833,11 @@ export function writeBuildAllStepCacheStamp(step, cacheState, params = {}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveBuildAllStepCacheStampState(step, cacheState, params = {}) {
|
export function resolveBuildAllStepCacheStampState(
|
||||||
|
step: BuildCacheStep,
|
||||||
|
cacheState: BuildAllCacheState,
|
||||||
|
params: Pick<BuildAllCacheParams, "rootDir" | "fs"> = {},
|
||||||
|
) {
|
||||||
if (!cacheState.cacheable || !cacheState.signature || !step.cache) {
|
if (!cacheState.cacheable || !cacheState.signature || !step.cache) {
|
||||||
return cacheState;
|
return cacheState;
|
||||||
}
|
}
|
||||||
@@ -816,7 +851,10 @@ export function resolveBuildAllStepCacheStampState(step, cacheState, params = {}
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function restoreBuildAllStepCacheOutputs(cacheState, params = {}) {
|
export function restoreBuildAllStepCacheOutputs(
|
||||||
|
cacheState: BuildAllCacheState,
|
||||||
|
params: Pick<BuildAllCacheParams, "rootDir" | "fs"> = {},
|
||||||
|
) {
|
||||||
if (!cacheState.restorable || !cacheState.outputRoot || !cacheState.stampedOutputs?.length) {
|
if (!cacheState.restorable || !cacheState.outputRoot || !cacheState.stampedOutputs?.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -832,7 +870,11 @@ export function restoreBuildAllStepCacheOutputs(cacheState, params = {}) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function finalizeBuildAllStepCache(step, cacheState, params = {}) {
|
export function finalizeBuildAllStepCache(
|
||||||
|
step: BuildCacheStep,
|
||||||
|
cacheState: BuildAllCacheState,
|
||||||
|
params: BuildAllCacheParams & { reusedCache?: boolean } = {},
|
||||||
|
) {
|
||||||
if (params.reusedCache && step.cache?.runOnHit?.finalize !== "refresh") {
|
if (params.reusedCache && step.cache?.runOnHit?.finalize !== "refresh") {
|
||||||
return restoreBuildAllStepCacheOutputs(cacheState, params);
|
return restoreBuildAllStepCacheOutputs(cacheState, params);
|
||||||
}
|
}
|
||||||
@@ -846,7 +888,7 @@ export function finalizeBuildAllStepCache(step, cacheState, params = {}) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatBuildAllDuration(durationMs) {
|
export function formatBuildAllDuration(durationMs: number) {
|
||||||
const clampedMs = Math.max(0, durationMs);
|
const clampedMs = Math.max(0, durationMs);
|
||||||
const roundedMs =
|
const roundedMs =
|
||||||
clampedMs < 1000
|
clampedMs < 1000
|
||||||
@@ -859,7 +901,7 @@ export function formatBuildAllDuration(durationMs) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatBuildAllTimingSummary(timings) {
|
export function formatBuildAllTimingSummary(timings: BuildAllTiming[]) {
|
||||||
if (timings.length === 0) {
|
if (timings.length === 0) {
|
||||||
return "[build-all] phase timings: no phases ran";
|
return "[build-all] phase timings: no phases ran";
|
||||||
}
|
}
|
||||||
@@ -894,7 +936,7 @@ if (isMainModule()) {
|
|||||||
console.log(buildAllUsage());
|
console.log(buildAllUsage());
|
||||||
} else {
|
} else {
|
||||||
const buildEnv = resolveBuildAllEnvironment();
|
const buildEnv = resolveBuildAllEnvironment();
|
||||||
const timings = [];
|
const timings: BuildAllTiming[] = [];
|
||||||
let exitCode = 0;
|
let exitCode = 0;
|
||||||
for (const step of resolveBuildAllSteps(args.profile)) {
|
for (const step of resolveBuildAllSteps(args.profile)) {
|
||||||
const startedAt = performance.now();
|
const startedAt = performance.now();
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Creates the esbuild plugin that neutralizes Pierre diffs' browser side-effect import.
|
|
||||||
*/
|
|
||||||
export function createPierreDiffsSideEffectImportPlugin(): {
|
|
||||||
name: string;
|
|
||||||
setup(buildContext: unknown): void;
|
|
||||||
};
|
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
// Builds browser runtime bundles for the diffs viewer assets.
|
// Builds browser runtime bundles for the diffs viewer assets.
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { build } from "esbuild";
|
import { build, type Plugin } from "esbuild";
|
||||||
import { writeGeneratedTextAsset } from "./lib/generated-text-asset.mjs";
|
import { writeGeneratedTextAsset } from "./lib/generated-text-asset.mts";
|
||||||
|
|
||||||
const modulePath = fileURLToPath(import.meta.url);
|
const modulePath = fileURLToPath(import.meta.url);
|
||||||
const repoRoot = path.resolve(path.dirname(modulePath), "..");
|
const repoRoot = path.resolve(path.dirname(modulePath), "..");
|
||||||
@@ -25,13 +25,17 @@ const targets = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function toPosixPath(value) {
|
function toPosixPath(value: string) {
|
||||||
return String(value ?? "").replaceAll("\\", "/");
|
return value.replaceAll("\\", "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the esbuild plugin that neutralizes Pierre diffs' browser side-effect import.
|
* Creates the esbuild plugin that neutralizes Pierre diffs' browser side-effect import.
|
||||||
*/
|
*/
|
||||||
|
export function createPierreDiffsSideEffectImportPlugin(): {
|
||||||
|
name: string;
|
||||||
|
setup(buildContext: unknown): void;
|
||||||
|
};
|
||||||
export function createPierreDiffsSideEffectImportPlugin() {
|
export function createPierreDiffsSideEffectImportPlugin() {
|
||||||
return {
|
return {
|
||||||
name: "openclaw-diffs-pierre-side-effect-imports",
|
name: "openclaw-diffs-pierre-side-effect-imports",
|
||||||
@@ -58,21 +62,22 @@ export function createPierreDiffsSideEffectImportPlugin() {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
} satisfies Plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds one configured diffs viewer runtime target.
|
* Builds one configured diffs viewer runtime target.
|
||||||
*/
|
*/
|
||||||
async function buildDiffsViewerRuntime(targetName) {
|
async function buildDiffsViewerRuntime(targetName: string | undefined) {
|
||||||
const target = targets[targetName];
|
const target = Object.entries(targets).find(([name]) => name === targetName)?.[1];
|
||||||
if (!target) {
|
if (!target) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Usage: node scripts/build-diffs-viewer-runtime.mjs ${Object.keys(targets).join("|")}`,
|
`Usage: node --import tsx scripts/build-diffs-viewer-runtime.mts ${Object.keys(targets).join("|")}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputPath = path.join(repoRoot, target.output);
|
const outputPath = path.join(repoRoot, target.output);
|
||||||
|
const shikiAlias = "shikiAlias" in target ? target.shikiAlias : undefined;
|
||||||
const result = await build({
|
const result = await build({
|
||||||
entryPoints: [path.join(repoRoot, target.entry)],
|
entryPoints: [path.join(repoRoot, target.entry)],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
@@ -89,16 +94,16 @@ async function buildDiffsViewerRuntime(targetName) {
|
|||||||
write: false,
|
write: false,
|
||||||
plugins: [
|
plugins: [
|
||||||
createPierreDiffsSideEffectImportPlugin(),
|
createPierreDiffsSideEffectImportPlugin(),
|
||||||
...(target.shikiAlias
|
...(shikiAlias
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
name: "openclaw-diffs-curated-shiki",
|
name: "openclaw-diffs-curated-shiki",
|
||||||
setup(buildContext) {
|
setup(buildContext) {
|
||||||
buildContext.onResolve({ filter: /^shiki$/ }, () => ({
|
buildContext.onResolve({ filter: /^shiki$/ }, () => ({
|
||||||
path: path.join(repoRoot, target.shikiAlias),
|
path: path.join(repoRoot, shikiAlias),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
},
|
} satisfies Plugin,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
],
|
],
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
type DiscordActivitySdkBuild = (options: {
|
|
||||||
absWorkingDir: string;
|
|
||||||
bundle: boolean;
|
|
||||||
entryPoints: string[];
|
|
||||||
format: string;
|
|
||||||
legalComments: string;
|
|
||||||
minify: boolean;
|
|
||||||
outfile: string;
|
|
||||||
platform: string;
|
|
||||||
target: string;
|
|
||||||
write: false;
|
|
||||||
}) => Promise<{
|
|
||||||
outputFiles?: Array<{ text: string }>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
/** Builds the browser SDK bundle and returns whether the generated asset changed. */
|
|
||||||
export function buildDiscordActivitySdk(params?: {
|
|
||||||
build?: DiscordActivitySdkBuild;
|
|
||||||
outputPath?: string;
|
|
||||||
}): Promise<boolean>;
|
|
||||||
@@ -3,7 +3,14 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { build } from "esbuild";
|
import { build } from "esbuild";
|
||||||
import { writeGeneratedTextAsset } from "./lib/generated-text-asset.mjs";
|
import { writeGeneratedTextAsset } from "./lib/generated-text-asset.mts";
|
||||||
|
|
||||||
|
type DiscordActivitySdkBuildParams = {
|
||||||
|
build?: (
|
||||||
|
options: Parameters<typeof build>[0],
|
||||||
|
) => Promise<{ outputFiles?: Array<{ text: string }> }>;
|
||||||
|
outputPath?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const modulePath = fileURLToPath(import.meta.url);
|
const modulePath = fileURLToPath(import.meta.url);
|
||||||
const repoRoot = path.resolve(path.dirname(modulePath), "..");
|
const repoRoot = path.resolve(path.dirname(modulePath), "..");
|
||||||
@@ -11,7 +18,7 @@ const discordDir = path.join(repoRoot, "extensions/discord");
|
|||||||
const outputPath = path.join(repoRoot, "extensions/discord/assets/embedded-app-sdk.mjs");
|
const outputPath = path.join(repoRoot, "extensions/discord/assets/embedded-app-sdk.mjs");
|
||||||
|
|
||||||
/** Builds the browser SDK bundle without rewriting an identical generated asset. */
|
/** Builds the browser SDK bundle without rewriting an identical generated asset. */
|
||||||
export async function buildDiscordActivitySdk(params = {}) {
|
export async function buildDiscordActivitySdk(params: DiscordActivitySdkBuildParams = {}) {
|
||||||
const buildImpl = params.build ?? build;
|
const buildImpl = params.build ?? build;
|
||||||
const targetPath = params.outputPath ?? outputPath;
|
const targetPath = params.outputPath ?? outputPath;
|
||||||
const result = await buildImpl({
|
const result = await buildImpl({
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { BUILD_STAMP_FILE, resolveGitHead, writeBuildStamp } from "./lib/local-build-metadata.mjs";
|
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
// Writes the local build stamp and re-exports build metadata helpers.
|
// Writes the local build stamp and re-exports build metadata helpers.
|
||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
import { pathToFileURL } from "node:url";
|
import { pathToFileURL } from "node:url";
|
||||||
import { writeBuildStamp } from "./lib/local-build-metadata.mjs";
|
import { writeBuildStamp } from "./lib/local-build-metadata.mts";
|
||||||
|
|
||||||
export { BUILD_STAMP_FILE, resolveGitHead, writeBuildStamp } from "./lib/local-build-metadata.mjs";
|
export { BUILD_STAMP_FILE, resolveGitHead, writeBuildStamp } from "./lib/local-build-metadata.mts";
|
||||||
|
|
||||||
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
||||||
try {
|
try {
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
// Runs bundled asset build hooks for the Canvas A2UI runtime.
|
// Runs bundled asset build hooks for the Canvas A2UI runtime.
|
||||||
import { pathToFileURL } from "node:url";
|
import { pathToFileURL } from "node:url";
|
||||||
import { runBundledPluginAssetHooks } from "./bundled-plugin-assets.mjs";
|
import { runBundledPluginAssetHooks } from "./bundled-plugin-assets.mts";
|
||||||
|
|
||||||
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||||
await runBundledPluginAssetHooks({ phase: "build", plugins: ["canvas"] });
|
await runBundledPluginAssetHooks({ phase: "build", plugins: ["canvas"] });
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Reads bundled plugin asset hook commands for a build or copy phase.
|
|
||||||
*/
|
|
||||||
export function readBundledPluginAssetHooks(options?: Record<string, unknown>): Promise<
|
|
||||||
{
|
|
||||||
aliases: string[];
|
|
||||||
command: string;
|
|
||||||
packageName: unknown;
|
|
||||||
phase: unknown;
|
|
||||||
pluginDir: string;
|
|
||||||
pluginId: string;
|
|
||||||
}[]
|
|
||||||
>;
|
|
||||||
/**
|
|
||||||
* Runs bundled plugin asset hook commands for the selected phase/plugins.
|
|
||||||
*/
|
|
||||||
export function runBundledPluginAssetHooks(options?: Record<string, unknown>): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Lists declared generated source-tree outputs that differ from the committed bytes.
|
|
||||||
*/
|
|
||||||
export function listStaleGeneratedPluginAssets(options?: Record<string, unknown>): string[];
|
|
||||||
/**
|
|
||||||
* Parses `--phase`, repeated `--plugin`, and `--check` flags for asset hook scripts.
|
|
||||||
*/
|
|
||||||
export function parseBundledPluginAssetArgs(argv: unknown): {
|
|
||||||
check: boolean;
|
|
||||||
phase: unknown;
|
|
||||||
plugins: unknown[];
|
|
||||||
};
|
|
||||||
@@ -1,24 +1,41 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
// Discovers and runs bundled plugin package asset hooks.
|
|
||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { pathToFileURL } from "node:url";
|
import { pathToFileURL } from "node:url";
|
||||||
import { runManagedCommand } from "./lib/managed-child-process.mjs";
|
// Discovers and runs bundled plugin package asset hooks.
|
||||||
|
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||||
|
import { runManagedCommand } from "./lib/managed-child-process.mts";
|
||||||
import { assertRealOutputRoot } from "./lib/output-root-guard.mjs";
|
import { assertRealOutputRoot } from "./lib/output-root-guard.mjs";
|
||||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||||
import { listGeneratedExtensionAssetSources } from "./lib/static-extension-assets.mjs";
|
import { listGeneratedExtensionAssetSources } from "./lib/static-extension-assets.mts";
|
||||||
const rootDir = resolveRepoRoot(import.meta.url);
|
const rootDir = resolveRepoRoot(import.meta.url);
|
||||||
const VALID_PHASES = new Set(["build", "copy"]);
|
const VALID_PHASES = new Set(["build", "copy"]);
|
||||||
// Each complete bundled-plugin asset generator gets the same 10-minute build ceiling.
|
// Each complete bundled-plugin asset generator gets the same 10-minute build ceiling.
|
||||||
const BUNDLED_PLUGIN_ASSET_HOOK_TIMEOUT_MS = 600_000;
|
const BUNDLED_PLUGIN_ASSET_HOOK_TIMEOUT_MS = 600_000;
|
||||||
|
|
||||||
async function readJsonFile(filePath) {
|
type AssetPhase = "build" | "copy";
|
||||||
return JSON.parse(await fs.readFile(filePath, "utf8"));
|
type AssetOptions = {
|
||||||
|
phase?: AssetPhase;
|
||||||
|
plugins?: string[];
|
||||||
|
rootDir?: string;
|
||||||
|
timeoutMs?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isAssetPhase(value: unknown): value is AssetPhase {
|
||||||
|
return typeof value === "string" && VALID_PHASES.has(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pathExists(filePath) {
|
async function readJsonFile(filePath: string) {
|
||||||
|
const value: unknown = JSON.parse(await fs.readFile(filePath, "utf8"));
|
||||||
|
if (!isRecord(value)) {
|
||||||
|
throw new Error(`${filePath} must contain a JSON object`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pathExists(filePath: string) {
|
||||||
try {
|
try {
|
||||||
await fs.stat(filePath);
|
await fs.stat(filePath);
|
||||||
return true;
|
return true;
|
||||||
@@ -27,7 +44,7 @@ async function pathExists(filePath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function packagePluginAliases(packageName) {
|
function packagePluginAliases(packageName: unknown) {
|
||||||
if (typeof packageName !== "string") {
|
if (typeof packageName !== "string") {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -42,7 +59,7 @@ function packagePluginAliases(packageName) {
|
|||||||
return aliases;
|
return aliases;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolvePluginAliases(pluginDir, packageJson) {
|
async function resolvePluginAliases(pluginDir: string, packageJson: Record<string, unknown>) {
|
||||||
const aliases = new Set([path.basename(pluginDir), ...packagePluginAliases(packageJson.name)]);
|
const aliases = new Set([path.basename(pluginDir), ...packagePluginAliases(packageJson.name)]);
|
||||||
const manifestPath = path.join(pluginDir, "openclaw.plugin.json");
|
const manifestPath = path.join(pluginDir, "openclaw.plugin.json");
|
||||||
if (await pathExists(manifestPath)) {
|
if (await pathExists(manifestPath)) {
|
||||||
@@ -54,9 +71,11 @@ async function resolvePluginAliases(pluginDir, packageJson) {
|
|||||||
return aliases;
|
return aliases;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveAssetCommand(packageJson, phase) {
|
function resolveAssetCommand(packageJson: Record<string, unknown>, phase: AssetPhase) {
|
||||||
const assetScripts = packageJson.openclaw?.assetScripts;
|
const assetScripts = isRecord(packageJson.openclaw)
|
||||||
if (!assetScripts || typeof assetScripts !== "object") {
|
? packageJson.openclaw.assetScripts
|
||||||
|
: undefined;
|
||||||
|
if (!isRecord(assetScripts)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const command = assetScripts[phase];
|
const command = assetScripts[phase];
|
||||||
@@ -66,10 +85,10 @@ function resolveAssetCommand(packageJson, phase) {
|
|||||||
/**
|
/**
|
||||||
* Reads bundled plugin asset hook commands for a build or copy phase.
|
* Reads bundled plugin asset hook commands for a build or copy phase.
|
||||||
*/
|
*/
|
||||||
export async function readBundledPluginAssetHooks(options = {}) {
|
export async function readBundledPluginAssetHooks(options: AssetOptions = {}) {
|
||||||
const repoRoot = options.rootDir ?? rootDir;
|
const repoRoot = options.rootDir ?? rootDir;
|
||||||
const phase = options.phase;
|
const phase = options.phase;
|
||||||
if (!VALID_PHASES.has(phase)) {
|
if (!isAssetPhase(phase)) {
|
||||||
throw new Error(`Unsupported bundled plugin asset phase: ${String(phase)}`);
|
throw new Error(`Unsupported bundled plugin asset phase: ${String(phase)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +129,7 @@ export async function readBundledPluginAssetHooks(options = {}) {
|
|||||||
packageName: packageJson.name,
|
packageName: packageJson.name,
|
||||||
phase,
|
phase,
|
||||||
pluginDir,
|
pluginDir,
|
||||||
pluginId: aliases.has(entry.name) ? entry.name : [...aliases][0],
|
pluginId: aliases.has(entry.name) ? entry.name : ([...aliases][0] ?? entry.name),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +139,7 @@ export async function readBundledPluginAssetHooks(options = {}) {
|
|||||||
/**
|
/**
|
||||||
* Runs bundled plugin asset hook commands for the selected phase/plugins.
|
* Runs bundled plugin asset hook commands for the selected phase/plugins.
|
||||||
*/
|
*/
|
||||||
export async function runBundledPluginAssetHooks(options = {}) {
|
export async function runBundledPluginAssetHooks(options: AssetOptions = {}) {
|
||||||
const phase = options.phase;
|
const phase = options.phase;
|
||||||
const timeoutMs = options.timeoutMs ?? BUNDLED_PLUGIN_ASSET_HOOK_TIMEOUT_MS;
|
const timeoutMs = options.timeoutMs ?? BUNDLED_PLUGIN_ASSET_HOOK_TIMEOUT_MS;
|
||||||
const hooks = await readBundledPluginAssetHooks(options);
|
const hooks = await readBundledPluginAssetHooks(options);
|
||||||
@@ -168,7 +187,7 @@ export async function runBundledPluginAssetHooks(options = {}) {
|
|||||||
* selection skips extension suites for packages-only diffs, so this check is
|
* selection skips extension suites for packages-only diffs, so this check is
|
||||||
* the guard that keeps upstream changes from landing stale committed bundles.
|
* the guard that keeps upstream changes from landing stale committed bundles.
|
||||||
*/
|
*/
|
||||||
export function listStaleGeneratedPluginAssets(options = {}) {
|
export function listStaleGeneratedPluginAssets(options: Pick<AssetOptions, "rootDir"> = {}) {
|
||||||
const repoRoot = options.rootDir ?? rootDir;
|
const repoRoot = options.rootDir ?? rootDir;
|
||||||
const sources = listGeneratedExtensionAssetSources({ rootDir: repoRoot });
|
const sources = listGeneratedExtensionAssetSources({ rootDir: repoRoot });
|
||||||
if (sources.length === 0) {
|
if (sources.length === 0) {
|
||||||
@@ -194,10 +213,10 @@ export function listStaleGeneratedPluginAssets(options = {}) {
|
|||||||
/**
|
/**
|
||||||
* Parses `--phase`, repeated `--plugin`, and `--check` flags for asset hook scripts.
|
* Parses `--phase`, repeated `--plugin`, and `--check` flags for asset hook scripts.
|
||||||
*/
|
*/
|
||||||
export function parseBundledPluginAssetArgs(argv) {
|
export function parseBundledPluginAssetArgs(argv: string[]) {
|
||||||
const args = [...argv];
|
const args = [...argv];
|
||||||
const plugins = [];
|
const plugins: string[] = [];
|
||||||
let phase = null;
|
let phase: string | null = null;
|
||||||
let check = false;
|
let check = false;
|
||||||
|
|
||||||
while (args.length > 0) {
|
while (args.length > 0) {
|
||||||
@@ -228,7 +247,7 @@ export function parseBundledPluginAssetArgs(argv) {
|
|||||||
throw new Error(`Unknown bundled plugin asset argument: ${String(arg)}`);
|
throw new Error(`Unknown bundled plugin asset argument: ${String(arg)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!VALID_PHASES.has(phase)) {
|
if (!isAssetPhase(phase)) {
|
||||||
throw new Error(`Expected --phase ${[...VALID_PHASES].join("|")}`);
|
throw new Error(`Expected --phase ${[...VALID_PHASES].join("|")}`);
|
||||||
}
|
}
|
||||||
// The stale-asset scan covers every declared buildOutput, so a filtered run
|
// The stale-asset scan covers every declared buildOutput, so a filtered run
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
export type ChangedLane =
|
|
||||||
| "core"
|
|
||||||
| "coreTests"
|
|
||||||
| "ui"
|
|
||||||
| "extensions"
|
|
||||||
| "extensionTests"
|
|
||||||
| "scripts"
|
|
||||||
| "testRoot"
|
|
||||||
| "apps"
|
|
||||||
| "docs"
|
|
||||||
| "tooling"
|
|
||||||
| "liveDockerTooling"
|
|
||||||
| "bundledChannelConfigMetadata"
|
|
||||||
| "releaseMetadata"
|
|
||||||
| "all";
|
|
||||||
|
|
||||||
export type ChangedLanes = Record<ChangedLane, boolean>;
|
|
||||||
|
|
||||||
export type ChangedLaneResult = {
|
|
||||||
paths: string[];
|
|
||||||
lanes: ChangedLanes;
|
|
||||||
extensionImpactFromCore: boolean;
|
|
||||||
docsOnly: boolean;
|
|
||||||
reasons: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DetectChangedLanesOptions = {
|
|
||||||
packageJsonChangeKind?: "liveDockerTooling" | "tooling" | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createEmptyChangedLanes(): ChangedLanes;
|
|
||||||
export function isChangedLaneTestPath(changedPath: string): boolean;
|
|
||||||
export function detectChangedLanes(
|
|
||||||
changedPaths: string[],
|
|
||||||
options?: DetectChangedLanesOptions,
|
|
||||||
): ChangedLaneResult;
|
|
||||||
export function detectChangedLanesForPaths(params: {
|
|
||||||
paths: string[];
|
|
||||||
base: string;
|
|
||||||
head?: string;
|
|
||||||
staged?: boolean;
|
|
||||||
mergeHeadFirstParent?: boolean;
|
|
||||||
}): ChangedLaneResult;
|
|
||||||
export function listChangedPathsFromGit(params: {
|
|
||||||
base: string;
|
|
||||||
head?: string;
|
|
||||||
includeWorktree?: boolean;
|
|
||||||
cwd?: string;
|
|
||||||
mergeHeadFirstParent?: boolean;
|
|
||||||
}): string[];
|
|
||||||
export function listStagedChangedPaths(cwd?: string): string[];
|
|
||||||
export function hasDeadcodeScannedSource(changedPaths: string[]): boolean;
|
|
||||||
export function isLiveDockerPackageScriptOnlyChange(before: string, after: string): boolean;
|
|
||||||
export function isPackageScriptOnlyChange(before: string, after: string): boolean;
|
|
||||||
|
|
||||||
export const LIVE_DOCKER_AUTH_SHELL_TARGETS: string[];
|
|
||||||
export const RELEASE_METADATA_PATHS: Set<string>;
|
|
||||||
+4
-658
@@ -1,659 +1,5 @@
|
|||||||
import { execFileSync } from "node:child_process";
|
import { runTsxCliShim } from "./lib/tsx-cli-shim.mjs";
|
||||||
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
||||||
import { booleanFlag, parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs";
|
|
||||||
import { getChangedPathFacts, normalizeChangedPath } from "./lib/changed-path-facts.mjs";
|
|
||||||
import { isDirectRunUrl } from "./lib/direct-run.mjs";
|
|
||||||
import { resolveMergeHeadDiffBase } from "./lib/merge-head-diff-base.mjs";
|
|
||||||
|
|
||||||
const GIT_OUTPUT_MAX_BUFFER = 64 * 1024 * 1024;
|
await runTsxCliShim(import.meta.url, {
|
||||||
const IMPLAUSIBLE_NO_MERGE_BASE_DIFF_PATHS = 200;
|
implementation: "./changed-lanes.mts",
|
||||||
const RAW_SYNC_CHANGED_LANES_ENV = "OPENCLAW_CHANGED_LANES_RAW_SYNC";
|
});
|
||||||
// Source files knip's production scan reads. Any edit to one of these can orphan
|
|
||||||
// an export -- including an import-only edit that drops a barrel re-export's last
|
|
||||||
// consumer -- so the scan is selected by path, not by inspecting changed lines.
|
|
||||||
const DEADCODE_SOURCE_PATH_RE = /^(?:src|extensions|ui|packages)\/.+\.[cm]?[jt]sx?$/u;
|
|
||||||
|
|
||||||
/** Returns whether any changed path is production source knip scans. */
|
|
||||||
export function hasDeadcodeScannedSource(changedPaths) {
|
|
||||||
return changedPaths.map(normalizeChangedPath).some((p) => DEADCODE_SOURCE_PATH_RE.test(p));
|
|
||||||
}
|
|
||||||
|
|
||||||
const SCRIPTS_TYPECHECK_PATH_RE =
|
|
||||||
/^(?:scripts\/.*\.(?:[cm]?ts|[cm]?tsx)|tsconfig\.scripts\.json)$/u;
|
|
||||||
const TEST_ROOT_TYPECHECK_PATH_RE =
|
|
||||||
/^(?:test\/(?!fixtures\/).*\.(?:[cm]?ts|[cm]?tsx)|test\/tsconfig\/tsconfig\.test\.root\.json)$/u;
|
|
||||||
/** @internal Shared repository-script contract. */
|
|
||||||
export const LIVE_DOCKER_AUTH_SHELL_TARGETS = [
|
|
||||||
"scripts/lib/live-docker-auth.sh",
|
|
||||||
"scripts/test-live-acp-bind-docker.sh",
|
|
||||||
"scripts/test-live-cli-backend-docker.sh",
|
|
||||||
"scripts/test-live-codex-harness-docker.sh",
|
|
||||||
"scripts/test-live-gateway-models-docker.sh",
|
|
||||||
"scripts/test-live-models-docker.sh",
|
|
||||||
"scripts/test-live-subagent-announce-docker.sh",
|
|
||||||
];
|
|
||||||
const LIVE_DOCKER_TOOLING_PATHS = new Set([
|
|
||||||
...LIVE_DOCKER_AUTH_SHELL_TARGETS,
|
|
||||||
"scripts/test-docker-all.mjs",
|
|
||||||
"src/gateway/gateway-acp-bind.live.test.ts",
|
|
||||||
"src/gateway/live-agent-probes.test.ts",
|
|
||||||
]);
|
|
||||||
const LIVE_DOCKER_PACKAGE_SCRIPT_RE = /^test:docker:live-[\w:-]+$/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;
|
|
||||||
const BUNDLED_CHANNEL_CONFIG_METADATA_PATH_RE =
|
|
||||||
/^(?:src\/config\/(?:bundled-channel-config-metadata\.generated|zod-schema\.[^/]+)\.ts|src\/channels\/plugins\/config-schema\.ts|src\/plugin-sdk\/(?:bundled-channel-config-schema|channel-config-schema)\.ts|src\/plugins\/(?:bundled-dir|public-surface-loader|public-surface-runtime|sdk-alias)\.ts|scripts\/(?:generate-bundled-channel-config-metadata\.ts|load-channel-config-surface\.ts|lib\/(?:bundled-plugin-source-utils|format-generated-module|generated-output-utils)\.mjs)|extensions\/[^/]+\/(?:openclaw\.plugin\.json|package\.json|(?:config|security-contract)-api\.[cm]?[jt]sx?|src\/config-(?:schema(?:-[^/]+)?|surface|ui-hints)\.[cm]?[jt]sx?))$/u;
|
|
||||||
/**
|
|
||||||
* Files whose changes are treated as release metadata only.
|
|
||||||
* @internal Shared repository-script contract.
|
|
||||||
*/
|
|
||||||
export const RELEASE_METADATA_PATHS = new Set([
|
|
||||||
"CHANGELOG.md",
|
|
||||||
"apps/android/CHANGELOG.md",
|
|
||||||
"apps/android/Config/Version.properties",
|
|
||||||
"apps/android/fastlane/metadata/android/en-US/release_notes.txt",
|
|
||||||
"apps/android/version.json",
|
|
||||||
"apps/ios/CHANGELOG.md",
|
|
||||||
"apps/macos/Sources/OpenClaw/Resources/Info.plist",
|
|
||||||
"docs/.generated/config-baseline.counts.json",
|
|
||||||
"docs/.generated/config-baseline.sha256",
|
|
||||||
"docs/install/updating.md",
|
|
||||||
"package.json",
|
|
||||||
]);
|
|
||||||
|
|
||||||
/** @typedef {"core" | "coreTests" | "ui" | "extensions" | "extensionTests" | "scripts" | "testRoot" | "apps" | "docs" | "tooling" | "liveDockerTooling" | "bundledChannelConfigMetadata" | "releaseMetadata" | "all"} ChangedLane */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {{
|
|
||||||
* paths: string[];
|
|
||||||
* lanes: Record<ChangedLane, boolean>;
|
|
||||||
* extensionImpactFromCore: boolean;
|
|
||||||
* docsOnly: boolean;
|
|
||||||
* reasons: string[];
|
|
||||||
* }} ChangedLaneResult
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the default changed-lanes result object.
|
|
||||||
* @internal Directly tested script implementation detail.
|
|
||||||
*/
|
|
||||||
export function createEmptyChangedLanes() {
|
|
||||||
return {
|
|
||||||
core: false,
|
|
||||||
coreTests: false,
|
|
||||||
ui: false,
|
|
||||||
extensions: false,
|
|
||||||
extensionTests: false,
|
|
||||||
scripts: false,
|
|
||||||
testRoot: false,
|
|
||||||
apps: false,
|
|
||||||
docs: false,
|
|
||||||
tooling: false,
|
|
||||||
liveDockerTooling: false,
|
|
||||||
bundledChannelConfigMetadata: false,
|
|
||||||
releaseMetadata: false,
|
|
||||||
all: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isChangedLaneTestPath(changedPath) {
|
|
||||||
return getChangedPathFacts(normalizeChangedPath(changedPath)).isChangedLaneTest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Classifies a list of changed paths into docs, app, extension, core, and tooling lanes.
|
|
||||||
* @internal Shared repository-script contract.
|
|
||||||
*/
|
|
||||||
export function detectChangedLanes(changedPaths, options = {}) {
|
|
||||||
const paths = [...new Set(changedPaths.map(normalizeChangedPath).filter(Boolean))]
|
|
||||||
.toSorted((left, right) => left.localeCompare(right))
|
|
||||||
.filter((changedPath) => changedPath !== "--");
|
|
||||||
const lanes = createEmptyChangedLanes();
|
|
||||||
const reasons = [];
|
|
||||||
let extensionImpactFromCore = false;
|
|
||||||
let hasNonDocs = false;
|
|
||||||
const packageJsonIsLiveDockerTooling =
|
|
||||||
paths.includes("package.json") && options.packageJsonChangeKind === "liveDockerTooling";
|
|
||||||
const packageJsonIsTooling =
|
|
||||||
paths.includes("package.json") && options.packageJsonChangeKind === "tooling";
|
|
||||||
|
|
||||||
if (paths.length === 0) {
|
|
||||||
reasons.push("no changed paths");
|
|
||||||
return { paths, lanes, extensionImpactFromCore: false, docsOnly: false, reasons };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!packageJsonIsLiveDockerTooling &&
|
|
||||||
!packageJsonIsTooling &&
|
|
||||||
paths.some((changedPath) => RELEASE_METADATA_PATHS.has(changedPath)) &&
|
|
||||||
paths.every((changedPath) => RELEASE_METADATA_PATHS.has(changedPath))
|
|
||||||
) {
|
|
||||||
lanes.releaseMetadata = true;
|
|
||||||
lanes.docs = paths.some((changedPath) => getChangedPathFacts(changedPath).surface === "docs");
|
|
||||||
for (const changedPath of paths) {
|
|
||||||
reasons.push(`${changedPath}: release metadata`);
|
|
||||||
}
|
|
||||||
return { paths, lanes, extensionImpactFromCore: false, docsOnly: false, reasons };
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const changedPath of paths) {
|
|
||||||
const facts = getChangedPathFacts(changedPath);
|
|
||||||
if (BUNDLED_CHANNEL_CONFIG_METADATA_PATH_RE.test(changedPath)) {
|
|
||||||
lanes.bundledChannelConfigMetadata = true;
|
|
||||||
reasons.push(`${changedPath}: bundled channel config metadata input`);
|
|
||||||
}
|
|
||||||
if (SCRIPTS_TYPECHECK_PATH_RE.test(changedPath)) {
|
|
||||||
lanes.scripts = true;
|
|
||||||
}
|
|
||||||
if (TEST_ROOT_TYPECHECK_PATH_RE.test(changedPath)) {
|
|
||||||
lanes.testRoot = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (facts.surface === "docs") {
|
|
||||||
lanes.docs = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasNonDocs = true;
|
|
||||||
|
|
||||||
if (changedPath === "package.json" && packageJsonIsLiveDockerTooling) {
|
|
||||||
lanes.liveDockerTooling = true;
|
|
||||||
reasons.push(`${changedPath}: live Docker package scripts`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changedPath === "package.json" && packageJsonIsTooling) {
|
|
||||||
lanes.tooling = true;
|
|
||||||
reasons.push(`${changedPath}: package scripts`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LIVE_DOCKER_TOOLING_PATHS.has(changedPath)) {
|
|
||||||
lanes.liveDockerTooling = true;
|
|
||||||
reasons.push(`${changedPath}: live Docker tooling surface`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (facts.surface === "rootGlobal") {
|
|
||||||
lanes.all = true;
|
|
||||||
extensionImpactFromCore = true;
|
|
||||||
reasons.push(`${changedPath}: root config/package surface`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PUBLIC_EXTENSION_CONTRACT_RE.test(changedPath)) {
|
|
||||||
lanes.core = true;
|
|
||||||
lanes.coreTests = true;
|
|
||||||
lanes.extensions = true;
|
|
||||||
lanes.extensionTests = true;
|
|
||||||
extensionImpactFromCore = true;
|
|
||||||
reasons.push(`${changedPath}: public core/plugin contract affects extensions`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (facts.surface === "extension") {
|
|
||||||
if (facts.isChangedLaneTest) {
|
|
||||||
lanes.extensionTests = true;
|
|
||||||
reasons.push(`${changedPath}: extension test`);
|
|
||||||
} else {
|
|
||||||
lanes.extensions = true;
|
|
||||||
lanes.extensionTests = true;
|
|
||||||
reasons.push(`${changedPath}: extension production`);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (facts.surface === "source" || facts.surface === "package") {
|
|
||||||
if (facts.isChangedLaneTest) {
|
|
||||||
lanes.coreTests = true;
|
|
||||||
reasons.push(`${changedPath}: core test`);
|
|
||||||
} else {
|
|
||||||
lanes.core = true;
|
|
||||||
lanes.coreTests = true;
|
|
||||||
reasons.push(`${changedPath}: core production`);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (facts.surface === "ui") {
|
|
||||||
if (facts.isChangedLaneTest) {
|
|
||||||
lanes.coreTests = true;
|
|
||||||
reasons.push(`${changedPath}: UI test`);
|
|
||||||
} else {
|
|
||||||
lanes.ui = true;
|
|
||||||
lanes.coreTests = true;
|
|
||||||
reasons.push(`${changedPath}: UI production`);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (facts.surface === "app") {
|
|
||||||
lanes.apps = true;
|
|
||||||
reasons.push(`${changedPath}: app surface`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (facts.surface === "rootTest" || facts.surface === "testFixture") {
|
|
||||||
lanes.tooling = true;
|
|
||||||
reasons.push(`${changedPath}: root test/support surface`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (facts.surface === "rootTooling") {
|
|
||||||
lanes.tooling = true;
|
|
||||||
reasons.push(`${changedPath}: tooling surface`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (facts.surface === "legacyRootAsset") {
|
|
||||||
lanes.tooling = true;
|
|
||||||
reasons.push(`${changedPath}: legacy root asset cleanup`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
lanes.all = true;
|
|
||||||
extensionImpactFromCore = true;
|
|
||||||
reasons.push(`${changedPath}: unknown surface; fail-safe all lanes`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
lanes,
|
|
||||||
extensionImpactFromCore,
|
|
||||||
docsOnly: lanes.docs && !hasNonDocs,
|
|
||||||
reasons,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Classifies changed paths with optional package.json before/after contents.
|
|
||||||
* @internal Shared repository-script contract.
|
|
||||||
*/
|
|
||||||
export function detectChangedLanesForPaths(params) {
|
|
||||||
const base = params.staged
|
|
||||||
? params.base
|
|
||||||
: resolveMergeHeadDiffBase({
|
|
||||||
base: params.base,
|
|
||||||
head: params.head ?? "HEAD",
|
|
||||||
maxBuffer: GIT_OUTPUT_MAX_BUFFER,
|
|
||||||
preferFirstParent: params.mergeHeadFirstParent === true,
|
|
||||||
});
|
|
||||||
const packageJsonChangeKind = params.paths.includes("package.json")
|
|
||||||
? classifyPackageJsonChangeFromGit({
|
|
||||||
base,
|
|
||||||
head: params.head,
|
|
||||||
staged: params.staged,
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
return detectChangedLanes(params.paths, { packageJsonChangeKind });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
const base = resolveMergeHeadDiffBase({
|
|
||||||
base: params.base,
|
|
||||||
head,
|
|
||||||
cwd,
|
|
||||||
maxBuffer: GIT_OUTPUT_MAX_BUFFER,
|
|
||||||
preferFirstParent: params.mergeHeadFirstParent === true,
|
|
||||||
});
|
|
||||||
if (!base) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
let rangePaths;
|
|
||||||
let noMergeBase = false;
|
|
||||||
try {
|
|
||||||
// oxlint-disable-next-line typescript/no-base-to-string, typescript/restrict-template-expressions -- resolveMergeHeadDiffBase returns a git ref string when present.
|
|
||||||
rangePaths = runGitNameOnlyDiff([`${base}...${head}`], cwd);
|
|
||||||
} catch (error) {
|
|
||||||
if (!isGitNoMergeBaseError(error)) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
noMergeBase = true;
|
|
||||||
// oxlint-disable-next-line typescript/no-base-to-string, typescript/restrict-template-expressions -- resolveMergeHeadDiffBase returns a git ref string when present.
|
|
||||||
rangePaths = runGitNameOnlyDiff([`${base}..${head}`], cwd);
|
|
||||||
}
|
|
||||||
if (params.includeWorktree === false) {
|
|
||||||
return rangePaths;
|
|
||||||
}
|
|
||||||
const worktreePaths = [
|
|
||||||
...runGitNameOnlyDiff(["--cached", "--diff-filter=ACMRD"], cwd),
|
|
||||||
...runGitNameOnlyDiff(["--diff-filter=ACMRD"], cwd),
|
|
||||||
...runGitLsFiles(["--others", "--exclude-standard"], cwd),
|
|
||||||
];
|
|
||||||
// Raw Crabbox syncs can have unrelated synthetic refs; prefer the synced
|
|
||||||
// worktree delta instead of turning that into an accidental whole-repo gate.
|
|
||||||
if (
|
|
||||||
noMergeBase &&
|
|
||||||
process.env[RAW_SYNC_CHANGED_LANES_ENV] === "1" &&
|
|
||||||
worktreePaths.length > 0 &&
|
|
||||||
rangePaths.length > IMPLAUSIBLE_NO_MERGE_BASE_DIFF_PATHS
|
|
||||||
) {
|
|
||||||
rangePaths = [];
|
|
||||||
}
|
|
||||||
return [...new Set([...rangePaths, ...worktreePaths])].toSorted((left, right) =>
|
|
||||||
left.localeCompare(right),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function runGitNameOnlyDiff(extraArgs, cwd = process.cwd()) {
|
|
||||||
const output = execFileSync("git", ["diff", "--name-only", ...extraArgs], {
|
|
||||||
cwd,
|
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
|
||||||
encoding: "utf8",
|
|
||||||
maxBuffer: GIT_OUTPUT_MAX_BUFFER,
|
|
||||||
});
|
|
||||||
return output.split("\n").map(normalizeChangedPath).filter(Boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isGitNoMergeBaseError(error) {
|
|
||||||
const text = [
|
|
||||||
error?.message,
|
|
||||||
error?.stderr?.toString?.("utf8"),
|
|
||||||
Array.isArray(error?.output)
|
|
||||||
? error.output.map((value) => value?.toString?.("utf8")).join("\n")
|
|
||||||
: "",
|
|
||||||
].join("\n");
|
|
||||||
return text.includes("no merge base");
|
|
||||||
}
|
|
||||||
|
|
||||||
function runGitLsFiles(extraArgs, cwd = process.cwd()) {
|
|
||||||
const output = execFileSync("git", ["ls-files", ...extraArgs], {
|
|
||||||
cwd,
|
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
|
||||||
encoding: "utf8",
|
|
||||||
maxBuffer: GIT_OUTPUT_MAX_BUFFER,
|
|
||||||
});
|
|
||||||
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,
|
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
|
||||||
encoding: "utf8",
|
|
||||||
maxBuffer: GIT_OUTPUT_MAX_BUFFER,
|
|
||||||
});
|
|
||||||
return output.split("\n").map(normalizeChangedPath).filter(Boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Classifies package.json script-only changes from git content.
|
|
||||||
*/
|
|
||||||
function classifyPackageJsonChangeFromGit(params) {
|
|
||||||
try {
|
|
||||||
const { before, after } = readPackageJsonBeforeAfter(params);
|
|
||||||
if (isLiveDockerPackageScriptOnlyChange(before, after)) {
|
|
||||||
return "liveDockerTooling";
|
|
||||||
}
|
|
||||||
return isPackageScriptOnlyChange(before, after) ? "tooling" : null;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether package scripts changed only live Docker script entries.
|
|
||||||
* @internal Directly tested script implementation detail.
|
|
||||||
*/
|
|
||||||
export function isLiveDockerPackageScriptOnlyChange(before, after) {
|
|
||||||
const beforePackage = JSON.parse(before);
|
|
||||||
const afterPackage = JSON.parse(after);
|
|
||||||
const beforeAllowed = extractLiveDockerPackageScripts(beforePackage);
|
|
||||||
const afterAllowed = extractLiveDockerPackageScripts(afterPackage);
|
|
||||||
const beforeStripped = stripLiveDockerPackageScripts(beforePackage);
|
|
||||||
const afterStripped = stripLiveDockerPackageScripts(afterPackage);
|
|
||||||
|
|
||||||
return (
|
|
||||||
stableJson(beforeStripped) === stableJson(afterStripped) &&
|
|
||||||
stableJson(beforeAllowed) !== stableJson(afterAllowed)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether package.json changes are limited to scripts.
|
|
||||||
* @internal Directly tested script implementation detail.
|
|
||||||
*/
|
|
||||||
export function isPackageScriptOnlyChange(before, after) {
|
|
||||||
const beforePackage = JSON.parse(before);
|
|
||||||
const afterPackage = JSON.parse(after);
|
|
||||||
const beforeScripts = extractPackageScripts(beforePackage);
|
|
||||||
const afterScripts = extractPackageScripts(afterPackage);
|
|
||||||
const beforeStripped = stripPackageScripts(beforePackage);
|
|
||||||
const afterStripped = stripPackageScripts(afterPackage);
|
|
||||||
|
|
||||||
return (
|
|
||||||
stableJson(beforeStripped) === stableJson(afterStripped) &&
|
|
||||||
stableJson(beforeScripts) !== stableJson(afterScripts)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function readPackageJsonBeforeAfter(params) {
|
|
||||||
const before = readGitText(params.staged ? "HEAD" : params.base, "package.json");
|
|
||||||
if (params.staged) {
|
|
||||||
return { before, after: readGitText("INDEX", "package.json") };
|
|
||||||
}
|
|
||||||
|
|
||||||
let after = readGitText(params.head ?? "HEAD", "package.json");
|
|
||||||
if (params.includeWorktree !== false && existsSync("package.json")) {
|
|
||||||
const worktree = readGitText("WORKTREE", "package.json");
|
|
||||||
if (worktree !== after) {
|
|
||||||
after = worktree;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { before, after };
|
|
||||||
}
|
|
||||||
|
|
||||||
function readGitText(ref, filePath) {
|
|
||||||
if (ref === "WORKTREE") {
|
|
||||||
return readFileSync(filePath, "utf8");
|
|
||||||
}
|
|
||||||
const spec = ref === "INDEX" ? `:${filePath}` : `${ref}:${filePath}`;
|
|
||||||
return execFileSync("git", ["show", spec], {
|
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
|
||||||
encoding: "utf8",
|
|
||||||
maxBuffer: 16 * 1024 * 1024,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractLiveDockerPackageScripts(packageJson) {
|
|
||||||
const scripts = packageJson?.scripts;
|
|
||||||
if (!scripts || typeof scripts !== "object" || Array.isArray(scripts)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(scripts).filter(([name]) => LIVE_DOCKER_PACKAGE_SCRIPT_RE.test(name)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripLiveDockerPackageScripts(packageJson) {
|
|
||||||
const clone = structuredClone(packageJson);
|
|
||||||
const scripts = clone.scripts;
|
|
||||||
if (!scripts || typeof scripts !== "object" || Array.isArray(scripts)) {
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
for (const name of Object.keys(scripts)) {
|
|
||||||
if (LIVE_DOCKER_PACKAGE_SCRIPT_RE.test(name)) {
|
|
||||||
delete scripts[name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractPackageScripts(packageJson) {
|
|
||||||
const scripts = packageJson?.scripts;
|
|
||||||
return scripts && typeof scripts === "object" && !Array.isArray(scripts) ? scripts : {};
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripPackageScripts(packageJson) {
|
|
||||||
const clone = structuredClone(packageJson);
|
|
||||||
delete clone.scripts;
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stableJson(value) {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return `[${value.map(stableJson).join(",")}]`;
|
|
||||||
}
|
|
||||||
if (value && typeof value === "object") {
|
|
||||||
return `{${Object.keys(value)
|
|
||||||
.toSorted((left, right) => left.localeCompare(right))
|
|
||||||
.map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
|
|
||||||
.join(",")}}`;
|
|
||||||
}
|
|
||||||
return JSON.stringify(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes changed-lane booleans to the GitHub Actions output file.
|
|
||||||
*/
|
|
||||||
function writeChangedLaneGitHubOutput(result, outputPath = process.env.GITHUB_OUTPUT) {
|
|
||||||
if (!outputPath) {
|
|
||||||
throw new Error("GITHUB_OUTPUT is required");
|
|
||||||
}
|
|
||||||
for (const [lane, enabled] of Object.entries(result.lanes)) {
|
|
||||||
appendFileSync(outputPath, `run_${toSnakeCase(lane)}=${String(enabled)}\n`, "utf8");
|
|
||||||
}
|
|
||||||
appendFileSync(outputPath, `docs_only=${result.docsOnly}\n`, "utf8");
|
|
||||||
appendFileSync(
|
|
||||||
outputPath,
|
|
||||||
`extension_impact_from_core=${result.extensionImpactFromCore}\n`,
|
|
||||||
"utf8",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toSnakeCase(value) {
|
|
||||||
return value.replace(/[A-Z]/gu, (match) => `_${match.toLowerCase()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseArgs(argv) {
|
|
||||||
const separatorIndex = argv.indexOf("--");
|
|
||||||
const flagArgv = separatorIndex === -1 ? argv : argv.slice(0, separatorIndex);
|
|
||||||
const explicitPaths = separatorIndex === -1 ? [] : argv.slice(separatorIndex + 1);
|
|
||||||
const args = {
|
|
||||||
base: "origin/main",
|
|
||||||
head: "HEAD",
|
|
||||||
staged: false,
|
|
||||||
mergeHeadFirstParent: false,
|
|
||||||
json: false,
|
|
||||||
githubOutput: false,
|
|
||||||
help: false,
|
|
||||||
paths: [],
|
|
||||||
};
|
|
||||||
const parsed = parseFlagArgs(
|
|
||||||
flagArgv,
|
|
||||||
args,
|
|
||||||
[
|
|
||||||
stringFlag("--base", "base"),
|
|
||||||
stringFlag("--head", "head"),
|
|
||||||
booleanFlag("--staged", "staged"),
|
|
||||||
booleanFlag("--merge-head-first-parent", "mergeHeadFirstParent"),
|
|
||||||
booleanFlag("--json", "json"),
|
|
||||||
booleanFlag("--github-output", "githubOutput"),
|
|
||||||
booleanFlag("--help", "help"),
|
|
||||||
booleanFlag("-h", "help"),
|
|
||||||
],
|
|
||||||
{
|
|
||||||
onUnhandledArg(arg, target) {
|
|
||||||
if (arg.startsWith("-")) {
|
|
||||||
throw new Error(`Unknown option: ${arg}`);
|
|
||||||
}
|
|
||||||
target.paths.push(arg);
|
|
||||||
return "handled";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
parsed.paths.push(...explicitPaths);
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
function printUsage() {
|
|
||||||
console.log(
|
|
||||||
[
|
|
||||||
"Usage: node scripts/changed-lanes.mjs [options] [-- <paths...>]",
|
|
||||||
"",
|
|
||||||
"Options:",
|
|
||||||
" --base <ref> Base ref for changed paths (default: origin/main)",
|
|
||||||
" --head <ref> Head ref for changed paths (default: HEAD)",
|
|
||||||
" --staged Inspect staged changes",
|
|
||||||
" --json Print JSON result",
|
|
||||||
" --github-output Append GitHub output variables",
|
|
||||||
" -h, --help Show this help",
|
|
||||||
].join("\n"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isDirectRun() {
|
|
||||||
return isDirectRunUrl(process.argv[1], import.meta.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
function printHuman(result) {
|
|
||||||
const enabled = Object.entries(result.lanes)
|
|
||||||
.filter(([, value]) => value)
|
|
||||||
.map(([lane]) => lane);
|
|
||||||
console.log(`lanes: ${enabled.length > 0 ? enabled.join(", ") : "none"}`);
|
|
||||||
if (result.docsOnly) {
|
|
||||||
console.log("docs-only: true");
|
|
||||||
}
|
|
||||||
if (result.extensionImpactFromCore) {
|
|
||||||
console.log("extension-impact-from-core: true");
|
|
||||||
}
|
|
||||||
if (result.paths.length > 0) {
|
|
||||||
console.log("paths:");
|
|
||||||
for (const changedPath of result.paths) {
|
|
||||||
console.log(`- ${changedPath}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result.reasons.length > 0) {
|
|
||||||
console.log("reasons:");
|
|
||||||
for (const reason of result.reasons) {
|
|
||||||
console.log(`- ${reason}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDirectRun()) {
|
|
||||||
let args;
|
|
||||||
try {
|
|
||||||
args = parseArgs(process.argv.slice(2));
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error instanceof Error ? error.message : String(error));
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
if (args.help) {
|
|
||||||
printUsage();
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
const paths =
|
|
||||||
args.paths.length > 0
|
|
||||||
? args.paths
|
|
||||||
: args.staged
|
|
||||||
? listStagedChangedPaths()
|
|
||||||
: listChangedPathsFromGit({
|
|
||||||
base: args.base,
|
|
||||||
head: args.head,
|
|
||||||
mergeHeadFirstParent: args.mergeHeadFirstParent,
|
|
||||||
});
|
|
||||||
const result = detectChangedLanesForPaths({
|
|
||||||
paths,
|
|
||||||
base: args.base,
|
|
||||||
head: args.head,
|
|
||||||
staged: args.staged,
|
|
||||||
mergeHeadFirstParent: args.mergeHeadFirstParent,
|
|
||||||
});
|
|
||||||
if (args.githubOutput) {
|
|
||||||
writeChangedLaneGitHubOutput(result);
|
|
||||||
}
|
|
||||||
if (args.json) {
|
|
||||||
console.log(JSON.stringify(result, null, 2));
|
|
||||||
} else if (!args.githubOutput) {
|
|
||||||
printHuman(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,710 @@
|
|||||||
|
import { execFileSync } from "node:child_process";
|
||||||
|
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
||||||
|
import { booleanFlag, parseFlagArgs, stringFlag } from "./lib/arg-utils.mts";
|
||||||
|
import { getChangedPathFacts, normalizeChangedPath } from "./lib/changed-path-facts.mjs";
|
||||||
|
import { isDirectRunUrl } from "./lib/direct-run.mjs";
|
||||||
|
import { resolveMergeHeadDiffBase } from "./lib/merge-head-diff-base.mjs";
|
||||||
|
|
||||||
|
const GIT_OUTPUT_MAX_BUFFER = 64 * 1024 * 1024;
|
||||||
|
const IMPLAUSIBLE_NO_MERGE_BASE_DIFF_PATHS = 200;
|
||||||
|
const RAW_SYNC_CHANGED_LANES_ENV = "OPENCLAW_CHANGED_LANES_RAW_SYNC";
|
||||||
|
|
||||||
|
// The CLI is invoked from temporary Git repositories, outside workspace package resolution.
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source files knip's production scan reads. Any edit to one of these can orphan
|
||||||
|
// an export -- including an import-only edit that drops a barrel re-export's last
|
||||||
|
// consumer -- so the scan is selected by path, not by inspecting changed lines.
|
||||||
|
const DEADCODE_SOURCE_PATH_RE = /^(?:src|extensions|ui|packages)\/.+\.[cm]?[jt]sx?$/u;
|
||||||
|
|
||||||
|
/** Returns whether any changed path is production source knip scans. */
|
||||||
|
export function hasDeadcodeScannedSource(changedPaths: string[]): boolean {
|
||||||
|
return changedPaths.map(normalizeChangedPath).some((p) => DEADCODE_SOURCE_PATH_RE.test(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
const SCRIPTS_TYPECHECK_PATH_RE =
|
||||||
|
/^(?:scripts\/.*\.(?:[cm]?ts|[cm]?tsx)|tsconfig\.scripts\.json)$/u;
|
||||||
|
const TEST_ROOT_TYPECHECK_PATH_RE =
|
||||||
|
/^(?:test\/(?!fixtures\/).*\.(?:[cm]?ts|[cm]?tsx)|test\/tsconfig\/tsconfig\.test\.root\.json)$/u;
|
||||||
|
/** @internal Shared repository-script contract. */
|
||||||
|
export const LIVE_DOCKER_AUTH_SHELL_TARGETS = [
|
||||||
|
"scripts/lib/live-docker-auth.sh",
|
||||||
|
"scripts/test-live-acp-bind-docker.sh",
|
||||||
|
"scripts/test-live-cli-backend-docker.sh",
|
||||||
|
"scripts/test-live-codex-harness-docker.sh",
|
||||||
|
"scripts/test-live-gateway-models-docker.sh",
|
||||||
|
"scripts/test-live-models-docker.sh",
|
||||||
|
"scripts/test-live-subagent-announce-docker.sh",
|
||||||
|
];
|
||||||
|
const LIVE_DOCKER_TOOLING_PATHS = new Set([
|
||||||
|
...LIVE_DOCKER_AUTH_SHELL_TARGETS,
|
||||||
|
"scripts/test-docker-all.mjs",
|
||||||
|
"scripts/test-docker-all.mts",
|
||||||
|
"src/gateway/gateway-acp-bind.live.test.ts",
|
||||||
|
"src/gateway/live-agent-probes.test.ts",
|
||||||
|
]);
|
||||||
|
const LIVE_DOCKER_PACKAGE_SCRIPT_RE = /^test:docker:live-[\w:-]+$/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\.mts$|scripts\/generate-plugin-sdk-api-baseline\.ts$)/u;
|
||||||
|
const BUNDLED_CHANNEL_CONFIG_METADATA_PATH_RE =
|
||||||
|
/^(?:src\/config\/(?:bundled-channel-config-metadata\.generated|zod-schema\.[^/]+)\.ts|src\/channels\/plugins\/config-schema\.ts|src\/plugin-sdk\/(?:bundled-channel-config-schema|channel-config-schema)\.ts|src\/plugins\/(?:bundled-dir|public-surface-loader|public-surface-runtime|sdk-alias)\.ts|scripts\/(?:generate-bundled-channel-config-metadata\.ts|load-channel-config-surface\.ts|lib\/(?:bundled-plugin-source-utils|format-generated-module|generated-output-utils)\.mts)|extensions\/[^/]+\/(?:openclaw\.plugin\.json|package\.json|(?:config|security-contract)-api\.[cm]?[jt]sx?|src\/config-(?:schema(?:-[^/]+)?|surface|ui-hints)\.[cm]?[jt]sx?))$/u;
|
||||||
|
/**
|
||||||
|
* Files whose changes are treated as release metadata only.
|
||||||
|
* @internal Shared repository-script contract.
|
||||||
|
*/
|
||||||
|
export const RELEASE_METADATA_PATHS = new Set([
|
||||||
|
"CHANGELOG.md",
|
||||||
|
"apps/android/CHANGELOG.md",
|
||||||
|
"apps/android/Config/Version.properties",
|
||||||
|
"apps/android/fastlane/metadata/android/en-US/release_notes.txt",
|
||||||
|
"apps/android/version.json",
|
||||||
|
"apps/ios/CHANGELOG.md",
|
||||||
|
"apps/macos/Sources/OpenClaw/Resources/Info.plist",
|
||||||
|
"docs/.generated/config-baseline.counts.json",
|
||||||
|
"docs/.generated/config-baseline.sha256",
|
||||||
|
"docs/install/updating.md",
|
||||||
|
"package.json",
|
||||||
|
]);
|
||||||
|
|
||||||
|
type ChangedLanes = ReturnType<typeof createEmptyChangedLanes>;
|
||||||
|
|
||||||
|
export type ChangedLaneResult = {
|
||||||
|
paths: string[];
|
||||||
|
lanes: ChangedLanes;
|
||||||
|
extensionImpactFromCore: boolean;
|
||||||
|
docsOnly: boolean;
|
||||||
|
reasons: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type DetectChangedLanesOptions = {
|
||||||
|
packageJsonChangeKind?: "liveDockerTooling" | "tooling" | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PackageJsonGitParams = {
|
||||||
|
base: string;
|
||||||
|
head?: string;
|
||||||
|
staged?: boolean;
|
||||||
|
includeWorktree?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the default changed-lanes result object.
|
||||||
|
* @internal Directly tested script implementation detail.
|
||||||
|
*/
|
||||||
|
export function createEmptyChangedLanes() {
|
||||||
|
return {
|
||||||
|
core: false,
|
||||||
|
coreTests: false,
|
||||||
|
ui: false,
|
||||||
|
extensions: false,
|
||||||
|
extensionTests: false,
|
||||||
|
scripts: false,
|
||||||
|
testRoot: false,
|
||||||
|
apps: false,
|
||||||
|
docs: false,
|
||||||
|
tooling: false,
|
||||||
|
liveDockerTooling: false,
|
||||||
|
bundledChannelConfigMetadata: false,
|
||||||
|
releaseMetadata: false,
|
||||||
|
all: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isChangedLaneTestPath(changedPath: string) {
|
||||||
|
return getChangedPathFacts(normalizeChangedPath(changedPath)).isChangedLaneTest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classifies a list of changed paths into docs, app, extension, core, and tooling lanes.
|
||||||
|
* @internal Shared repository-script contract.
|
||||||
|
*/
|
||||||
|
export function detectChangedLanes(
|
||||||
|
changedPaths: string[],
|
||||||
|
options: DetectChangedLanesOptions = {},
|
||||||
|
): ChangedLaneResult {
|
||||||
|
const paths = [...new Set(changedPaths.map(normalizeChangedPath).filter(Boolean))]
|
||||||
|
.toSorted((left, right) => left.localeCompare(right))
|
||||||
|
.filter((changedPath) => changedPath !== "--");
|
||||||
|
const lanes = createEmptyChangedLanes();
|
||||||
|
const reasons = [];
|
||||||
|
let extensionImpactFromCore = false;
|
||||||
|
let hasNonDocs = false;
|
||||||
|
const packageJsonIsLiveDockerTooling =
|
||||||
|
paths.includes("package.json") && options.packageJsonChangeKind === "liveDockerTooling";
|
||||||
|
const packageJsonIsTooling =
|
||||||
|
paths.includes("package.json") && options.packageJsonChangeKind === "tooling";
|
||||||
|
|
||||||
|
if (paths.length === 0) {
|
||||||
|
reasons.push("no changed paths");
|
||||||
|
return { paths, lanes, extensionImpactFromCore: false, docsOnly: false, reasons };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!packageJsonIsLiveDockerTooling &&
|
||||||
|
!packageJsonIsTooling &&
|
||||||
|
paths.some((changedPath) => RELEASE_METADATA_PATHS.has(changedPath)) &&
|
||||||
|
paths.every((changedPath) => RELEASE_METADATA_PATHS.has(changedPath))
|
||||||
|
) {
|
||||||
|
lanes.releaseMetadata = true;
|
||||||
|
lanes.docs = paths.some((changedPath) => getChangedPathFacts(changedPath).surface === "docs");
|
||||||
|
for (const changedPath of paths) {
|
||||||
|
reasons.push(`${changedPath}: release metadata`);
|
||||||
|
}
|
||||||
|
return { paths, lanes, extensionImpactFromCore: false, docsOnly: false, reasons };
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const changedPath of paths) {
|
||||||
|
const facts = getChangedPathFacts(changedPath);
|
||||||
|
if (BUNDLED_CHANNEL_CONFIG_METADATA_PATH_RE.test(changedPath)) {
|
||||||
|
lanes.bundledChannelConfigMetadata = true;
|
||||||
|
reasons.push(`${changedPath}: bundled channel config metadata input`);
|
||||||
|
}
|
||||||
|
if (SCRIPTS_TYPECHECK_PATH_RE.test(changedPath)) {
|
||||||
|
lanes.scripts = true;
|
||||||
|
}
|
||||||
|
if (TEST_ROOT_TYPECHECK_PATH_RE.test(changedPath)) {
|
||||||
|
lanes.testRoot = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (facts.surface === "docs") {
|
||||||
|
lanes.docs = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNonDocs = true;
|
||||||
|
|
||||||
|
if (changedPath === "package.json" && packageJsonIsLiveDockerTooling) {
|
||||||
|
lanes.liveDockerTooling = true;
|
||||||
|
reasons.push(`${changedPath}: live Docker package scripts`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changedPath === "package.json" && packageJsonIsTooling) {
|
||||||
|
lanes.tooling = true;
|
||||||
|
reasons.push(`${changedPath}: package scripts`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LIVE_DOCKER_TOOLING_PATHS.has(changedPath)) {
|
||||||
|
lanes.liveDockerTooling = true;
|
||||||
|
reasons.push(`${changedPath}: live Docker tooling surface`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (facts.surface === "rootGlobal") {
|
||||||
|
lanes.all = true;
|
||||||
|
extensionImpactFromCore = true;
|
||||||
|
reasons.push(`${changedPath}: root config/package surface`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PUBLIC_EXTENSION_CONTRACT_RE.test(changedPath)) {
|
||||||
|
lanes.core = true;
|
||||||
|
lanes.coreTests = true;
|
||||||
|
lanes.extensions = true;
|
||||||
|
lanes.extensionTests = true;
|
||||||
|
extensionImpactFromCore = true;
|
||||||
|
reasons.push(`${changedPath}: public core/plugin contract affects extensions`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (facts.surface === "extension") {
|
||||||
|
if (facts.isChangedLaneTest) {
|
||||||
|
lanes.extensionTests = true;
|
||||||
|
reasons.push(`${changedPath}: extension test`);
|
||||||
|
} else {
|
||||||
|
lanes.extensions = true;
|
||||||
|
lanes.extensionTests = true;
|
||||||
|
reasons.push(`${changedPath}: extension production`);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (facts.surface === "source" || facts.surface === "package") {
|
||||||
|
if (facts.isChangedLaneTest) {
|
||||||
|
lanes.coreTests = true;
|
||||||
|
reasons.push(`${changedPath}: core test`);
|
||||||
|
} else {
|
||||||
|
lanes.core = true;
|
||||||
|
lanes.coreTests = true;
|
||||||
|
reasons.push(`${changedPath}: core production`);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (facts.surface === "ui") {
|
||||||
|
if (facts.isChangedLaneTest) {
|
||||||
|
lanes.coreTests = true;
|
||||||
|
reasons.push(`${changedPath}: UI test`);
|
||||||
|
} else {
|
||||||
|
lanes.ui = true;
|
||||||
|
lanes.coreTests = true;
|
||||||
|
reasons.push(`${changedPath}: UI production`);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (facts.surface === "app") {
|
||||||
|
lanes.apps = true;
|
||||||
|
reasons.push(`${changedPath}: app surface`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (facts.surface === "rootTest" || facts.surface === "testFixture") {
|
||||||
|
lanes.tooling = true;
|
||||||
|
reasons.push(`${changedPath}: root test/support surface`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (facts.surface === "rootTooling") {
|
||||||
|
lanes.tooling = true;
|
||||||
|
reasons.push(`${changedPath}: tooling surface`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (facts.surface === "legacyRootAsset") {
|
||||||
|
lanes.tooling = true;
|
||||||
|
reasons.push(`${changedPath}: legacy root asset cleanup`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
lanes.all = true;
|
||||||
|
extensionImpactFromCore = true;
|
||||||
|
reasons.push(`${changedPath}: unknown surface; fail-safe all lanes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
paths,
|
||||||
|
lanes,
|
||||||
|
extensionImpactFromCore,
|
||||||
|
docsOnly: lanes.docs && !hasNonDocs,
|
||||||
|
reasons,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classifies changed paths with optional package.json before/after contents.
|
||||||
|
* @internal Shared repository-script contract.
|
||||||
|
*/
|
||||||
|
export function detectChangedLanesForPaths(params: {
|
||||||
|
paths: string[];
|
||||||
|
base: string;
|
||||||
|
head?: string;
|
||||||
|
staged?: boolean;
|
||||||
|
mergeHeadFirstParent?: boolean;
|
||||||
|
}): ChangedLaneResult {
|
||||||
|
const resolvedBase = params.staged
|
||||||
|
? params.base
|
||||||
|
: resolveMergeHeadDiffBase({
|
||||||
|
base: params.base,
|
||||||
|
head: params.head ?? "HEAD",
|
||||||
|
maxBuffer: GIT_OUTPUT_MAX_BUFFER,
|
||||||
|
preferFirstParent: params.mergeHeadFirstParent === true,
|
||||||
|
});
|
||||||
|
const base = typeof resolvedBase === "string" ? resolvedBase : "";
|
||||||
|
const packageJsonChangeKind = params.paths.includes("package.json")
|
||||||
|
? classifyPackageJsonChangeFromGit({
|
||||||
|
base,
|
||||||
|
head: params.head,
|
||||||
|
staged: params.staged,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
return detectChangedLanes(params.paths, { packageJsonChangeKind });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists changed paths from git for a base/head comparison.
|
||||||
|
*/
|
||||||
|
export function listChangedPathsFromGit(params: {
|
||||||
|
base: string;
|
||||||
|
head?: string;
|
||||||
|
includeWorktree?: boolean;
|
||||||
|
cwd?: string;
|
||||||
|
mergeHeadFirstParent?: boolean;
|
||||||
|
}): string[] {
|
||||||
|
const head = params.head ?? "HEAD";
|
||||||
|
const cwd = params.cwd ?? process.cwd();
|
||||||
|
const resolvedBase = resolveMergeHeadDiffBase({
|
||||||
|
base: params.base,
|
||||||
|
head,
|
||||||
|
cwd,
|
||||||
|
maxBuffer: GIT_OUTPUT_MAX_BUFFER,
|
||||||
|
preferFirstParent: params.mergeHeadFirstParent === true,
|
||||||
|
});
|
||||||
|
const base = typeof resolvedBase === "string" ? resolvedBase : "";
|
||||||
|
if (!base) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let rangePaths: string[];
|
||||||
|
let noMergeBase = false;
|
||||||
|
try {
|
||||||
|
rangePaths = runGitNameOnlyDiff([`${base}...${head}`], cwd);
|
||||||
|
} catch (error) {
|
||||||
|
if (!isGitNoMergeBaseError(error)) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
noMergeBase = true;
|
||||||
|
rangePaths = runGitNameOnlyDiff([`${base}..${head}`], cwd);
|
||||||
|
}
|
||||||
|
if (params.includeWorktree === false) {
|
||||||
|
return rangePaths;
|
||||||
|
}
|
||||||
|
const worktreePaths = [
|
||||||
|
...runGitNameOnlyDiff(["--cached", "--diff-filter=ACMRD"], cwd),
|
||||||
|
...runGitNameOnlyDiff(["--diff-filter=ACMRD"], cwd),
|
||||||
|
...runGitLsFiles(["--others", "--exclude-standard"], cwd),
|
||||||
|
];
|
||||||
|
// Raw Crabbox syncs can have unrelated synthetic refs; prefer the synced
|
||||||
|
// worktree delta instead of turning that into an accidental whole-repo gate.
|
||||||
|
if (
|
||||||
|
noMergeBase &&
|
||||||
|
process.env[RAW_SYNC_CHANGED_LANES_ENV] === "1" &&
|
||||||
|
worktreePaths.length > 0 &&
|
||||||
|
rangePaths.length > IMPLAUSIBLE_NO_MERGE_BASE_DIFF_PATHS
|
||||||
|
) {
|
||||||
|
rangePaths = [];
|
||||||
|
}
|
||||||
|
return [...new Set([...rangePaths, ...worktreePaths])].toSorted((left, right) =>
|
||||||
|
left.localeCompare(right),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runGitNameOnlyDiff(extraArgs: string[], cwd = process.cwd()): string[] {
|
||||||
|
const output = execFileSync("git", ["diff", "--name-only", ...extraArgs], {
|
||||||
|
cwd,
|
||||||
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
|
encoding: "utf8",
|
||||||
|
maxBuffer: GIT_OUTPUT_MAX_BUFFER,
|
||||||
|
});
|
||||||
|
return output.split("\n").map(normalizeChangedPath).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function gitOutputText(value: unknown) {
|
||||||
|
return typeof value === "string" || Buffer.isBuffer(value) ? value.toString() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGitNoMergeBaseError(error: unknown) {
|
||||||
|
const errorRecord = isRecord(error) ? error : null;
|
||||||
|
const output = Array.isArray(errorRecord?.output)
|
||||||
|
? errorRecord.output.map(gitOutputText).join("\n")
|
||||||
|
: "";
|
||||||
|
const text = [
|
||||||
|
error instanceof Error ? error.message : "",
|
||||||
|
gitOutputText(errorRecord?.stderr),
|
||||||
|
output,
|
||||||
|
].join("\n");
|
||||||
|
return text.includes("no merge base");
|
||||||
|
}
|
||||||
|
|
||||||
|
function runGitLsFiles(extraArgs: string[], cwd = process.cwd()): string[] {
|
||||||
|
const output = execFileSync("git", ["ls-files", ...extraArgs], {
|
||||||
|
cwd,
|
||||||
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
|
encoding: "utf8",
|
||||||
|
maxBuffer: GIT_OUTPUT_MAX_BUFFER,
|
||||||
|
});
|
||||||
|
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,
|
||||||
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
|
encoding: "utf8",
|
||||||
|
maxBuffer: GIT_OUTPUT_MAX_BUFFER,
|
||||||
|
});
|
||||||
|
return output.split("\n").map(normalizeChangedPath).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classifies package.json script-only changes from git content.
|
||||||
|
*/
|
||||||
|
function classifyPackageJsonChangeFromGit(params: PackageJsonGitParams) {
|
||||||
|
try {
|
||||||
|
const { before, after } = readPackageJsonBeforeAfter(params);
|
||||||
|
if (isLiveDockerPackageScriptOnlyChange(before, after)) {
|
||||||
|
return "liveDockerTooling";
|
||||||
|
}
|
||||||
|
return isPackageScriptOnlyChange(before, after) ? "tooling" : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether package scripts changed only live Docker script entries.
|
||||||
|
* @internal Directly tested script implementation detail.
|
||||||
|
*/
|
||||||
|
export function isLiveDockerPackageScriptOnlyChange(before: string, after: string): boolean {
|
||||||
|
const beforePackage = parsePackageJson(before);
|
||||||
|
const afterPackage = parsePackageJson(after);
|
||||||
|
if (!beforePackage || !afterPackage) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const beforeAllowed = extractLiveDockerPackageScripts(beforePackage);
|
||||||
|
const afterAllowed = extractLiveDockerPackageScripts(afterPackage);
|
||||||
|
const beforeStripped = stripLiveDockerPackageScripts(beforePackage);
|
||||||
|
const afterStripped = stripLiveDockerPackageScripts(afterPackage);
|
||||||
|
|
||||||
|
return (
|
||||||
|
stableJson(beforeStripped) === stableJson(afterStripped) &&
|
||||||
|
stableJson(beforeAllowed) !== stableJson(afterAllowed)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether package.json changes are limited to scripts.
|
||||||
|
* @internal Directly tested script implementation detail.
|
||||||
|
*/
|
||||||
|
export function isPackageScriptOnlyChange(before: string, after: string): boolean {
|
||||||
|
const beforePackage = parsePackageJson(before);
|
||||||
|
const afterPackage = parsePackageJson(after);
|
||||||
|
if (!beforePackage || !afterPackage) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const beforeScripts = extractPackageScripts(beforePackage);
|
||||||
|
const afterScripts = extractPackageScripts(afterPackage);
|
||||||
|
const beforeStripped = stripPackageScripts(beforePackage);
|
||||||
|
const afterStripped = stripPackageScripts(afterPackage);
|
||||||
|
|
||||||
|
return (
|
||||||
|
stableJson(beforeStripped) === stableJson(afterStripped) &&
|
||||||
|
stableJson(beforeScripts) !== stableJson(afterScripts)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePackageJson(value: string) {
|
||||||
|
const parsed: unknown = JSON.parse(value);
|
||||||
|
return isRecord(parsed) ? parsed : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPackageJsonBeforeAfter(params: PackageJsonGitParams) {
|
||||||
|
const before = readGitText(params.staged ? "HEAD" : params.base, "package.json");
|
||||||
|
if (params.staged) {
|
||||||
|
return { before, after: readGitText("INDEX", "package.json") };
|
||||||
|
}
|
||||||
|
|
||||||
|
let after = readGitText(params.head ?? "HEAD", "package.json");
|
||||||
|
if (params.includeWorktree !== false && existsSync("package.json")) {
|
||||||
|
const worktree = readGitText("WORKTREE", "package.json");
|
||||||
|
if (worktree !== after) {
|
||||||
|
after = worktree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { before, after };
|
||||||
|
}
|
||||||
|
|
||||||
|
function readGitText(ref: string, filePath: string) {
|
||||||
|
if (ref === "WORKTREE") {
|
||||||
|
return readFileSync(filePath, "utf8");
|
||||||
|
}
|
||||||
|
const spec = ref === "INDEX" ? `:${filePath}` : `${ref}:${filePath}`;
|
||||||
|
return execFileSync("git", ["show", spec], {
|
||||||
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
|
encoding: "utf8",
|
||||||
|
maxBuffer: 16 * 1024 * 1024,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractLiveDockerPackageScripts(packageJson: Record<string, unknown>) {
|
||||||
|
const scripts = packageJson.scripts;
|
||||||
|
if (!isRecord(scripts)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(scripts).filter(([name]) => LIVE_DOCKER_PACKAGE_SCRIPT_RE.test(name)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripLiveDockerPackageScripts(packageJson: Record<string, unknown>) {
|
||||||
|
const clone = structuredClone(packageJson);
|
||||||
|
const scripts = clone.scripts;
|
||||||
|
if (!isRecord(scripts)) {
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
for (const name of Object.keys(scripts)) {
|
||||||
|
if (LIVE_DOCKER_PACKAGE_SCRIPT_RE.test(name)) {
|
||||||
|
delete scripts[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractPackageScripts(packageJson: Record<string, unknown>) {
|
||||||
|
const scripts = packageJson.scripts;
|
||||||
|
return isRecord(scripts) ? scripts : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripPackageScripts(packageJson: Record<string, unknown>) {
|
||||||
|
const clone = structuredClone(packageJson);
|
||||||
|
delete clone.scripts;
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stableJson(value: unknown): string {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return `[${value.map(stableJson).join(",")}]`;
|
||||||
|
}
|
||||||
|
if (isRecord(value)) {
|
||||||
|
return `{${Object.keys(value)
|
||||||
|
.toSorted((left, right) => left.localeCompare(right))
|
||||||
|
.map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
|
||||||
|
.join(",")}}`;
|
||||||
|
}
|
||||||
|
return JSON.stringify(value) ?? "undefined";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes changed-lane booleans to the GitHub Actions output file.
|
||||||
|
*/
|
||||||
|
function writeChangedLaneGitHubOutput(
|
||||||
|
result: ChangedLaneResult,
|
||||||
|
outputPath = process.env.GITHUB_OUTPUT,
|
||||||
|
) {
|
||||||
|
if (!outputPath) {
|
||||||
|
throw new Error("GITHUB_OUTPUT is required");
|
||||||
|
}
|
||||||
|
for (const [lane, enabled] of Object.entries(result.lanes)) {
|
||||||
|
appendFileSync(outputPath, `run_${toSnakeCase(lane)}=${String(enabled)}\n`, "utf8");
|
||||||
|
}
|
||||||
|
appendFileSync(outputPath, `docs_only=${result.docsOnly}\n`, "utf8");
|
||||||
|
appendFileSync(
|
||||||
|
outputPath,
|
||||||
|
`extension_impact_from_core=${result.extensionImpactFromCore}\n`,
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toSnakeCase(value: string) {
|
||||||
|
return value.replace(/[A-Z]/gu, (match) => `_${match.toLowerCase()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(argv: string[]) {
|
||||||
|
const separatorIndex = argv.indexOf("--");
|
||||||
|
const flagArgv = separatorIndex === -1 ? argv : argv.slice(0, separatorIndex);
|
||||||
|
const explicitPaths = separatorIndex === -1 ? [] : argv.slice(separatorIndex + 1);
|
||||||
|
const args = {
|
||||||
|
base: "origin/main",
|
||||||
|
head: "HEAD",
|
||||||
|
staged: false,
|
||||||
|
mergeHeadFirstParent: false,
|
||||||
|
json: false,
|
||||||
|
githubOutput: false,
|
||||||
|
help: false,
|
||||||
|
paths: new Array<string>(),
|
||||||
|
};
|
||||||
|
const parsed = parseFlagArgs(
|
||||||
|
flagArgv,
|
||||||
|
args,
|
||||||
|
[
|
||||||
|
stringFlag("--base", "base"),
|
||||||
|
stringFlag("--head", "head"),
|
||||||
|
booleanFlag("--staged", "staged"),
|
||||||
|
booleanFlag("--merge-head-first-parent", "mergeHeadFirstParent"),
|
||||||
|
booleanFlag("--json", "json"),
|
||||||
|
booleanFlag("--github-output", "githubOutput"),
|
||||||
|
booleanFlag("--help", "help"),
|
||||||
|
booleanFlag("-h", "help"),
|
||||||
|
],
|
||||||
|
{
|
||||||
|
onUnhandledArg(arg, target) {
|
||||||
|
if (arg.startsWith("-")) {
|
||||||
|
throw new Error(`Unknown option: ${arg}`);
|
||||||
|
}
|
||||||
|
target.paths.push(arg);
|
||||||
|
return "handled";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
parsed.paths.push(...explicitPaths);
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printUsage() {
|
||||||
|
console.log(
|
||||||
|
[
|
||||||
|
"Usage: node scripts/changed-lanes.mjs [options] [-- <paths...>]",
|
||||||
|
"",
|
||||||
|
"Options:",
|
||||||
|
" --base <ref> Base ref for changed paths (default: origin/main)",
|
||||||
|
" --head <ref> Head ref for changed paths (default: HEAD)",
|
||||||
|
" --staged Inspect staged changes",
|
||||||
|
" --json Print JSON result",
|
||||||
|
" --github-output Append GitHub output variables",
|
||||||
|
" -h, --help Show this help",
|
||||||
|
].join("\n"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDirectRun() {
|
||||||
|
return isDirectRunUrl(process.argv[1], import.meta.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function printHuman(result: ChangedLaneResult) {
|
||||||
|
const enabled = Object.entries(result.lanes)
|
||||||
|
.filter(([, value]) => value)
|
||||||
|
.map(([lane]) => lane);
|
||||||
|
console.log(`lanes: ${enabled.length > 0 ? enabled.join(", ") : "none"}`);
|
||||||
|
if (result.docsOnly) {
|
||||||
|
console.log("docs-only: true");
|
||||||
|
}
|
||||||
|
if (result.extensionImpactFromCore) {
|
||||||
|
console.log("extension-impact-from-core: true");
|
||||||
|
}
|
||||||
|
if (result.paths.length > 0) {
|
||||||
|
console.log("paths:");
|
||||||
|
for (const changedPath of result.paths) {
|
||||||
|
console.log(`- ${changedPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result.reasons.length > 0) {
|
||||||
|
console.log("reasons:");
|
||||||
|
for (const reason of result.reasons) {
|
||||||
|
console.log(`- ${reason}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDirectRun()) {
|
||||||
|
let args;
|
||||||
|
try {
|
||||||
|
args = parseArgs(process.argv.slice(2));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error instanceof Error ? error.message : String(error));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (args.help) {
|
||||||
|
printUsage();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
const paths =
|
||||||
|
args.paths.length > 0
|
||||||
|
? args.paths
|
||||||
|
: args.staged
|
||||||
|
? listStagedChangedPaths()
|
||||||
|
: listChangedPathsFromGit({
|
||||||
|
base: args.base,
|
||||||
|
head: args.head,
|
||||||
|
mergeHeadFirstParent: args.mergeHeadFirstParent,
|
||||||
|
});
|
||||||
|
const result = detectChangedLanesForPaths({
|
||||||
|
paths,
|
||||||
|
base: args.base,
|
||||||
|
head: args.head,
|
||||||
|
staged: args.staged,
|
||||||
|
mergeHeadFirstParent: args.mergeHeadFirstParent,
|
||||||
|
});
|
||||||
|
if (args.githubOutput) {
|
||||||
|
writeChangedLaneGitHubOutput(result);
|
||||||
|
}
|
||||||
|
if (args.json) {
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
|
} else if (!args.githubOutput) {
|
||||||
|
printHuman(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
export type BuiltPluginControlPlaneModule = {
|
|
||||||
pluginId: string;
|
|
||||||
kind: string;
|
|
||||||
relativePath: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BuiltPluginControlPlaneModuleFailure = BuiltPluginControlPlaneModule & {
|
|
||||||
error: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function listBuiltPluginControlPlaneModules(params?: {
|
|
||||||
rootDir?: string;
|
|
||||||
}): BuiltPluginControlPlaneModule[];
|
|
||||||
|
|
||||||
export function probeBuiltPluginControlPlaneModules(
|
|
||||||
modules: BuiltPluginControlPlaneModule[],
|
|
||||||
params?: { rootDir?: string; timeoutMs?: number },
|
|
||||||
): BuiltPluginControlPlaneModuleFailure[];
|
|
||||||
|
|
||||||
export function verifyBuiltPluginControlPlaneModules(params?: {
|
|
||||||
rootDir?: string;
|
|
||||||
timeoutMs?: number;
|
|
||||||
}): void;
|
|
||||||
+47
-12
@@ -8,9 +8,29 @@ import { pathToFileURL } from "node:url";
|
|||||||
import ts from "typescript";
|
import ts from "typescript";
|
||||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||||
|
|
||||||
|
// The live-updater fixture copies this script without workspace packages.
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
type BuiltPluginControlPlaneModule = {
|
||||||
|
pluginId: string;
|
||||||
|
kind: string;
|
||||||
|
relativePath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BuiltPluginControlPlaneModuleFailure = BuiltPluginControlPlaneModule & {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ProbeParams = {
|
||||||
|
rootDir?: string;
|
||||||
|
timeoutMs?: number;
|
||||||
|
};
|
||||||
|
|
||||||
const ROOT = resolveRepoRoot(import.meta.url);
|
const ROOT = resolveRepoRoot(import.meta.url);
|
||||||
const DIRECT_CONTRACT_FILES = ["contract-api.js", "doctor-contract-api.js"];
|
const DIRECT_CONTRACT_FILES = ["contract-api.js", "doctor-contract-api.js"];
|
||||||
const LEGACY_SETUP_PROPERTIES = new Map([
|
const LEGACY_SETUP_PROPERTIES = new Map<string, string>([
|
||||||
["legacyStateMigrations", "channel-legacy-state-migrations"],
|
["legacyStateMigrations", "channel-legacy-state-migrations"],
|
||||||
["legacySessionSurface", "channel-legacy-session-surface"],
|
["legacySessionSurface", "channel-legacy-session-surface"],
|
||||||
["legacySessionSurfaces", "channel-legacy-session-surface"],
|
["legacySessionSurfaces", "channel-legacy-session-surface"],
|
||||||
@@ -36,15 +56,15 @@ for (const target of targets) {
|
|||||||
process.stdout.write("\n${PROBE_RESULT_MARKER}" + JSON.stringify({ failures }));
|
process.stdout.write("\n${PROBE_RESULT_MARKER}" + JSON.stringify({ failures }));
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function propertyNameText(name) {
|
function propertyNameText(name: ts.PropertyName) {
|
||||||
return ts.isIdentifier(name) || ts.isStringLiteralLike(name) ? name.text : null;
|
return ts.isIdentifier(name) || ts.isStringLiteralLike(name) ? name.text : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function listLegacySetupModuleSpecifiers(setupEntryPath) {
|
function listLegacySetupModuleSpecifiers(setupEntryPath: string) {
|
||||||
const source = fs.readFileSync(setupEntryPath, "utf8");
|
const source = fs.readFileSync(setupEntryPath, "utf8");
|
||||||
const sourceFile = ts.createSourceFile(setupEntryPath, source, ts.ScriptTarget.Latest, true);
|
const sourceFile = ts.createSourceFile(setupEntryPath, source, ts.ScriptTarget.Latest, true);
|
||||||
const specifiers = [];
|
const specifiers: Array<{ kind: string; specifier: string }> = [];
|
||||||
const visit = (node) => {
|
const visit = (node: ts.Node): void => {
|
||||||
if (ts.isPropertyAssignment(node) && ts.isObjectLiteralExpression(node.initializer)) {
|
if (ts.isPropertyAssignment(node) && ts.isObjectLiteralExpression(node.initializer)) {
|
||||||
const kind = LEGACY_SETUP_PROPERTIES.get(propertyNameText(node.name));
|
const kind = LEGACY_SETUP_PROPERTIES.get(propertyNameText(node.name));
|
||||||
if (kind) {
|
if (kind) {
|
||||||
@@ -68,13 +88,13 @@ function listLegacySetupModuleSpecifiers(setupEntryPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Lists exact built doctor, contract, and channel legacy migration artifacts. */
|
/** Lists exact built doctor, contract, and channel legacy migration artifacts. */
|
||||||
export function listBuiltPluginControlPlaneModules(params = {}) {
|
export function listBuiltPluginControlPlaneModules(params: { rootDir?: string } = {}) {
|
||||||
const rootDir = path.resolve(params.rootDir ?? ROOT);
|
const rootDir = path.resolve(params.rootDir ?? ROOT);
|
||||||
const extensionsDir = path.join(rootDir, "dist", "extensions");
|
const extensionsDir = path.join(rootDir, "dist", "extensions");
|
||||||
if (!fs.existsSync(extensionsDir)) {
|
if (!fs.existsSync(extensionsDir)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const modules = new Map();
|
const modules = new Map<string, BuiltPluginControlPlaneModule>();
|
||||||
for (const entry of fs
|
for (const entry of fs
|
||||||
.readdirSync(extensionsDir, { withFileTypes: true })
|
.readdirSync(extensionsDir, { withFileTypes: true })
|
||||||
.filter((candidate) => candidate.isDirectory())
|
.filter((candidate) => candidate.isDirectory())
|
||||||
@@ -112,7 +132,10 @@ export function listBuiltPluginControlPlaneModules(params = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Loads every selected artifact in one timeout-bounded native-require child. */
|
/** Loads every selected artifact in one timeout-bounded native-require child. */
|
||||||
export function probeBuiltPluginControlPlaneModules(modules, params = {}) {
|
export function probeBuiltPluginControlPlaneModules(
|
||||||
|
modules: BuiltPluginControlPlaneModule[],
|
||||||
|
params: ProbeParams = {},
|
||||||
|
) {
|
||||||
if (modules.length === 0) {
|
if (modules.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -135,12 +158,24 @@ export function probeBuiltPluginControlPlaneModules(modules, params = {}) {
|
|||||||
`built plugin control-plane native-require probe exited ${String(result.status)} without a result`,
|
`built plugin control-plane native-require probe exited ${String(result.status)} without a result`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const payload = JSON.parse(result.stdout.slice(markerIndex + PROBE_RESULT_MARKER.length));
|
const payload: unknown = JSON.parse(
|
||||||
return Array.isArray(payload.failures) ? payload.failures : [];
|
result.stdout.slice(markerIndex + PROBE_RESULT_MARKER.length),
|
||||||
|
);
|
||||||
|
if (!isRecord(payload) || !Array.isArray(payload.failures)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return payload.failures.filter(
|
||||||
|
(failure): failure is BuiltPluginControlPlaneModuleFailure =>
|
||||||
|
isRecord(failure) &&
|
||||||
|
typeof failure.pluginId === "string" &&
|
||||||
|
typeof failure.kind === "string" &&
|
||||||
|
typeof failure.relativePath === "string" &&
|
||||||
|
typeof failure.error === "string",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fails the build when a generated plugin control-plane module cannot be required natively. */
|
/** Fails the build when a generated plugin control-plane module cannot be required natively. */
|
||||||
export function verifyBuiltPluginControlPlaneModules(params = {}) {
|
export function verifyBuiltPluginControlPlaneModules(params: ProbeParams = {}) {
|
||||||
const modules = listBuiltPluginControlPlaneModules(params);
|
const modules = listBuiltPluginControlPlaneModules(params);
|
||||||
const failures = probeBuiltPluginControlPlaneModules(modules, params);
|
const failures = probeBuiltPluginControlPlaneModules(modules, params);
|
||||||
if (failures.length > 0) {
|
if (failures.length > 0) {
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
import type { ChangedLaneResult } from "./changed-lanes.mjs";
|
|
||||||
|
|
||||||
export type ChangedCheckCommand = {
|
|
||||||
name: string;
|
|
||||||
args: string[];
|
|
||||||
bin?: string;
|
|
||||||
env?: NodeJS.ProcessEnv;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ChangedCheckPlan = {
|
|
||||||
commands: ChangedCheckCommand[];
|
|
||||||
summary: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ChangedCheckPlanOptions = {
|
|
||||||
env?: NodeJS.ProcessEnv;
|
|
||||||
staged?: boolean;
|
|
||||||
base?: string;
|
|
||||||
head?: string;
|
|
||||||
platform?: NodeJS.Platform;
|
|
||||||
swiftlintAvailable?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TargetedLintOptions = {
|
|
||||||
fileExists?: (path: string) => boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TargetedLintCommand = Required<
|
|
||||||
Pick<ChangedCheckCommand, "name" | "bin" | "args" | "env">
|
|
||||||
>;
|
|
||||||
|
|
||||||
export function createChangedCheckChildEnv(baseEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
||||||
export function changedCheckLocalDependenciesReady(cwd?: string): boolean;
|
|
||||||
export function changedCheckRequiresRemote(result?: ChangedLaneResult): boolean;
|
|
||||||
export function shouldDelegateChangedCheckToCrabbox(
|
|
||||||
argv?: string[],
|
|
||||||
env?: NodeJS.ProcessEnv,
|
|
||||||
options?: { cwd?: string; result?: ChangedLaneResult; diffRefsReady?: boolean },
|
|
||||||
): boolean;
|
|
||||||
export function buildChangedCheckCrabboxArgs(argv?: string[], options?: { cwd?: string }): string[];
|
|
||||||
export function delegationFailedBeforeRunning(output: string): boolean;
|
|
||||||
export function shouldRunNpmLockGuard(paths: string[]): boolean;
|
|
||||||
export function shouldRunPromptSnapshotCheck(paths: string[]): boolean;
|
|
||||||
export function shouldRunPromptSnapshotOwnerTest(paths: string[]): boolean;
|
|
||||||
export function shouldRunControlUiI18nVerify(paths: string[]): boolean;
|
|
||||||
export function shouldRunRuntimeSidecarBaselineCheck(paths: string[]): boolean;
|
|
||||||
export function shouldRunDoctorContractOwnerTests(paths: string[]): boolean;
|
|
||||||
export function shouldRunSqliteSessionSchemaBaselineCheck(paths: string[]): boolean;
|
|
||||||
export function shouldRunPluginSdkApiBaselineCheck(paths: string[]): boolean;
|
|
||||||
export function shouldRunPluginSdkSurfaceChecks(paths: string[]): boolean;
|
|
||||||
export function shouldRunDeprecationHygieneChecks(paths: string[]): boolean;
|
|
||||||
export function shouldRunCanvasA2uiNativeResourceCheck(paths: string[]): boolean;
|
|
||||||
export function shouldRunAppcastOwnerTest(paths: string[]): boolean;
|
|
||||||
export function shouldRunTestTempCreationReport(paths: string[]): boolean;
|
|
||||||
export function createNpmLockGuardCommand(paths: string[]): ChangedCheckCommand | null;
|
|
||||||
export function createChangedCheckPlan(
|
|
||||||
result: ChangedLaneResult,
|
|
||||||
options?: ChangedCheckPlanOptions,
|
|
||||||
): ChangedCheckPlan;
|
|
||||||
export function createTargetedCoreLintCommand(
|
|
||||||
paths: string[],
|
|
||||||
env?: NodeJS.ProcessEnv,
|
|
||||||
options?: TargetedLintOptions,
|
|
||||||
): TargetedLintCommand | null;
|
|
||||||
export function createTargetedExtensionLintCommand(
|
|
||||||
paths: string[],
|
|
||||||
env?: NodeJS.ProcessEnv,
|
|
||||||
options?: TargetedLintOptions,
|
|
||||||
): TargetedLintCommand | null;
|
|
||||||
export function createTargetedScriptLintCommand(
|
|
||||||
paths: string[],
|
|
||||||
env?: NodeJS.ProcessEnv,
|
|
||||||
options?: TargetedLintOptions,
|
|
||||||
): TargetedLintCommand | null;
|
|
||||||
export function createPnpmManagedCommand<T extends ChangedCheckCommand>(
|
|
||||||
command: T,
|
|
||||||
env?: NodeJS.ProcessEnv,
|
|
||||||
): T & { bin: string; env: NodeJS.ProcessEnv };
|
|
||||||
export function cleanupCorepackPnpmShimDir(): void;
|
|
||||||
+4
-1191
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,20 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Reports whether a handle is forbidden in changelog thanks text.
|
|
||||||
*/
|
|
||||||
export function isForbiddenChangelogThanksHandle(
|
|
||||||
handle: unknown,
|
|
||||||
options?: Record<string, unknown>,
|
|
||||||
): boolean;
|
|
||||||
/**
|
|
||||||
* Reports whether a handle needs a separate human credit.
|
|
||||||
*/
|
|
||||||
export function requiresExplicitHumanChangelogThanks(handle: unknown): boolean;
|
|
||||||
/**
|
|
||||||
* Finds changelog lines that thank forbidden handles.
|
|
||||||
*/
|
|
||||||
export function findForbiddenChangelogThanks(content: unknown): unknown;
|
|
||||||
/**
|
|
||||||
* Runs the changelog attribution check.
|
|
||||||
*/
|
|
||||||
export function main(argv?: string[]): Promise<void>;
|
|
||||||
@@ -1,159 +1,4 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
import { runTsxCliShim } from "./lib/tsx-cli-shim.mjs";
|
||||||
|
|
||||||
// Rejects changelog thanks entries that credit bots or internal handles.
|
await runTsxCliShim(import.meta.url, { implementation: "./check-changelog-attributions.mts" });
|
||||||
import fs from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exact handles that changelog thanks entries must not credit.
|
|
||||||
*/
|
|
||||||
const FORBIDDEN_CHANGELOG_THANKS_HANDLES = [
|
|
||||||
"codex",
|
|
||||||
"openclaw",
|
|
||||||
"steipete",
|
|
||||||
"clawsweeper",
|
|
||||||
"openclaw-clawsweeper",
|
|
||||||
"clawsweeper[bot]",
|
|
||||||
"openclaw-clawsweeper[bot]",
|
|
||||||
];
|
|
||||||
/**
|
|
||||||
* Handle prefixes that identify forbidden changelog thanks credits.
|
|
||||||
*/
|
|
||||||
const FORBIDDEN_CHANGELOG_THANKS_HANDLE_PREFIXES = ["app/"];
|
|
||||||
/**
|
|
||||||
* Handle suffixes that identify forbidden changelog thanks credits.
|
|
||||||
*/
|
|
||||||
const FORBIDDEN_CHANGELOG_THANKS_HANDLE_SUFFIXES = ["[bot]"];
|
|
||||||
/**
|
|
||||||
* Handles that require an explicit human credit instead.
|
|
||||||
*/
|
|
||||||
const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLES = new Set([
|
|
||||||
"clawsweeper",
|
|
||||||
"openclaw-clawsweeper",
|
|
||||||
"clawsweeper[bot]",
|
|
||||||
"openclaw-clawsweeper[bot]",
|
|
||||||
]);
|
|
||||||
/**
|
|
||||||
* Handle prefixes that require explicit human credit instead.
|
|
||||||
*/
|
|
||||||
const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLE_PREFIXES = ["app/"];
|
|
||||||
/**
|
|
||||||
* Handle suffixes that require explicit human credit instead.
|
|
||||||
*/
|
|
||||||
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();
|
|
||||||
if (normalized === "" || normalized === "null") {
|
|
||||||
// Empty/null input is not a GitHub handle, but the shell query path may pass it through.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
FORBIDDEN_CHANGELOG_THANKS_HANDLES.includes(normalized) ||
|
|
||||||
FORBIDDEN_CHANGELOG_THANKS_HANDLE_PREFIXES.some((prefix) => normalized.startsWith(prefix)) ||
|
|
||||||
FORBIDDEN_CHANGELOG_THANKS_HANDLE_SUFFIXES.some((suffix) => normalized.endsWith(suffix))
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (strictBotHandle) {
|
|
||||||
// PR-author checks should not reject a real human whose login merely contains a bot keyword.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reports whether a handle needs a separate human credit.
|
|
||||||
*/
|
|
||||||
export function requiresExplicitHumanChangelogThanks(handle) {
|
|
||||||
const normalized = handle.toLowerCase();
|
|
||||||
if (normalized === "" || normalized === "null") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLES.has(normalized) ||
|
|
||||||
CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLE_PREFIXES.some((prefix) =>
|
|
||||||
normalized.startsWith(prefix),
|
|
||||||
) ||
|
|
||||||
CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLE_SUFFIXES.some((suffix) =>
|
|
||||||
normalized.endsWith(suffix),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds changelog lines that thank forbidden handles.
|
|
||||||
*/
|
|
||||||
export function findForbiddenChangelogThanks(content) {
|
|
||||||
return content
|
|
||||||
.split(/\r?\n/u)
|
|
||||||
.map((text, index) => {
|
|
||||||
if (!THANKS_PATTERN.test(text)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// A single changelog line may thank multiple handles; scan all of them.
|
|
||||||
for (const match of text.matchAll(THANKED_HANDLE_PATTERN)) {
|
|
||||||
if (isForbiddenChangelogThanksHandle(match[1])) {
|
|
||||||
return { line: index + 1, handle: match[1].toLowerCase(), text };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.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] ?? "", {
|
|
||||||
strictBotHandle: true,
|
|
||||||
})
|
|
||||||
? 0
|
|
||||||
: 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argv[0] === "--requires-explicit-human-thanks") {
|
|
||||||
process.exitCode = requiresExplicitHumanChangelogThanks(argv[1] ?? "") ? 0 : 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const changelogPath = argv[0] ?? "CHANGELOG.md";
|
|
||||||
const absolutePath = path.resolve(process.cwd(), changelogPath);
|
|
||||||
const content = fs.readFileSync(absolutePath, "utf8");
|
|
||||||
const violations = findForbiddenChangelogThanks(content);
|
|
||||||
if (violations.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error("Forbidden changelog thanks attribution:");
|
|
||||||
for (const violation of violations) {
|
|
||||||
const relativePath = path.relative(process.cwd(), absolutePath) || changelogPath;
|
|
||||||
console.error(`- ${relativePath}:${violation.line} uses Thanks @${violation.handle}`);
|
|
||||||
}
|
|
||||||
console.error(
|
|
||||||
`Use a credited external GitHub username instead of ${FORBIDDEN_CHANGELOG_THANKS_HANDLES.map(
|
|
||||||
(handle) => `@${handle}`,
|
|
||||||
).join(", ")}.`,
|
|
||||||
);
|
|
||||||
process.exitCode = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
||||||
main().catch(
|
|
||||||
/** @param {unknown} error */ (error) => {
|
|
||||||
console.error(error);
|
|
||||||
process.exit(1);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
#!/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.
|
||||||
|
*/
|
||||||
|
const FORBIDDEN_CHANGELOG_THANKS_HANDLES = [
|
||||||
|
"codex",
|
||||||
|
"openclaw",
|
||||||
|
"steipete",
|
||||||
|
"clawsweeper",
|
||||||
|
"openclaw-clawsweeper",
|
||||||
|
"clawsweeper[bot]",
|
||||||
|
"openclaw-clawsweeper[bot]",
|
||||||
|
];
|
||||||
|
/**
|
||||||
|
* Handle prefixes that identify forbidden changelog thanks credits.
|
||||||
|
*/
|
||||||
|
const FORBIDDEN_CHANGELOG_THANKS_HANDLE_PREFIXES = ["app/"];
|
||||||
|
/**
|
||||||
|
* Handle suffixes that identify forbidden changelog thanks credits.
|
||||||
|
*/
|
||||||
|
const FORBIDDEN_CHANGELOG_THANKS_HANDLE_SUFFIXES = ["[bot]"];
|
||||||
|
/**
|
||||||
|
* Handles that require an explicit human credit instead.
|
||||||
|
*/
|
||||||
|
const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLES = new Set([
|
||||||
|
"clawsweeper",
|
||||||
|
"openclaw-clawsweeper",
|
||||||
|
"clawsweeper[bot]",
|
||||||
|
"openclaw-clawsweeper[bot]",
|
||||||
|
]);
|
||||||
|
/**
|
||||||
|
* Handle prefixes that require explicit human credit instead.
|
||||||
|
*/
|
||||||
|
const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLE_PREFIXES = ["app/"];
|
||||||
|
/**
|
||||||
|
* Handle suffixes that require explicit human credit instead.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
type ThanksOptions = { strictBotHandle?: boolean };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports whether a handle is forbidden in changelog thanks text.
|
||||||
|
*/
|
||||||
|
export function isForbiddenChangelogThanksHandle(handle: string, options: ThanksOptions = {}) {
|
||||||
|
const { strictBotHandle = false } = options;
|
||||||
|
const normalized = handle.toLowerCase();
|
||||||
|
if (normalized === "" || normalized === "null") {
|
||||||
|
// Empty/null input is not a GitHub handle, but the shell query path may pass it through.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
FORBIDDEN_CHANGELOG_THANKS_HANDLES.includes(normalized) ||
|
||||||
|
FORBIDDEN_CHANGELOG_THANKS_HANDLE_PREFIXES.some((prefix) => normalized.startsWith(prefix)) ||
|
||||||
|
FORBIDDEN_CHANGELOG_THANKS_HANDLE_SUFFIXES.some((suffix) => normalized.endsWith(suffix))
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (strictBotHandle) {
|
||||||
|
// PR-author checks should not reject a real human whose login merely contains a bot keyword.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports whether a handle needs a separate human credit.
|
||||||
|
*/
|
||||||
|
export function requiresExplicitHumanChangelogThanks(handle: string) {
|
||||||
|
const normalized = handle.toLowerCase();
|
||||||
|
if (normalized === "" || normalized === "null") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLES.has(normalized) ||
|
||||||
|
CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLE_PREFIXES.some((prefix) =>
|
||||||
|
normalized.startsWith(prefix),
|
||||||
|
) ||
|
||||||
|
CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLE_SUFFIXES.some((suffix) =>
|
||||||
|
normalized.endsWith(suffix),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds changelog lines that thank forbidden handles.
|
||||||
|
*/
|
||||||
|
export function findForbiddenChangelogThanks(content: string) {
|
||||||
|
return content
|
||||||
|
.split(/\r?\n/u)
|
||||||
|
.map((text, index) => {
|
||||||
|
if (!THANKS_PATTERN.test(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// A single changelog line may thank multiple handles; scan all of them.
|
||||||
|
for (const match of text.matchAll(THANKED_HANDLE_PATTERN)) {
|
||||||
|
const handle = match[1];
|
||||||
|
if (handle && isForbiddenChangelogThanksHandle(handle)) {
|
||||||
|
return { line: index + 1, handle: handle.toLowerCase(), text };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((violation) => violation !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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] ?? "", {
|
||||||
|
strictBotHandle: true,
|
||||||
|
})
|
||||||
|
? 0
|
||||||
|
: 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argv[0] === "--requires-explicit-human-thanks") {
|
||||||
|
process.exitCode = requiresExplicitHumanChangelogThanks(argv[1] ?? "") ? 0 : 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const changelogPath = argv[0] ?? "CHANGELOG.md";
|
||||||
|
const absolutePath = path.resolve(process.cwd(), changelogPath);
|
||||||
|
const content = fs.readFileSync(absolutePath, "utf8");
|
||||||
|
const violations = findForbiddenChangelogThanks(content);
|
||||||
|
if (violations.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error("Forbidden changelog thanks attribution:");
|
||||||
|
for (const violation of violations) {
|
||||||
|
const relativePath = path.relative(process.cwd(), absolutePath) || changelogPath;
|
||||||
|
console.error(`- ${relativePath}:${violation.line} uses Thanks @${violation.handle}`);
|
||||||
|
}
|
||||||
|
console.error(
|
||||||
|
`Use a credited external GitHub username instead of ${FORBIDDEN_CHANGELOG_THANKS_HANDLES.map(
|
||||||
|
(handle) => `@${handle}`,
|
||||||
|
).join(", ")}.`,
|
||||||
|
);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||||
|
main().catch((error: unknown) => {
|
||||||
|
console.error(error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Finds channel-specific references inside channel-agnostic protected sources.
|
|
||||||
*/
|
|
||||||
export function findChannelAgnosticBoundaryViolations(
|
|
||||||
content: unknown,
|
|
||||||
fileName?: string,
|
|
||||||
options?: Record<string, unknown>,
|
|
||||||
): unknown[];
|
|
||||||
/**
|
|
||||||
* Finds reverse dependencies from channel core into plugin/runtime surfaces.
|
|
||||||
*/
|
|
||||||
export function findChannelCoreReverseDependencyViolations(
|
|
||||||
content: unknown,
|
|
||||||
fileName?: string,
|
|
||||||
): unknown[];
|
|
||||||
/**
|
|
||||||
* Finds user-facing channel names in ACP-owned text sources.
|
|
||||||
*/
|
|
||||||
export function findAcpUserFacingChannelNameViolations(
|
|
||||||
content: unknown,
|
|
||||||
fileName?: string,
|
|
||||||
): unknown[];
|
|
||||||
/**
|
|
||||||
* Finds raw system mark literals where shared constants should be used.
|
|
||||||
*/
|
|
||||||
export function findSystemMarkLiteralViolations(content: unknown, fileName?: string): unknown[];
|
|
||||||
/**
|
|
||||||
* Runs all channel-agnostic boundary checks.
|
|
||||||
*/
|
|
||||||
export function main(): Promise<void>;
|
|
||||||
+43
-26
@@ -11,7 +11,7 @@ import {
|
|||||||
getPropertyNameText,
|
getPropertyNameText,
|
||||||
runAsScript,
|
runAsScript,
|
||||||
toLine,
|
toLine,
|
||||||
} from "./lib/ts-guard-utils.mjs";
|
} from "./lib/ts-guard-utils.mts";
|
||||||
|
|
||||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||||
|
|
||||||
@@ -65,16 +65,30 @@ const channelIds = [
|
|||||||
|
|
||||||
const channelIdSet = new Set(channelIds);
|
const channelIdSet = new Set(channelIds);
|
||||||
const channelSegmentRe = new RegExp(`(^|[._/-])(?:${channelIds.join("|")})([._/-]|$)`);
|
const channelSegmentRe = new RegExp(`(^|[._/-])(?:${channelIds.join("|")})([._/-]|$)`);
|
||||||
const comparisonOperators = new Set([
|
const comparisonOperators: ReadonlySet<ts.SyntaxKind> = new Set([
|
||||||
ts.SyntaxKind.EqualsEqualsEqualsToken,
|
ts.SyntaxKind.EqualsEqualsEqualsToken,
|
||||||
ts.SyntaxKind.ExclamationEqualsEqualsToken,
|
ts.SyntaxKind.ExclamationEqualsEqualsToken,
|
||||||
ts.SyntaxKind.EqualsEqualsToken,
|
ts.SyntaxKind.EqualsEqualsToken,
|
||||||
ts.SyntaxKind.ExclamationEqualsToken,
|
ts.SyntaxKind.ExclamationEqualsToken,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const allowedViolations = new Set([]);
|
const allowedViolations = new Set<string>();
|
||||||
|
|
||||||
function isChannelsPropertyAccess(node) {
|
type BoundaryViolation = { line: number; reason: string };
|
||||||
|
type BoundaryOptions = {
|
||||||
|
checkModuleSpecifiers?: boolean;
|
||||||
|
checkConfigPaths?: boolean;
|
||||||
|
checkChannelComparisons?: boolean;
|
||||||
|
checkChannelAssignments?: boolean;
|
||||||
|
moduleSpecifierMatcher?: (specifier: string) => boolean;
|
||||||
|
};
|
||||||
|
type ModuleSpecifierVisit = {
|
||||||
|
kind: string;
|
||||||
|
node: ts.Node;
|
||||||
|
specifier: string;
|
||||||
|
specifierNode: ts.Node;
|
||||||
|
};
|
||||||
|
function isChannelsPropertyAccess(node: ts.Node) {
|
||||||
if (ts.isPropertyAccessExpression(node)) {
|
if (ts.isPropertyAccessExpression(node)) {
|
||||||
return node.name.text === "channels";
|
return node.name.text === "channels";
|
||||||
}
|
}
|
||||||
@@ -84,7 +98,7 @@ function isChannelsPropertyAccess(node) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readStringLiteral(node) {
|
function readStringLiteral(node: ts.Node) {
|
||||||
if (ts.isStringLiteral(node)) {
|
if (ts.isStringLiteral(node)) {
|
||||||
return node.text;
|
return node.text;
|
||||||
}
|
}
|
||||||
@@ -94,12 +108,12 @@ function readStringLiteral(node) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isChannelLiteralNode(node) {
|
function isChannelLiteralNode(node: ts.Node) {
|
||||||
const text = readStringLiteral(node);
|
const text = readStringLiteral(node);
|
||||||
return text ? channelIdSet.has(text) : false;
|
return text ? channelIdSet.has(text) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchesChannelModuleSpecifier(specifier) {
|
function matchesChannelModuleSpecifier(specifier: string) {
|
||||||
return channelSegmentRe.test(specifier.replaceAll("\\", "/"));
|
return channelSegmentRe.test(specifier.replaceAll("\\", "/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +121,7 @@ const userFacingChannelNameRe =
|
|||||||
/\b(?:discord|telegram|slack|signal|imessage|whatsapp|google\s*chat|irc|line|zalo|matrix|msteams)\b/i;
|
/\b(?:discord|telegram|slack|signal|imessage|whatsapp|google\s*chat|irc|line|zalo|matrix|msteams)\b/i;
|
||||||
const systemMarkLiteral = "⚙️";
|
const systemMarkLiteral = "⚙️";
|
||||||
|
|
||||||
function isModuleSpecifierStringNode(node) {
|
function isModuleSpecifierStringNode(node: ts.Node) {
|
||||||
const parent = node.parent;
|
const parent = node.parent;
|
||||||
if (ts.isImportDeclaration(parent) || ts.isExportDeclaration(parent)) {
|
if (ts.isImportDeclaration(parent) || ts.isExportDeclaration(parent)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -123,9 +137,9 @@ function isModuleSpecifierStringNode(node) {
|
|||||||
* Finds channel-specific references inside channel-agnostic protected sources.
|
* Finds channel-specific references inside channel-agnostic protected sources.
|
||||||
*/
|
*/
|
||||||
export function findChannelAgnosticBoundaryViolations(
|
export function findChannelAgnosticBoundaryViolations(
|
||||||
content,
|
content: string,
|
||||||
fileName = "source.ts",
|
fileName = "source.ts",
|
||||||
options = {},
|
options: BoundaryOptions = {},
|
||||||
) {
|
) {
|
||||||
const checkModuleSpecifiers = options.checkModuleSpecifiers ?? true;
|
const checkModuleSpecifiers = options.checkModuleSpecifiers ?? true;
|
||||||
const checkConfigPaths = options.checkConfigPaths ?? true;
|
const checkConfigPaths = options.checkConfigPaths ?? true;
|
||||||
@@ -134,13 +148,13 @@ export function findChannelAgnosticBoundaryViolations(
|
|||||||
const moduleSpecifierMatcher = options.moduleSpecifierMatcher ?? matchesChannelModuleSpecifier;
|
const moduleSpecifierMatcher = options.moduleSpecifierMatcher ?? matchesChannelModuleSpecifier;
|
||||||
|
|
||||||
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
|
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
|
||||||
const violations = [];
|
const violations: BoundaryViolation[] = [];
|
||||||
const moduleViolations = new Map();
|
const moduleViolations = new Map<ts.Node, BoundaryViolation>();
|
||||||
if (checkModuleSpecifiers) {
|
if (checkModuleSpecifiers) {
|
||||||
visitModuleSpecifiers(
|
visitModuleSpecifiers(
|
||||||
ts,
|
ts,
|
||||||
sourceFile,
|
sourceFile,
|
||||||
({ kind, node, specifier, specifierNode }) => {
|
({ kind, node, specifier, specifierNode }: ModuleSpecifierVisit) => {
|
||||||
if (moduleSpecifierMatcher(specifier)) {
|
if (moduleSpecifierMatcher(specifier)) {
|
||||||
const verb =
|
const verb =
|
||||||
kind === "export"
|
kind === "export"
|
||||||
@@ -158,7 +172,7 @@ export function findChannelAgnosticBoundaryViolations(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const visit = (node) => {
|
const visit = (node: ts.Node): void => {
|
||||||
const moduleViolation = moduleViolations.get(node);
|
const moduleViolation = moduleViolations.get(node);
|
||||||
if (moduleViolation) {
|
if (moduleViolation) {
|
||||||
violations.push(moduleViolation);
|
violations.push(moduleViolation);
|
||||||
@@ -226,7 +240,10 @@ export function findChannelAgnosticBoundaryViolations(
|
|||||||
/**
|
/**
|
||||||
* Finds reverse dependencies from channel core into plugin/runtime surfaces.
|
* Finds reverse dependencies from channel core into plugin/runtime surfaces.
|
||||||
*/
|
*/
|
||||||
export function findChannelCoreReverseDependencyViolations(content, fileName = "source.ts") {
|
export function findChannelCoreReverseDependencyViolations(
|
||||||
|
content: string,
|
||||||
|
fileName = "source.ts",
|
||||||
|
) {
|
||||||
return findChannelAgnosticBoundaryViolations(content, fileName, {
|
return findChannelAgnosticBoundaryViolations(content, fileName, {
|
||||||
checkModuleSpecifiers: true,
|
checkModuleSpecifiers: true,
|
||||||
checkConfigPaths: false,
|
checkConfigPaths: false,
|
||||||
@@ -239,11 +256,11 @@ export function findChannelCoreReverseDependencyViolations(content, fileName = "
|
|||||||
/**
|
/**
|
||||||
* Finds user-facing channel names in ACP-owned text sources.
|
* Finds user-facing channel names in ACP-owned text sources.
|
||||||
*/
|
*/
|
||||||
export function findAcpUserFacingChannelNameViolations(content, fileName = "source.ts") {
|
export function findAcpUserFacingChannelNameViolations(content: string, fileName = "source.ts") {
|
||||||
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
|
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
|
||||||
const violations = [];
|
const violations: BoundaryViolation[] = [];
|
||||||
|
|
||||||
const visit = (node) => {
|
const visit = (node: ts.Node): void => {
|
||||||
const text = readStringLiteral(node);
|
const text = readStringLiteral(node);
|
||||||
if (text && userFacingChannelNameRe.test(text) && !isModuleSpecifierStringNode(node)) {
|
if (text && userFacingChannelNameRe.test(text) && !isModuleSpecifierStringNode(node)) {
|
||||||
violations.push({
|
violations.push({
|
||||||
@@ -261,11 +278,11 @@ export function findAcpUserFacingChannelNameViolations(content, fileName = "sour
|
|||||||
/**
|
/**
|
||||||
* Finds raw system mark literals where shared constants should be used.
|
* Finds raw system mark literals where shared constants should be used.
|
||||||
*/
|
*/
|
||||||
export function findSystemMarkLiteralViolations(content, fileName = "source.ts") {
|
export function findSystemMarkLiteralViolations(content: string, fileName = "source.ts") {
|
||||||
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
|
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
|
||||||
const violations = [];
|
const violations: BoundaryViolation[] = [];
|
||||||
|
|
||||||
const visit = (node) => {
|
const visit = (node: ts.Node): void => {
|
||||||
const text = readStringLiteral(node);
|
const text = readStringLiteral(node);
|
||||||
if (text && text.includes(systemMarkLiteral) && !isModuleSpecifierStringNode(node)) {
|
if (text && text.includes(systemMarkLiteral) && !isModuleSpecifierStringNode(node)) {
|
||||||
violations.push({
|
violations.push({
|
||||||
@@ -284,22 +301,22 @@ const boundaryRuleSets = [
|
|||||||
{
|
{
|
||||||
id: "acp-core",
|
id: "acp-core",
|
||||||
sources: acpCoreProtectedSources,
|
sources: acpCoreProtectedSources,
|
||||||
scan: (content, fileName) => findChannelAgnosticBoundaryViolations(content, fileName),
|
scan: findChannelAgnosticBoundaryViolations,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "channel-core-reverse-deps",
|
id: "channel-core-reverse-deps",
|
||||||
sources: channelCoreProtectedSources,
|
sources: channelCoreProtectedSources,
|
||||||
scan: (content, fileName) => findChannelCoreReverseDependencyViolations(content, fileName),
|
scan: findChannelCoreReverseDependencyViolations,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "acp-user-facing-text",
|
id: "acp-user-facing-text",
|
||||||
sources: acpUserFacingTextSources,
|
sources: acpUserFacingTextSources,
|
||||||
scan: (content, fileName) => findAcpUserFacingChannelNameViolations(content, fileName),
|
scan: findAcpUserFacingChannelNameViolations,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system-mark-literal-usage",
|
id: "system-mark-literal-usage",
|
||||||
sources: systemMarkLiteralGuardSources,
|
sources: systemMarkLiteralGuardSources,
|
||||||
scan: (content, fileName) => findSystemMarkLiteralViolations(content, fileName),
|
scan: findSystemMarkLiteralViolations,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -307,7 +324,7 @@ const boundaryRuleSets = [
|
|||||||
* Runs all channel-agnostic boundary checks.
|
* Runs all channel-agnostic boundary checks.
|
||||||
*/
|
*/
|
||||||
export async function main() {
|
export async function main() {
|
||||||
const violations = [];
|
const violations: string[] = [];
|
||||||
for (const ruleSet of boundaryRuleSets) {
|
for (const ruleSet of boundaryRuleSets) {
|
||||||
const files = (
|
const files = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import type fs from "node:fs";
|
|
||||||
|
|
||||||
type CliBootstrapCheckParams = {
|
|
||||||
rootDir?: string;
|
|
||||||
entrypoints?: string[];
|
|
||||||
distDir?: string;
|
|
||||||
gatewayRunChunkMaxBytes?: number;
|
|
||||||
fs?: typeof fs;
|
|
||||||
logger?: { error(message: string): void };
|
|
||||||
};
|
|
||||||
|
|
||||||
export function listStaticImportSpecifiers(source: string): string[];
|
|
||||||
export function collectCliBootstrapExternalImportErrors(params?: CliBootstrapCheckParams): string[];
|
|
||||||
export function collectGatewayRunChunkBudgetErrors(params?: CliBootstrapCheckParams): string[];
|
|
||||||
export function checkCliBootstrapExternalImports(params?: CliBootstrapCheckParams): void;
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user