mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-28 12:36:55 +02:00
2183 lines
81 KiB
YAML
2183 lines
81 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths-ignore:
|
|
- "**/*.md"
|
|
- "docs/**"
|
|
pull_request:
|
|
types: [opened, reopened, synchronize, ready_for_review, converted_to_draft]
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ${{ github.event_name == 'pull_request' && format('{0}-v7-{1}', github.workflow, github.event.pull_request.number) || (github.repository == 'openclaw/openclaw' && format('{0}-v7-{1}', github.workflow, github.ref) || format('{0}-v7-{1}-{2}', github.workflow, github.ref, github.sha)) }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
|
|
jobs:
|
|
# Preflight: establish routing truth and job matrices once, then let real
|
|
# work fan out from a single source of truth.
|
|
preflight:
|
|
permissions:
|
|
contents: read
|
|
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 20
|
|
outputs:
|
|
docs_only: ${{ steps.manifest.outputs.docs_only }}
|
|
docs_changed: ${{ steps.manifest.outputs.docs_changed }}
|
|
run_node: ${{ steps.manifest.outputs.run_node }}
|
|
run_macos: ${{ steps.manifest.outputs.run_macos }}
|
|
run_android: ${{ steps.manifest.outputs.run_android }}
|
|
run_skills_python: ${{ steps.manifest.outputs.run_skills_python }}
|
|
run_skills_python_job: ${{ steps.manifest.outputs.run_skills_python_job }}
|
|
run_windows: ${{ steps.manifest.outputs.run_windows }}
|
|
has_changed_extensions: ${{ steps.manifest.outputs.has_changed_extensions }}
|
|
changed_extensions_matrix: ${{ steps.manifest.outputs.changed_extensions_matrix }}
|
|
run_build_artifacts: ${{ steps.manifest.outputs.run_build_artifacts }}
|
|
run_checks_fast: ${{ steps.manifest.outputs.run_checks_fast }}
|
|
checks_fast_core_matrix: ${{ steps.manifest.outputs.checks_fast_core_matrix }}
|
|
channel_contracts_matrix: ${{ steps.manifest.outputs.channel_contracts_matrix }}
|
|
checks_node_extensions_matrix: ${{ steps.manifest.outputs.checks_node_extensions_matrix }}
|
|
run_checks: ${{ steps.manifest.outputs.run_checks }}
|
|
checks_matrix: ${{ steps.manifest.outputs.checks_matrix }}
|
|
run_checks_node_core_nondist: ${{ steps.manifest.outputs.run_checks_node_core_nondist }}
|
|
checks_node_core_nondist_matrix: ${{ steps.manifest.outputs.checks_node_core_nondist_matrix }}
|
|
run_checks_node_core_dist: ${{ steps.manifest.outputs.run_checks_node_core_dist }}
|
|
checks_node_core_dist_matrix: ${{ steps.manifest.outputs.checks_node_core_dist_matrix }}
|
|
run_extension_fast: ${{ steps.manifest.outputs.run_extension_fast }}
|
|
extension_fast_matrix: ${{ steps.manifest.outputs.extension_fast_matrix }}
|
|
run_check: ${{ steps.manifest.outputs.run_check }}
|
|
run_check_additional: ${{ steps.manifest.outputs.run_check_additional }}
|
|
run_build_smoke: ${{ steps.manifest.outputs.run_build_smoke }}
|
|
run_check_docs: ${{ steps.manifest.outputs.run_check_docs }}
|
|
run_control_ui_i18n: ${{ steps.manifest.outputs.run_control_ui_i18n }}
|
|
run_checks_windows: ${{ steps.manifest.outputs.run_checks_windows }}
|
|
checks_windows_matrix: ${{ steps.manifest.outputs.checks_windows_matrix }}
|
|
run_macos_node: ${{ steps.manifest.outputs.run_macos_node }}
|
|
macos_node_matrix: ${{ steps.manifest.outputs.macos_node_matrix }}
|
|
run_macos_swift: ${{ steps.manifest.outputs.run_macos_swift }}
|
|
run_android_job: ${{ steps.manifest.outputs.run_android_job }}
|
|
android_matrix: ${{ steps.manifest.outputs.android_matrix }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 1
|
|
fetch-tags: false
|
|
persist-credentials: false
|
|
submodules: false
|
|
|
|
- name: Ensure preflight base commit
|
|
uses: ./.github/actions/ensure-base-commit
|
|
with:
|
|
base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
|
|
fetch-ref: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }}
|
|
|
|
- name: Detect docs-only changes
|
|
id: docs_scope
|
|
uses: ./.github/actions/detect-docs-changes
|
|
|
|
- name: Detect changed scopes
|
|
id: changed_scope
|
|
if: steps.docs_scope.outputs.docs_only != 'true'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if [ "${{ github.event_name }}" = "push" ]; then
|
|
BASE="${{ github.event.before }}"
|
|
else
|
|
BASE="${{ github.event.pull_request.base.sha }}"
|
|
fi
|
|
|
|
node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD
|
|
|
|
- name: Detect changed extensions
|
|
id: changed_extensions
|
|
if: steps.docs_scope.outputs.docs_only != 'true' && steps.changed_scope.outputs.run_node == 'true'
|
|
env:
|
|
BASE_SHA: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
|
|
BASE_REF: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }}
|
|
run: |
|
|
node --input-type=module <<'EOF'
|
|
import { appendFileSync } from "node:fs";
|
|
import { listChangedExtensionIds } from "./scripts/lib/changed-extensions.mjs";
|
|
|
|
const extensionIds = listChangedExtensionIds({
|
|
base: process.env.BASE_SHA,
|
|
head: "HEAD",
|
|
fallbackBaseRef: process.env.BASE_REF,
|
|
unavailableBaseBehavior: "all",
|
|
});
|
|
const matrix = JSON.stringify({ include: extensionIds.map((extension) => ({ extension })) });
|
|
|
|
appendFileSync(process.env.GITHUB_OUTPUT, `has_changed_extensions=${extensionIds.length > 0}\n`, "utf8");
|
|
appendFileSync(process.env.GITHUB_OUTPUT, `changed_extensions_matrix=${matrix}\n`, "utf8");
|
|
EOF
|
|
|
|
- name: Build CI manifest
|
|
id: manifest
|
|
env:
|
|
OPENCLAW_CI_DOCS_ONLY: ${{ steps.docs_scope.outputs.docs_only }}
|
|
OPENCLAW_CI_DOCS_CHANGED: ${{ steps.docs_scope.outputs.docs_changed }}
|
|
OPENCLAW_CI_RUN_NODE: ${{ steps.changed_scope.outputs.run_node || 'false' }}
|
|
OPENCLAW_CI_RUN_MACOS: ${{ steps.changed_scope.outputs.run_macos || 'false' }}
|
|
OPENCLAW_CI_RUN_ANDROID: ${{ steps.changed_scope.outputs.run_android || 'false' }}
|
|
OPENCLAW_CI_RUN_WINDOWS: ${{ steps.changed_scope.outputs.run_windows || 'false' }}
|
|
OPENCLAW_CI_RUN_SKILLS_PYTHON: ${{ steps.changed_scope.outputs.run_skills_python || 'false' }}
|
|
OPENCLAW_CI_RUN_CONTROL_UI_I18N: ${{ steps.changed_scope.outputs.run_control_ui_i18n || 'false' }}
|
|
OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: ${{ steps.changed_extensions.outputs.has_changed_extensions || 'false' }}
|
|
OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: ${{ steps.changed_extensions.outputs.changed_extensions_matrix || '{"include":[]}' }}
|
|
OPENCLAW_CI_REPOSITORY: ${{ github.repository }}
|
|
run: |
|
|
node --input-type=module <<'EOF'
|
|
import { appendFileSync } from "node:fs";
|
|
import {
|
|
createNodeTestShards,
|
|
} from "./scripts/lib/ci-node-test-plan.mjs";
|
|
import {
|
|
createChannelContractTestShards,
|
|
} from "./scripts/lib/channel-contract-test-plan.mjs";
|
|
import {
|
|
createExtensionTestShards,
|
|
DEFAULT_EXTENSION_TEST_SHARD_COUNT,
|
|
} from "./scripts/lib/extension-test-plan.mjs";
|
|
|
|
const parseBoolean = (value, fallback = false) => {
|
|
if (value === undefined) return fallback;
|
|
const normalized = value.trim().toLowerCase();
|
|
if (normalized === "true" || normalized === "1") return true;
|
|
if (normalized === "false" || normalized === "0" || normalized === "") return false;
|
|
return fallback;
|
|
};
|
|
|
|
const parseJson = (value, fallback) => {
|
|
try {
|
|
return value ? JSON.parse(value) : fallback;
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
};
|
|
|
|
const createMatrix = (include) => ({ include });
|
|
const outputPath = process.env.GITHUB_OUTPUT;
|
|
const eventName = process.env.GITHUB_EVENT_NAME ?? "pull_request";
|
|
const isPush = eventName === "push";
|
|
const isCanonicalRepository = process.env.OPENCLAW_CI_REPOSITORY === "openclaw/openclaw";
|
|
const docsOnly = parseBoolean(process.env.OPENCLAW_CI_DOCS_ONLY);
|
|
const docsChanged = parseBoolean(process.env.OPENCLAW_CI_DOCS_CHANGED);
|
|
const runNode = parseBoolean(process.env.OPENCLAW_CI_RUN_NODE) && !docsOnly;
|
|
const runMacos =
|
|
parseBoolean(process.env.OPENCLAW_CI_RUN_MACOS) && !docsOnly && isCanonicalRepository;
|
|
const runAndroid =
|
|
parseBoolean(process.env.OPENCLAW_CI_RUN_ANDROID) && !docsOnly && isCanonicalRepository;
|
|
const runWindows =
|
|
parseBoolean(process.env.OPENCLAW_CI_RUN_WINDOWS) && !docsOnly && isCanonicalRepository;
|
|
const runSkillsPython = parseBoolean(process.env.OPENCLAW_CI_RUN_SKILLS_PYTHON) && !docsOnly;
|
|
const runControlUiI18n =
|
|
parseBoolean(process.env.OPENCLAW_CI_RUN_CONTROL_UI_I18N) && !docsOnly;
|
|
const hasChangedExtensions =
|
|
parseBoolean(process.env.OPENCLAW_CI_HAS_CHANGED_EXTENSIONS) && !docsOnly;
|
|
const changedExtensionsMatrix = hasChangedExtensions
|
|
? parseJson(process.env.OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX, { include: [] })
|
|
: { include: [] };
|
|
const extensionTestShardCount = isCanonicalRepository
|
|
? DEFAULT_EXTENSION_TEST_SHARD_COUNT
|
|
: Math.max(DEFAULT_EXTENSION_TEST_SHARD_COUNT, 36);
|
|
const extensionShardMatrix = createMatrix(
|
|
runNode
|
|
? createExtensionTestShards({
|
|
shardCount: extensionTestShardCount,
|
|
}).map((shard) => ({
|
|
check_name: shard.checkName,
|
|
extensions_csv: shard.extensionIds.join(","),
|
|
runner: isCanonicalRepository && [0, 3, 4].includes(shard.index)
|
|
? "blacksmith-8vcpu-ubuntu-2404"
|
|
: isCanonicalRepository
|
|
? "blacksmith-4vcpu-ubuntu-2404"
|
|
: "ubuntu-24.04",
|
|
shard_index: shard.index + 1,
|
|
task: "extensions-batch",
|
|
}))
|
|
: [],
|
|
);
|
|
const nodeTestShards = runNode
|
|
? createNodeTestShards().map((shard) => ({
|
|
check_name: shard.checkName,
|
|
runtime: "node",
|
|
task: "test-shard",
|
|
shard_name: shard.shardName,
|
|
configs: shard.configs,
|
|
includePatterns: shard.includePatterns,
|
|
requires_dist: shard.requiresDist,
|
|
runner: shard.runner,
|
|
}))
|
|
: [];
|
|
const nodeTestNonDistShards = nodeTestShards.filter((shard) => !shard.requires_dist);
|
|
const nodeTestDistShards = nodeTestShards.filter((shard) => shard.requires_dist);
|
|
|
|
const manifest = {
|
|
docs_only: docsOnly,
|
|
docs_changed: docsChanged,
|
|
run_node: runNode,
|
|
run_macos: runMacos,
|
|
run_android: runAndroid,
|
|
run_skills_python: runSkillsPython,
|
|
run_windows: runWindows,
|
|
has_changed_extensions: hasChangedExtensions,
|
|
changed_extensions_matrix: changedExtensionsMatrix,
|
|
run_build_artifacts: runNode,
|
|
run_checks_fast: runNode,
|
|
checks_fast_core_matrix: createMatrix(
|
|
runNode
|
|
? [
|
|
{ check_name: "checks-fast-bundled", runtime: "node", task: "bundled" },
|
|
{
|
|
check_name: "checks-fast-contracts-plugins",
|
|
runtime: "node",
|
|
task: "contracts-plugins",
|
|
},
|
|
]
|
|
: [],
|
|
),
|
|
channel_contracts_matrix: createMatrix(runNode ? createChannelContractTestShards() : []),
|
|
checks_node_extensions_matrix: extensionShardMatrix,
|
|
run_checks: runNode,
|
|
checks_matrix: createMatrix(
|
|
runNode
|
|
? [
|
|
{ check_name: "checks-node-channels", runtime: "node", task: "channels" },
|
|
]
|
|
: [],
|
|
),
|
|
run_checks_node_core_nondist: nodeTestNonDistShards.length > 0,
|
|
checks_node_core_nondist_matrix: createMatrix(nodeTestNonDistShards),
|
|
run_checks_node_core_dist: nodeTestDistShards.length > 0,
|
|
checks_node_core_dist_matrix: createMatrix(nodeTestDistShards),
|
|
run_extension_fast: hasChangedExtensions && !isPush,
|
|
extension_fast_matrix: createMatrix(
|
|
hasChangedExtensions && !isPush
|
|
? (changedExtensionsMatrix.include ?? []).map((entry) => ({
|
|
check_name: `extension-fast-${entry.extension}`,
|
|
extension: entry.extension,
|
|
}))
|
|
: [],
|
|
),
|
|
run_check: runNode,
|
|
run_check_additional: runNode,
|
|
run_build_smoke: runNode,
|
|
run_check_docs: docsChanged,
|
|
run_control_ui_i18n: runControlUiI18n,
|
|
run_skills_python_job: runSkillsPython,
|
|
run_checks_windows: runWindows,
|
|
checks_windows_matrix: createMatrix(
|
|
runWindows
|
|
? [{ check_name: "checks-windows-node-test", runtime: "node", task: "test" }]
|
|
: [],
|
|
),
|
|
run_macos_node: runMacos,
|
|
macos_node_matrix: createMatrix(
|
|
runMacos ? [{ check_name: "macos-node", runtime: "node", task: "test" }] : [],
|
|
),
|
|
run_macos_swift: runMacos,
|
|
run_android_job: runAndroid,
|
|
android_matrix: createMatrix(
|
|
runAndroid
|
|
? [
|
|
{ check_name: "android-test-play", task: "test-play" },
|
|
{ check_name: "android-test-third-party", task: "test-third-party" },
|
|
{ check_name: "android-build-play", task: "build-play" },
|
|
]
|
|
: [],
|
|
),
|
|
};
|
|
|
|
for (const [key, value] of Object.entries(manifest)) {
|
|
appendFileSync(
|
|
outputPath,
|
|
`${key}=${typeof value === "string" ? value : JSON.stringify(value)}\n`,
|
|
"utf8",
|
|
);
|
|
}
|
|
EOF
|
|
|
|
# Run the fast security/SCM checks in parallel with scope detection so the
|
|
# main Node jobs do not have to wait for Python/pre-commit setup.
|
|
security-scm-fast:
|
|
permissions:
|
|
contents: read
|
|
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 20
|
|
env:
|
|
PRE_COMMIT_HOME: .cache/pre-commit-security-fast
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 1
|
|
fetch-tags: false
|
|
persist-credentials: false
|
|
submodules: false
|
|
|
|
- name: Ensure security base commit
|
|
uses: ./.github/actions/ensure-base-commit
|
|
with:
|
|
base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
|
|
fetch-ref: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }}
|
|
|
|
- name: Prepare trusted pre-commit config
|
|
if: github.event_name == 'pull_request'
|
|
env:
|
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
|
run: |
|
|
set -euo pipefail
|
|
trusted_config="$RUNNER_TEMP/pre-commit-base.yaml"
|
|
if git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null &&
|
|
git cat-file -e "${BASE_SHA}:.pre-commit-config.yaml" 2>/dev/null; then
|
|
git show "${BASE_SHA}:.pre-commit-config.yaml" > "$trusted_config"
|
|
elif git show "refs/remotes/origin/${BASE_REF}:.pre-commit-config.yaml" \
|
|
> "$trusted_config" 2>/dev/null; then
|
|
echo "Base SHA ${BASE_SHA} does not expose .pre-commit-config.yaml; using origin/${BASE_REF} instead."
|
|
else
|
|
echo "::warning title=trusted pre-commit config unavailable::Could not read .pre-commit-config.yaml from ${BASE_SHA} or origin/${BASE_REF}; falling back to the checked-out config."
|
|
rm -f "$trusted_config"
|
|
exit 0
|
|
fi
|
|
echo "PRE_COMMIT_CONFIG_PATH=$trusted_config" >> "$GITHUB_ENV"
|
|
|
|
- name: Setup Python
|
|
id: setup-python
|
|
uses: actions/setup-python@v6
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Restore pre-commit cache
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: .cache/pre-commit-security-fast
|
|
key: pre-commit-security-fast-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}
|
|
restore-keys: |
|
|
pre-commit-security-fast-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-
|
|
|
|
- name: Install pre-commit
|
|
run: python -m pip install --disable-pip-version-check pre-commit==4.2.0
|
|
|
|
- name: Detect committed private keys
|
|
run: pre-commit run --config "${PRE_COMMIT_CONFIG_PATH:-.pre-commit-config.yaml}" --all-files detect-private-key
|
|
|
|
- name: Audit changed GitHub workflows with zizmor
|
|
env:
|
|
BASE_SHA: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if [ -z "${BASE_SHA:-}" ] || [ "${BASE_SHA}" = "0000000000000000000000000000000000000000" ]; then
|
|
echo "No usable base SHA detected; skipping zizmor."
|
|
exit 0
|
|
fi
|
|
|
|
if ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then
|
|
echo "Base SHA ${BASE_SHA} is unavailable; skipping zizmor."
|
|
exit 0
|
|
fi
|
|
|
|
mapfile -t workflow_files < <(
|
|
git diff --name-only "${BASE_SHA}" HEAD -- '.github/workflows/*.yml' '.github/workflows/*.yaml'
|
|
)
|
|
if [ "${#workflow_files[@]}" -eq 0 ]; then
|
|
echo "No workflow changes detected; skipping zizmor."
|
|
exit 0
|
|
fi
|
|
|
|
printf 'Auditing workflow files:\n%s\n' "${workflow_files[@]}"
|
|
pre-commit run --config "${PRE_COMMIT_CONFIG_PATH:-.pre-commit-config.yaml}" zizmor --files "${workflow_files[@]}"
|
|
|
|
security-dependency-audit:
|
|
permissions:
|
|
contents: read
|
|
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 10
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 1
|
|
fetch-tags: false
|
|
persist-credentials: false
|
|
submodules: false
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: "24.x"
|
|
check-latest: false
|
|
|
|
- name: Audit production dependencies
|
|
run: node scripts/pre-commit/pnpm-audit-prod.mjs --audit-level=high
|
|
|
|
security-fast:
|
|
permissions: {}
|
|
needs: [security-scm-fast, security-dependency-audit]
|
|
if: ${{ !cancelled() && always() && (github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Verify fast security jobs
|
|
env:
|
|
DEPENDENCY_AUDIT_RESULT: ${{ needs.security-dependency-audit.result }}
|
|
SCM_RESULT: ${{ needs.security-scm-fast.result }}
|
|
run: |
|
|
set -euo pipefail
|
|
failed=0
|
|
|
|
for result in \
|
|
"security-scm-fast=${SCM_RESULT}" \
|
|
"security-dependency-audit=${DEPENDENCY_AUDIT_RESULT}"
|
|
do
|
|
job="${result%%=*}"
|
|
status="${result#*=}"
|
|
if [ "$status" != "success" ]; then
|
|
echo "::error::${job} ended with ${status}"
|
|
failed=1
|
|
fi
|
|
done
|
|
|
|
exit "$failed"
|
|
|
|
# Build dist once for Node-relevant changes and share it with downstream jobs.
|
|
# Keep this overlapping with the fast correctness lanes so green PRs get heavy
|
|
# test/build feedback sooner instead of waiting behind a full `check` pass.
|
|
build-artifacts:
|
|
permissions:
|
|
contents: read
|
|
needs: [preflight]
|
|
if: needs.preflight.outputs.run_build_artifacts == 'true'
|
|
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
|
|
timeout-minutes: 20
|
|
outputs:
|
|
channels-result: ${{ steps.built_artifact_checks.outputs['channels-result'] }}
|
|
core-support-boundary-result: ${{ steps.built_artifact_checks.outputs['core-support-boundary-result'] }}
|
|
gateway-watch-result: ${{ steps.built_artifact_checks.outputs['gateway-watch-result'] }}
|
|
steps:
|
|
- name: Checkout
|
|
shell: bash
|
|
env:
|
|
CHECKOUT_REPO: ${{ github.repository }}
|
|
CHECKOUT_SHA: ${{ github.sha }}
|
|
CHECKOUT_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
workdir="$GITHUB_WORKSPACE"
|
|
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
|
|
|
reset_checkout_dir() {
|
|
mkdir -p "$workdir"
|
|
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
|
}
|
|
|
|
checkout_attempt() {
|
|
local attempt="$1"
|
|
|
|
reset_checkout_dir
|
|
git init "$workdir" >/dev/null
|
|
git config --global --add safe.directory "$workdir"
|
|
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
|
git -C "$workdir" config gc.auto 0
|
|
|
|
timeout --signal=TERM 30s git -C "$workdir" \
|
|
-c protocol.version=2 \
|
|
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
|
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
|
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
|
|
|
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
|
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
|
echo "checkout attempt ${attempt}/5 succeeded"
|
|
}
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if checkout_attempt "$attempt"; then
|
|
exit 0
|
|
fi
|
|
echo "checkout attempt ${attempt}/5 failed"
|
|
sleep $((attempt * 5))
|
|
done
|
|
|
|
echo "checkout failed after 5 attempts" >&2
|
|
exit 1
|
|
|
|
- name: Ensure secrets base commit (PR fast path)
|
|
if: github.event_name == 'pull_request'
|
|
uses: ./.github/actions/ensure-base-commit
|
|
with:
|
|
base-sha: ${{ github.event.pull_request.base.sha }}
|
|
fetch-ref: ${{ github.event.pull_request.base.ref }}
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
install-bun: "false"
|
|
|
|
- name: Build dist
|
|
run: pnpm build:ci-artifacts
|
|
|
|
- name: Build Control UI
|
|
run: pnpm ui:build
|
|
|
|
- name: Check Control UI i18n
|
|
if: needs.preflight.outputs.run_control_ui_i18n == 'true'
|
|
run: pnpm ui:i18n:check
|
|
|
|
- name: Cache dist build
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
dist/
|
|
dist-runtime/
|
|
key: ${{ runner.os }}-dist-build-${{ github.sha }}
|
|
|
|
- name: Pack built runtime artifacts
|
|
run: tar --posix -cf dist-runtime-build.tar.zst --use-compress-program zstdmt dist dist-runtime
|
|
|
|
- name: Upload built runtime artifacts
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: dist-runtime-build
|
|
path: dist-runtime-build.tar.zst
|
|
retention-days: 1
|
|
|
|
- name: Upload A2UI bundle artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: canvas-a2ui-bundle
|
|
path: src/canvas-host/a2ui/
|
|
include-hidden-files: true
|
|
retention-days: 1
|
|
|
|
- name: Smoke test CLI launcher help
|
|
run: node openclaw.mjs --help
|
|
|
|
- name: Smoke test CLI launcher status json
|
|
run: node openclaw.mjs status --json --timeout 1
|
|
|
|
- name: Smoke test built bundled plugin singleton
|
|
run: pnpm test:build:singleton
|
|
|
|
- name: Smoke test built bundled runtime deps
|
|
run: pnpm test:build:bundled-runtime-deps
|
|
|
|
- name: Check CLI startup memory
|
|
run: pnpm test:startup:memory
|
|
|
|
- name: Run built artifact checks
|
|
id: built_artifact_checks
|
|
if: needs.preflight.outputs.run_checks == 'true' || needs.preflight.outputs.run_checks_node_core_dist == 'true' || needs.preflight.outputs.run_check_additional == 'true'
|
|
env:
|
|
RUN_CHANNELS: ${{ needs.preflight.outputs.run_checks }}
|
|
RUN_CORE_SUPPORT_BOUNDARY: ${{ needs.preflight.outputs.run_checks_node_core_dist }}
|
|
RUN_GATEWAY_WATCH: ${{ needs.preflight.outputs.run_check_additional }}
|
|
shell: bash
|
|
run: |
|
|
set -uo pipefail
|
|
|
|
names=()
|
|
pids=()
|
|
logs=()
|
|
declare -A results=(
|
|
["channels"]="skipped"
|
|
["core-support-boundary"]="skipped"
|
|
["gateway-watch"]="skipped"
|
|
)
|
|
|
|
start_check() {
|
|
local name="$1"
|
|
shift
|
|
local log="${RUNNER_TEMP}/${name}.log"
|
|
names+=("$name")
|
|
logs+=("$log")
|
|
echo "starting ${name}: $*"
|
|
"$@" >"$log" 2>&1 &
|
|
pids+=("$!")
|
|
}
|
|
|
|
if [ "$RUN_CHANNELS" = "true" ]; then
|
|
start_check "channels" env \
|
|
NODE_OPTIONS=--max-old-space-size=6144 \
|
|
OPENCLAW_VITEST_MAX_WORKERS=1 \
|
|
pnpm test:channels
|
|
fi
|
|
|
|
if [ "$RUN_CORE_SUPPORT_BOUNDARY" = "true" ]; then
|
|
start_check "core-support-boundary" env \
|
|
NODE_OPTIONS=--max-old-space-size=6144 \
|
|
OPENCLAW_VITEST_MAX_WORKERS=2 \
|
|
node scripts/run-vitest.mjs run --config test/vitest/vitest.full-core-support-boundary.config.ts
|
|
fi
|
|
|
|
if [ "$RUN_GATEWAY_WATCH" = "true" ]; then
|
|
start_check "gateway-watch" node scripts/check-gateway-watch-regression.mjs --skip-build --ready-timeout-ms 5000
|
|
fi
|
|
|
|
for index in "${!pids[@]}"; do
|
|
name="${names[$index]}"
|
|
log="${logs[$index]}"
|
|
pid="${pids[$index]}"
|
|
|
|
if wait "$pid"; then
|
|
result="success"
|
|
else
|
|
result="failure"
|
|
fi
|
|
|
|
echo "::group::${name} log"
|
|
cat "$log"
|
|
echo "::endgroup::"
|
|
results["$name"]="$result"
|
|
done
|
|
|
|
for name in channels core-support-boundary gateway-watch; do
|
|
echo "${name}-result=${results[$name]}" >> "$GITHUB_OUTPUT"
|
|
done
|
|
|
|
- name: Upload gateway watch regression artifacts
|
|
if: always() && needs.preflight.outputs.run_check_additional == 'true'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: gateway-watch-regression
|
|
path: .local/gateway-watch-regression/
|
|
retention-days: 7
|
|
|
|
checks-fast-core:
|
|
permissions:
|
|
contents: read
|
|
name: ${{ matrix.check_name }}
|
|
needs: [preflight]
|
|
if: needs.preflight.outputs.run_checks_fast == 'true'
|
|
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
|
|
timeout-minutes: 60
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJson(needs.preflight.outputs.checks_fast_core_matrix) }}
|
|
steps:
|
|
- name: Checkout
|
|
shell: bash
|
|
env:
|
|
CHECKOUT_REPO: ${{ github.repository }}
|
|
CHECKOUT_SHA: ${{ github.sha }}
|
|
CHECKOUT_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
workdir="$GITHUB_WORKSPACE"
|
|
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
|
|
|
reset_checkout_dir() {
|
|
mkdir -p "$workdir"
|
|
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
|
}
|
|
|
|
checkout_attempt() {
|
|
local attempt="$1"
|
|
|
|
reset_checkout_dir
|
|
git init "$workdir" >/dev/null
|
|
git config --global --add safe.directory "$workdir"
|
|
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
|
git -C "$workdir" config gc.auto 0
|
|
|
|
timeout --signal=TERM 30s git -C "$workdir" \
|
|
-c protocol.version=2 \
|
|
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
|
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
|
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
|
|
|
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
|
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
|
echo "checkout attempt ${attempt}/5 succeeded"
|
|
}
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if checkout_attempt "$attempt"; then
|
|
exit 0
|
|
fi
|
|
echo "checkout attempt ${attempt}/5 failed"
|
|
sleep $((attempt * 5))
|
|
done
|
|
|
|
echo "checkout failed after 5 attempts" >&2
|
|
exit 1
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
install-bun: "false"
|
|
|
|
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
|
|
env:
|
|
OPENCLAW_TEST_PROJECTS_PARALLEL: 3
|
|
TASK: ${{ matrix.task }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
case "$TASK" in
|
|
bundled)
|
|
pnpm test:bundled
|
|
;;
|
|
contracts-channels)
|
|
pnpm test:contracts:channels
|
|
;;
|
|
contracts-plugins)
|
|
pnpm test:contracts:plugins
|
|
;;
|
|
*)
|
|
echo "Unsupported checks-fast task: $TASK" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
checks-fast-channel-contracts-shard:
|
|
permissions:
|
|
contents: read
|
|
name: ${{ matrix.checkName }}
|
|
needs: [preflight]
|
|
if: needs.preflight.outputs.run_checks_fast == 'true'
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 60
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJson(needs.preflight.outputs.channel_contracts_matrix) }}
|
|
steps:
|
|
- name: Checkout
|
|
shell: bash
|
|
env:
|
|
CHECKOUT_REPO: ${{ github.repository }}
|
|
CHECKOUT_SHA: ${{ github.sha }}
|
|
CHECKOUT_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
workdir="$GITHUB_WORKSPACE"
|
|
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
|
|
|
reset_checkout_dir() {
|
|
mkdir -p "$workdir"
|
|
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
|
}
|
|
|
|
checkout_attempt() {
|
|
local attempt="$1"
|
|
|
|
reset_checkout_dir
|
|
git init "$workdir" >/dev/null
|
|
git config --global --add safe.directory "$workdir"
|
|
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
|
git -C "$workdir" config gc.auto 0
|
|
|
|
timeout --signal=TERM 30s git -C "$workdir" \
|
|
-c protocol.version=2 \
|
|
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
|
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
|
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
|
|
|
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
|
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
|
echo "checkout attempt ${attempt}/5 succeeded"
|
|
}
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if checkout_attempt "$attempt"; then
|
|
exit 0
|
|
fi
|
|
echo "checkout attempt ${attempt}/5 failed"
|
|
sleep $((attempt * 5))
|
|
done
|
|
|
|
echo "checkout failed after 5 attempts" >&2
|
|
exit 1
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
install-bun: "false"
|
|
|
|
- name: Run channel contract shard
|
|
env:
|
|
OPENCLAW_CONTRACT_INCLUDE_PATTERNS_JSON: ${{ toJson(matrix.includePatterns) }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
include_file="$RUNNER_TEMP/channel-contract-include.json"
|
|
INCLUDE_FILE="$include_file" node --input-type=module <<'EOF'
|
|
import { writeFileSync } from "node:fs";
|
|
|
|
const includePatterns = JSON.parse(process.env.OPENCLAW_CONTRACT_INCLUDE_PATTERNS_JSON ?? "[]");
|
|
if (!Array.isArray(includePatterns) || includePatterns.length === 0) {
|
|
console.error("Missing channel contract include patterns");
|
|
process.exit(1);
|
|
}
|
|
writeFileSync(process.env.INCLUDE_FILE, JSON.stringify(includePatterns), "utf8");
|
|
EOF
|
|
OPENCLAW_VITEST_INCLUDE_FILE="$include_file" pnpm test:contracts:channels
|
|
|
|
checks-fast-channel-contracts:
|
|
permissions:
|
|
contents: read
|
|
name: checks-fast-contracts-channels
|
|
needs: [preflight, checks-fast-channel-contracts-shard]
|
|
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks_fast == 'true' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Verify channel contract shards
|
|
env:
|
|
SHARD_RESULT: ${{ needs.checks-fast-channel-contracts-shard.result }}
|
|
run: |
|
|
if [ "$SHARD_RESULT" = "cancelled" ]; then
|
|
echo "Channel contract shards were cancelled, usually because a newer commit superseded this run." >&2
|
|
exit 1
|
|
fi
|
|
if [ "$SHARD_RESULT" != "success" ]; then
|
|
echo "Channel contract shards failed: $SHARD_RESULT" >&2
|
|
exit 1
|
|
fi
|
|
|
|
checks-fast-protocol:
|
|
permissions:
|
|
contents: read
|
|
name: "checks-fast-protocol"
|
|
needs: [preflight]
|
|
if: needs.preflight.outputs.run_checks_fast == 'true'
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 30
|
|
steps:
|
|
- name: Checkout
|
|
shell: bash
|
|
env:
|
|
CHECKOUT_REPO: ${{ github.repository }}
|
|
CHECKOUT_SHA: ${{ github.sha }}
|
|
CHECKOUT_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
workdir="$GITHUB_WORKSPACE"
|
|
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
|
|
|
reset_checkout_dir() {
|
|
mkdir -p "$workdir"
|
|
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
|
}
|
|
|
|
checkout_attempt() {
|
|
local attempt="$1"
|
|
|
|
reset_checkout_dir
|
|
git init "$workdir" >/dev/null
|
|
git config --global --add safe.directory "$workdir"
|
|
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
|
git -C "$workdir" config gc.auto 0
|
|
|
|
timeout --signal=TERM 30s git -C "$workdir" \
|
|
-c protocol.version=2 \
|
|
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
|
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
|
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
|
|
|
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
|
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
|
echo "checkout attempt ${attempt}/5 succeeded"
|
|
}
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if checkout_attempt "$attempt"; then
|
|
exit 0
|
|
fi
|
|
echo "checkout attempt ${attempt}/5 failed"
|
|
sleep $((attempt * 5))
|
|
done
|
|
|
|
echo "checkout failed after 5 attempts" >&2
|
|
exit 1
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
install-bun: "false"
|
|
|
|
- name: Run protocol check
|
|
run: pnpm protocol:check
|
|
|
|
checks-node-extensions-shard:
|
|
permissions:
|
|
contents: read
|
|
name: ${{ matrix.check_name }}
|
|
needs: [preflight]
|
|
if: needs.preflight.outputs.run_checks_fast == 'true'
|
|
runs-on: ${{ matrix.runner }}
|
|
timeout-minutes: 60
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJson(needs.preflight.outputs.checks_node_extensions_matrix) }}
|
|
steps:
|
|
- name: Checkout
|
|
shell: bash
|
|
env:
|
|
CHECKOUT_REPO: ${{ github.repository }}
|
|
CHECKOUT_SHA: ${{ github.sha }}
|
|
CHECKOUT_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
workdir="$GITHUB_WORKSPACE"
|
|
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
|
|
|
reset_checkout_dir() {
|
|
mkdir -p "$workdir"
|
|
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
|
}
|
|
|
|
checkout_attempt() {
|
|
local attempt="$1"
|
|
|
|
reset_checkout_dir
|
|
git init "$workdir" >/dev/null
|
|
git config --global --add safe.directory "$workdir"
|
|
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
|
git -C "$workdir" config gc.auto 0
|
|
|
|
timeout --signal=TERM 30s git -C "$workdir" \
|
|
-c protocol.version=2 \
|
|
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
|
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
|
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
|
|
|
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
|
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
|
echo "checkout attempt ${attempt}/5 succeeded"
|
|
}
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if checkout_attempt "$attempt"; then
|
|
exit 0
|
|
fi
|
|
echo "checkout attempt ${attempt}/5 failed"
|
|
sleep $((attempt * 5))
|
|
done
|
|
|
|
echo "checkout failed after 5 attempts" >&2
|
|
exit 1
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
install-bun: "false"
|
|
|
|
- name: Run extension shard
|
|
env:
|
|
NODE_OPTIONS: --max-old-space-size=6144
|
|
OPENCLAW_EXTENSION_BATCH_PARALLEL: 2
|
|
OPENCLAW_VITEST_MAX_WORKERS: 1
|
|
OPENCLAW_EXTENSION_BATCH: ${{ matrix.extensions_csv }}
|
|
run: pnpm test:extensions:batch -- "$OPENCLAW_EXTENSION_BATCH"
|
|
|
|
checks-node-extensions:
|
|
permissions:
|
|
contents: read
|
|
name: checks-node-extensions
|
|
needs: [preflight, checks-node-extensions-shard]
|
|
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks_fast == 'true' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Verify extension shards
|
|
env:
|
|
SHARD_RESULT: ${{ needs.checks-node-extensions-shard.result }}
|
|
run: |
|
|
if [ "$SHARD_RESULT" != "success" ]; then
|
|
echo "Extension shard checks failed: $SHARD_RESULT" >&2
|
|
exit 1
|
|
fi
|
|
|
|
checks:
|
|
permissions:
|
|
contents: read
|
|
name: ${{ matrix.check_name }}
|
|
needs: [preflight, build-artifacts]
|
|
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks == 'true' && needs.build-artifacts.result == 'success' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJson(needs.preflight.outputs.checks_matrix) }}
|
|
steps:
|
|
- name: Verify ${{ matrix.task }} (${{ matrix.runtime }})
|
|
env:
|
|
TASK: ${{ matrix.task }}
|
|
CHANNELS_RESULT: ${{ needs.build-artifacts.outputs['channels-result'] }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
case "$TASK" in
|
|
channels)
|
|
if [ "$CHANNELS_RESULT" != "success" ]; then
|
|
echo "Channel tests failed in build-artifacts: $CHANNELS_RESULT" >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
*)
|
|
echo "Unsupported checks task: $TASK" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
checks-node-compat:
|
|
permissions:
|
|
contents: read
|
|
name: checks-node-compat-node22
|
|
needs: [preflight]
|
|
if: needs.preflight.outputs.run_node == 'true' && github.event_name == 'push'
|
|
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
|
|
timeout-minutes: 60
|
|
steps:
|
|
- name: Checkout
|
|
shell: bash
|
|
env:
|
|
CHECKOUT_REPO: ${{ github.repository }}
|
|
CHECKOUT_SHA: ${{ github.sha }}
|
|
CHECKOUT_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
workdir="$GITHUB_WORKSPACE"
|
|
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
|
|
|
reset_checkout_dir() {
|
|
mkdir -p "$workdir"
|
|
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
|
}
|
|
|
|
checkout_attempt() {
|
|
local attempt="$1"
|
|
|
|
reset_checkout_dir
|
|
git init "$workdir" >/dev/null
|
|
git config --global --add safe.directory "$workdir"
|
|
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
|
git -C "$workdir" config gc.auto 0
|
|
|
|
timeout --signal=TERM 30s git -C "$workdir" \
|
|
-c protocol.version=2 \
|
|
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
|
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
|
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
|
|
|
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
|
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
|
echo "checkout attempt ${attempt}/5 succeeded"
|
|
}
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if checkout_attempt "$attempt"; then
|
|
exit 0
|
|
fi
|
|
echo "checkout attempt ${attempt}/5 failed"
|
|
sleep $((attempt * 5))
|
|
done
|
|
|
|
echo "checkout failed after 5 attempts" >&2
|
|
exit 1
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: "22.18.0"
|
|
cache-key-suffix: "node22"
|
|
install-bun: "false"
|
|
|
|
- name: Configure Node test resources
|
|
run: echo "OPENCLAW_VITEST_MAX_WORKERS=2" >> "$GITHUB_ENV"
|
|
|
|
- name: Run Node 22 compatibility
|
|
env:
|
|
NODE_OPTIONS: --max-old-space-size=6144
|
|
run: |
|
|
pnpm build
|
|
pnpm ui:build
|
|
node openclaw.mjs --help
|
|
node openclaw.mjs status --json --timeout 1
|
|
pnpm test:build:singleton
|
|
|
|
checks-node-core-test-nondist-shard:
|
|
permissions:
|
|
contents: read
|
|
name: ${{ matrix.check_name }}
|
|
needs: [preflight]
|
|
if: needs.preflight.outputs.run_checks_node_core_nondist == 'true'
|
|
runs-on: ${{ github.repository == 'openclaw/openclaw' && (matrix.runner || 'ubuntu-24.04') || 'ubuntu-24.04' }}
|
|
timeout-minutes: 60
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJson(needs.preflight.outputs.checks_node_core_nondist_matrix) }}
|
|
steps:
|
|
- name: Checkout
|
|
shell: bash
|
|
env:
|
|
CHECKOUT_REPO: ${{ github.repository }}
|
|
CHECKOUT_SHA: ${{ github.sha }}
|
|
CHECKOUT_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
workdir="$GITHUB_WORKSPACE"
|
|
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
|
|
|
reset_checkout_dir() {
|
|
mkdir -p "$workdir"
|
|
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
|
}
|
|
|
|
checkout_attempt() {
|
|
local attempt="$1"
|
|
|
|
reset_checkout_dir
|
|
git init "$workdir" >/dev/null
|
|
git config --global --add safe.directory "$workdir"
|
|
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
|
git -C "$workdir" config gc.auto 0
|
|
|
|
timeout --signal=TERM 30s git -C "$workdir" \
|
|
-c protocol.version=2 \
|
|
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
|
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
|
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
|
|
|
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
|
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
|
echo "checkout attempt ${attempt}/5 succeeded"
|
|
}
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if checkout_attempt "$attempt"; then
|
|
exit 0
|
|
fi
|
|
echo "checkout attempt ${attempt}/5 failed"
|
|
sleep $((attempt * 5))
|
|
done
|
|
|
|
echo "checkout failed after 5 attempts" >&2
|
|
exit 1
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: "${{ matrix.node_version || '24.x' }}"
|
|
cache-key-suffix: "${{ matrix.cache_key_suffix || 'node24' }}"
|
|
install-bun: "false"
|
|
|
|
- name: Configure Node test resources
|
|
run: echo "OPENCLAW_VITEST_MAX_WORKERS=2" >> "$GITHUB_ENV"
|
|
|
|
- name: Run Node test shard
|
|
env:
|
|
NODE_OPTIONS: --max-old-space-size=6144
|
|
OPENCLAW_NODE_TEST_CONFIGS_JSON: ${{ toJson(matrix.configs) }}
|
|
OPENCLAW_NODE_TEST_INCLUDE_PATTERNS_JSON: ${{ toJson(matrix.includePatterns) }}
|
|
OPENCLAW_TEST_PROJECTS_PARALLEL: "2"
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
node --input-type=module <<'EOF'
|
|
import { spawnSync } from "node:child_process";
|
|
import { writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
const configs = JSON.parse(process.env.OPENCLAW_NODE_TEST_CONFIGS_JSON ?? "[]");
|
|
if (!Array.isArray(configs) || configs.length === 0) {
|
|
console.error("Missing node test shard configs");
|
|
process.exit(1);
|
|
}
|
|
const includePatterns = JSON.parse(process.env.OPENCLAW_NODE_TEST_INCLUDE_PATTERNS_JSON ?? "null");
|
|
const childEnv = { ...process.env };
|
|
if (Array.isArray(includePatterns) && includePatterns.length > 0) {
|
|
const includeFile = join(
|
|
process.env.RUNNER_TEMP ?? ".",
|
|
`node-test-include-${process.env.GITHUB_JOB ?? "local"}-${Date.now()}.json`,
|
|
);
|
|
writeFileSync(includeFile, JSON.stringify(includePatterns), "utf8");
|
|
childEnv.OPENCLAW_VITEST_INCLUDE_FILE = includeFile;
|
|
}
|
|
|
|
const result = spawnSync("pnpm", ["exec", "node", "scripts/test-projects.mjs", ...configs], {
|
|
env: childEnv,
|
|
stdio: "inherit",
|
|
});
|
|
if ((result.status ?? 1) !== 0) {
|
|
process.exit(result.status ?? 1);
|
|
}
|
|
EOF
|
|
|
|
checks-node-core-test-dist-shard:
|
|
permissions:
|
|
contents: read
|
|
name: ${{ matrix.check_name }}
|
|
needs: [preflight, build-artifacts]
|
|
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks_node_core_dist == 'true' && needs.build-artifacts.result == 'success' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJson(needs.preflight.outputs.checks_node_core_dist_matrix) }}
|
|
steps:
|
|
- name: Verify Node test shard
|
|
env:
|
|
CORE_SUPPORT_BOUNDARY_RESULT: ${{ needs.build-artifacts.outputs['core-support-boundary-result'] }}
|
|
SHARD_NAME: ${{ matrix.shard_name }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
case "$SHARD_NAME" in
|
|
core-support-boundary)
|
|
if [ "$CORE_SUPPORT_BOUNDARY_RESULT" != "success" ]; then
|
|
echo "Core support boundary shard failed in build-artifacts: $CORE_SUPPORT_BOUNDARY_RESULT" >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
*)
|
|
echo "Unsupported built-artifact shard: $SHARD_NAME" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
checks-node-core-test:
|
|
permissions:
|
|
contents: read
|
|
name: checks-node-core
|
|
needs: [preflight, checks-node-core-test-nondist-shard, checks-node-core-test-dist-shard]
|
|
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks == 'true' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Verify node test shards
|
|
env:
|
|
DIST_SHARD_RESULT: ${{ needs.checks-node-core-test-dist-shard.result }}
|
|
NONDIST_SHARD_RESULT: ${{ needs.checks-node-core-test-nondist-shard.result }}
|
|
RUN_DIST_SHARDS: ${{ needs.preflight.outputs.run_checks_node_core_dist }}
|
|
RUN_NONDIST_SHARDS: ${{ needs.preflight.outputs.run_checks_node_core_nondist }}
|
|
run: |
|
|
if [ "$RUN_NONDIST_SHARDS" = "true" ] && [ "$NONDIST_SHARD_RESULT" != "success" ]; then
|
|
echo "Node non-dist test shards failed: $NONDIST_SHARD_RESULT" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$RUN_DIST_SHARDS" = "true" ] && [ "$DIST_SHARD_RESULT" != "success" ]; then
|
|
echo "Node dist test shards failed: $DIST_SHARD_RESULT" >&2
|
|
exit 1
|
|
fi
|
|
|
|
extension-fast:
|
|
permissions:
|
|
contents: read
|
|
name: "extension-fast"
|
|
needs: [preflight]
|
|
if: needs.preflight.outputs.run_extension_fast == 'true'
|
|
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
|
|
timeout-minutes: 60
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJson(needs.preflight.outputs.extension_fast_matrix) }}
|
|
steps:
|
|
- name: Checkout
|
|
shell: bash
|
|
env:
|
|
CHECKOUT_REPO: ${{ github.repository }}
|
|
CHECKOUT_SHA: ${{ github.sha }}
|
|
CHECKOUT_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
workdir="$GITHUB_WORKSPACE"
|
|
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
|
|
|
reset_checkout_dir() {
|
|
mkdir -p "$workdir"
|
|
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
|
}
|
|
|
|
checkout_attempt() {
|
|
local attempt="$1"
|
|
|
|
reset_checkout_dir
|
|
git init "$workdir" >/dev/null
|
|
git config --global --add safe.directory "$workdir"
|
|
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
|
git -C "$workdir" config gc.auto 0
|
|
|
|
timeout --signal=TERM 30s git -C "$workdir" \
|
|
-c protocol.version=2 \
|
|
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
|
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
|
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
|
|
|
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
|
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
|
echo "checkout attempt ${attempt}/5 succeeded"
|
|
}
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if checkout_attempt "$attempt"; then
|
|
exit 0
|
|
fi
|
|
echo "checkout attempt ${attempt}/5 failed"
|
|
sleep $((attempt * 5))
|
|
done
|
|
|
|
echo "checkout failed after 5 attempts" >&2
|
|
exit 1
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
install-bun: "false"
|
|
|
|
- name: Run changed extension tests
|
|
env:
|
|
OPENCLAW_CHANGED_EXTENSION: ${{ matrix.extension }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "$OPENCLAW_CHANGED_EXTENSION" = "telegram" ]; then
|
|
export OPENCLAW_VITEST_MAX_WORKERS=1
|
|
export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--max-old-space-size=6144"
|
|
pnpm test:extension "$OPENCLAW_CHANGED_EXTENSION" -- --pool=forks
|
|
exit 0
|
|
fi
|
|
pnpm test:extension "$OPENCLAW_CHANGED_EXTENSION"
|
|
|
|
# Types, lint, and format check shards.
|
|
check-shard:
|
|
permissions:
|
|
contents: read
|
|
name: ${{ matrix.check_name }}
|
|
needs: [preflight]
|
|
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check == 'true' }}
|
|
runs-on: ${{ github.repository == 'openclaw/openclaw' && matrix.runner || 'ubuntu-24.04' }}
|
|
timeout-minutes: 20
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- check_name: check-preflight-guards
|
|
task: preflight-guards
|
|
runner: ubuntu-24.04
|
|
- check_name: check-prod-types
|
|
task: prod-types
|
|
runner: blacksmith-4vcpu-ubuntu-2404
|
|
- check_name: check-lint
|
|
task: lint
|
|
runner: blacksmith-16vcpu-ubuntu-2404
|
|
- check_name: check-policy-guards
|
|
task: policy-guards
|
|
runner: ubuntu-24.04
|
|
- check_name: check-test-types
|
|
task: test-types
|
|
runner: blacksmith-4vcpu-ubuntu-2404
|
|
- check_name: check-strict-smoke
|
|
task: strict-smoke
|
|
runner: ubuntu-24.04
|
|
steps:
|
|
- name: Checkout
|
|
shell: bash
|
|
env:
|
|
CHECKOUT_REPO: ${{ github.repository }}
|
|
CHECKOUT_SHA: ${{ github.sha }}
|
|
CHECKOUT_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
workdir="$GITHUB_WORKSPACE"
|
|
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
|
|
|
reset_checkout_dir() {
|
|
mkdir -p "$workdir"
|
|
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
|
}
|
|
|
|
checkout_attempt() {
|
|
local attempt="$1"
|
|
|
|
reset_checkout_dir
|
|
git init "$workdir" >/dev/null
|
|
git config --global --add safe.directory "$workdir"
|
|
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
|
git -C "$workdir" config gc.auto 0
|
|
|
|
timeout --signal=TERM 30s git -C "$workdir" \
|
|
-c protocol.version=2 \
|
|
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
|
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
|
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
|
|
|
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
|
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
|
echo "checkout attempt ${attempt}/5 succeeded"
|
|
}
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if checkout_attempt "$attempt"; then
|
|
exit 0
|
|
fi
|
|
echo "checkout attempt ${attempt}/5 failed"
|
|
sleep $((attempt * 5))
|
|
done
|
|
|
|
echo "checkout failed after 5 attempts" >&2
|
|
exit 1
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
install-bun: "false"
|
|
|
|
- name: Run check shard
|
|
env:
|
|
OPENCLAW_LOCAL_CHECK: "0"
|
|
TASK: ${{ matrix.task }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
case "$TASK" in
|
|
preflight-guards)
|
|
pnpm check:no-conflict-markers
|
|
pnpm tool-display:check
|
|
pnpm check:host-env-policy:swift
|
|
;;
|
|
prod-types)
|
|
pnpm tsgo:prod
|
|
;;
|
|
lint)
|
|
pnpm lint --threads=8
|
|
;;
|
|
policy-guards)
|
|
pnpm lint:webhook:no-low-level-body-read
|
|
pnpm lint:auth:no-pairing-store-group
|
|
pnpm lint:auth:pairing-account-scope
|
|
pnpm check:import-cycles
|
|
;;
|
|
test-types)
|
|
pnpm check:test-types
|
|
;;
|
|
strict-smoke)
|
|
# build-artifacts already runs the tsdown/runtime build for the same Node-relevant changes.
|
|
pnpm build:plugin-sdk:strict-smoke
|
|
;;
|
|
*)
|
|
echo "Unsupported check task: $TASK" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
check:
|
|
permissions:
|
|
contents: read
|
|
name: "check"
|
|
needs: [preflight, check-shard]
|
|
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check == 'true' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Verify check shards
|
|
env:
|
|
SHARD_RESULT: ${{ needs.check-shard.result }}
|
|
run: |
|
|
if [ "$SHARD_RESULT" != "success" ]; then
|
|
echo "Check shards failed: $SHARD_RESULT" >&2
|
|
exit 1
|
|
fi
|
|
|
|
check-additional-shard:
|
|
permissions:
|
|
contents: read
|
|
name: ${{ matrix.check_name }}
|
|
needs: [preflight]
|
|
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check_additional == 'true' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 20
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- check_name: check-additional-boundaries
|
|
group: boundaries
|
|
- check_name: check-additional-extension-channels
|
|
group: extension-channels
|
|
- check_name: check-additional-extension-bundled
|
|
group: extension-bundled
|
|
- check_name: check-additional-extension-package-boundary
|
|
group: extension-package-boundary
|
|
- check_name: check-additional-runtime-topology-architecture
|
|
group: runtime-topology-architecture
|
|
steps:
|
|
- name: Checkout
|
|
shell: bash
|
|
env:
|
|
CHECKOUT_REPO: ${{ github.repository }}
|
|
CHECKOUT_SHA: ${{ github.sha }}
|
|
CHECKOUT_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
workdir="$GITHUB_WORKSPACE"
|
|
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
|
|
|
reset_checkout_dir() {
|
|
mkdir -p "$workdir"
|
|
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
|
}
|
|
|
|
checkout_attempt() {
|
|
local attempt="$1"
|
|
|
|
reset_checkout_dir
|
|
git init "$workdir" >/dev/null
|
|
git config --global --add safe.directory "$workdir"
|
|
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
|
git -C "$workdir" config gc.auto 0
|
|
|
|
timeout --signal=TERM 30s git -C "$workdir" \
|
|
-c protocol.version=2 \
|
|
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
|
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
|
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
|
|
|
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
|
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
|
echo "checkout attempt ${attempt}/5 succeeded"
|
|
}
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if checkout_attempt "$attempt"; then
|
|
exit 0
|
|
fi
|
|
echo "checkout attempt ${attempt}/5 failed"
|
|
sleep $((attempt * 5))
|
|
done
|
|
|
|
echo "checkout failed after 5 attempts" >&2
|
|
exit 1
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
install-bun: "false"
|
|
|
|
- name: Cache extension package boundary artifacts
|
|
id: extension-package-boundary-cache
|
|
if: matrix.group == 'extension-package-boundary'
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
dist/plugin-sdk
|
|
packages/plugin-sdk/dist
|
|
extensions/*/dist/.boundary-tsc.tsbuildinfo
|
|
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', '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/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: |
|
|
${{ runner.os }}-extension-package-boundary-v1-
|
|
|
|
- name: Preserve extension package boundary cache hit
|
|
if: matrix.group == 'extension-package-boundary' && steps.extension-package-boundary-cache.outputs.cache-hit == 'true'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
find extensions \
|
|
-path '*/dist' -prune -o \
|
|
-path '*/node_modules' -prune -o \
|
|
-type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.mts' -o -name '*.cts' -o -name '*.js' -o -name '*.mjs' -o -name '*.json' \) \
|
|
-exec touch -t 200001010000 {} +
|
|
find src \
|
|
-type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.mts' -o -name '*.cts' -o -name '*.js' -o -name '*.mjs' -o -name '*.json' \) \
|
|
-exec touch -t 200001010000 {} +
|
|
touch -t 200001010000 \
|
|
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
|
|
|
|
- name: Run additional check shard
|
|
env:
|
|
ADDITIONAL_CHECK_GROUP: ${{ matrix.group }}
|
|
RUN_CONTROL_UI_I18N: ${{ needs.preflight.outputs.run_control_ui_i18n }}
|
|
OPENCLAW_ADDITIONAL_BOUNDARY_CONCURRENCY: 4
|
|
OPENCLAW_EXTENSION_BOUNDARY_CONCURRENCY: 6
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
failures=0
|
|
|
|
run_check() {
|
|
local label="$1"
|
|
shift
|
|
|
|
echo "::group::${label}"
|
|
if "$@"; then
|
|
echo "[ok] ${label}"
|
|
else
|
|
echo "::error title=${label} failed::${label} failed"
|
|
failures=1
|
|
fi
|
|
echo "::endgroup::"
|
|
}
|
|
|
|
case "$ADDITIONAL_CHECK_GROUP" in
|
|
boundaries)
|
|
node scripts/run-additional-boundary-checks.mjs
|
|
;;
|
|
extension-channels)
|
|
run_check "lint:extensions:channels" pnpm run lint:extensions:channels
|
|
;;
|
|
extension-bundled)
|
|
run_check "lint:extensions:bundled" pnpm run lint:extensions:bundled
|
|
;;
|
|
extension-package-boundary)
|
|
run_check "test:extensions:package-boundary:compile" pnpm run test:extensions:package-boundary:compile
|
|
run_check "test:extensions:package-boundary:canary" pnpm run test:extensions:package-boundary:canary
|
|
;;
|
|
runtime-topology-architecture)
|
|
run_check "check:architecture" pnpm check:architecture
|
|
;;
|
|
*)
|
|
echo "Unsupported additional check group: $ADDITIONAL_CHECK_GROUP" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
exit "$failures"
|
|
|
|
check-additional:
|
|
permissions:
|
|
contents: read
|
|
name: "check-additional"
|
|
needs: [preflight, check-additional-shard, build-artifacts]
|
|
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check_additional == 'true' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Verify additional check shards
|
|
env:
|
|
SHARD_RESULT: ${{ needs.check-additional-shard.result }}
|
|
BUILD_ARTIFACTS_RESULT: ${{ needs.build-artifacts.result }}
|
|
GATEWAY_RESULT: ${{ needs.build-artifacts.outputs.gateway-watch-result }}
|
|
run: |
|
|
if [ "$SHARD_RESULT" != "success" ]; then
|
|
echo "Additional check shards failed: $SHARD_RESULT" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$BUILD_ARTIFACTS_RESULT" != "success" ]; then
|
|
echo "Build artifact job failed: $BUILD_ARTIFACTS_RESULT" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$GATEWAY_RESULT" != "success" ]; then
|
|
echo "Gateway topology check failed: $GATEWAY_RESULT" >&2
|
|
exit 1
|
|
fi
|
|
|
|
build-smoke:
|
|
permissions:
|
|
contents: read
|
|
name: "build-smoke"
|
|
needs: [preflight, build-artifacts]
|
|
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_build_smoke == 'true' && (github.event_name != 'push' || needs.build-artifacts.result == 'success') }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Verify build smoke
|
|
env:
|
|
BUILD_ARTIFACTS_RESULT: ${{ needs.build-artifacts.result }}
|
|
run: |
|
|
if [ "$BUILD_ARTIFACTS_RESULT" != "success" ]; then
|
|
echo "Build smoke checks failed in build-artifacts: $BUILD_ARTIFACTS_RESULT" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Validate docs (format, lint, broken links) only when docs files changed.
|
|
check-docs:
|
|
permissions:
|
|
contents: read
|
|
needs: [preflight]
|
|
if: needs.preflight.outputs.run_check_docs == 'true'
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 20
|
|
steps:
|
|
- name: Checkout
|
|
shell: bash
|
|
env:
|
|
CHECKOUT_REPO: ${{ github.repository }}
|
|
CHECKOUT_SHA: ${{ github.sha }}
|
|
CHECKOUT_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
workdir="$GITHUB_WORKSPACE"
|
|
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
|
|
|
reset_checkout_dir() {
|
|
mkdir -p "$workdir"
|
|
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
|
}
|
|
|
|
checkout_attempt() {
|
|
local attempt="$1"
|
|
|
|
reset_checkout_dir
|
|
git init "$workdir" >/dev/null
|
|
git config --global --add safe.directory "$workdir"
|
|
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
|
git -C "$workdir" config gc.auto 0
|
|
|
|
timeout --signal=TERM 30s git -C "$workdir" \
|
|
-c protocol.version=2 \
|
|
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
|
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
|
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
|
|
|
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
|
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
|
echo "checkout attempt ${attempt}/5 succeeded"
|
|
}
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if checkout_attempt "$attempt"; then
|
|
exit 0
|
|
fi
|
|
echo "checkout attempt ${attempt}/5 failed"
|
|
sleep $((attempt * 5))
|
|
done
|
|
|
|
echo "checkout failed after 5 attempts" >&2
|
|
exit 1
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
install-bun: "false"
|
|
|
|
- name: Check docs
|
|
run: pnpm check:docs
|
|
|
|
skills-python:
|
|
permissions:
|
|
contents: read
|
|
needs: [preflight]
|
|
if: needs.preflight.outputs.run_skills_python_job == 'true'
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 20
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
submodules: false
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@v6
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Install Python tooling
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
python -m pip install pytest ruff pyyaml
|
|
|
|
- name: Lint Python skill scripts
|
|
run: python -m ruff check skills
|
|
|
|
- name: Test skill Python scripts
|
|
run: python -m pytest -q skills
|
|
|
|
checks-windows:
|
|
permissions:
|
|
contents: read
|
|
name: ${{ matrix.check_name }}
|
|
needs: [preflight]
|
|
if: needs.preflight.outputs.run_checks_windows == 'true'
|
|
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-windows-2025' || 'windows-2025' }}
|
|
timeout-minutes: 60
|
|
env:
|
|
NODE_OPTIONS: --max-old-space-size=6144
|
|
# Keep total concurrency predictable on the smaller Windows runner.
|
|
OPENCLAW_VITEST_MAX_WORKERS: 1
|
|
OPENCLAW_TEST_SKIP_FULL_EXTENSIONS_SHARD: 1
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJson(needs.preflight.outputs.checks_windows_matrix) }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
submodules: false
|
|
|
|
- name: Try to exclude workspace from Windows Defender (best-effort)
|
|
shell: pwsh
|
|
run: |
|
|
$cmd = Get-Command Add-MpPreference -ErrorAction SilentlyContinue
|
|
if (-not $cmd) {
|
|
Write-Host "Add-MpPreference not available, skipping Defender exclusions."
|
|
exit 0
|
|
}
|
|
|
|
try {
|
|
# Defender sometimes intercepts process spawning (vitest workers). If this fails
|
|
# (eg hardened images), keep going and rely on worker limiting above.
|
|
Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE" -ErrorAction Stop
|
|
Add-MpPreference -ExclusionProcess "node.exe" -ErrorAction Stop
|
|
Write-Host "Defender exclusions applied."
|
|
} catch {
|
|
Write-Warning "Failed to apply Defender exclusions, continuing. $($_.Exception.Message)"
|
|
}
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24.x
|
|
check-latest: false
|
|
|
|
- name: Setup pnpm + cache store
|
|
id: pnpm-cache
|
|
uses: ./.github/actions/setup-pnpm-store-cache
|
|
with:
|
|
pnpm-version: "10.33.0"
|
|
cache-key-suffix: "node24"
|
|
use-restore-keys: "false"
|
|
use-actions-cache: "true"
|
|
|
|
- name: Runtime versions
|
|
run: |
|
|
node -v
|
|
npm -v
|
|
pnpm -v
|
|
|
|
- name: Capture node path
|
|
run: |
|
|
node_bin="$(dirname "$(node -p 'process.execPath')")"
|
|
if command -v cygpath >/dev/null 2>&1; then
|
|
node_bin="$(cygpath -u "$node_bin")"
|
|
fi
|
|
echo "NODE_BIN=$node_bin" >> "$GITHUB_ENV"
|
|
|
|
- name: Install dependencies
|
|
env:
|
|
CI: true
|
|
run: |
|
|
export PATH="$NODE_BIN:$PATH"
|
|
which node
|
|
node -v
|
|
pnpm -v
|
|
# Persist Windows-native postinstall outputs in the pnpm store so restored
|
|
# caches can skip repeated rebuild/download work on later shards/runs.
|
|
pnpm install --frozen-lockfile --prefer-offline --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true --config.side-effects-cache=true || pnpm install --frozen-lockfile --prefer-offline --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true --config.side-effects-cache=true
|
|
|
|
- name: Save pnpm store cache
|
|
if: steps.pnpm-cache.outputs.cache-enabled == 'true' && steps.pnpm-cache.outputs.cache-hit != 'true'
|
|
uses: actions/cache/save@v5
|
|
continue-on-error: true
|
|
with:
|
|
path: ${{ steps.pnpm-cache.outputs.store-path }}
|
|
key: ${{ steps.pnpm-cache.outputs.primary-key }}
|
|
|
|
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
|
|
env:
|
|
TASK: ${{ matrix.task }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
case "$TASK" in
|
|
test)
|
|
# Linux owns the full repo test suite. Keep the Windows runner focused on
|
|
# Windows-native process/path wrappers so platform regressions fail fast.
|
|
pnpm test:windows:ci
|
|
;;
|
|
*)
|
|
echo "Unsupported Windows checks task: $TASK" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
macos-node:
|
|
permissions:
|
|
contents: read
|
|
name: ${{ matrix.check_name }}
|
|
needs: [preflight]
|
|
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_macos_node == 'true' }}
|
|
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-6vcpu-macos-latest' || 'macos-latest' }}
|
|
timeout-minutes: 20
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJson(needs.preflight.outputs.macos_node_matrix) }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
submodules: false
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
install-bun: "false"
|
|
|
|
- name: TS tests (macOS)
|
|
env:
|
|
NODE_OPTIONS: --max-old-space-size=4096
|
|
OPENCLAW_VITEST_MAX_WORKERS: 2
|
|
TASK: ${{ matrix.task }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
case "$TASK" in
|
|
test)
|
|
# Linux owns the full repo test suite. Keep macOS CI focused on
|
|
# launchd/Homebrew/runtime path coverage and the process-group wrapper.
|
|
pnpm test:macos:ci
|
|
;;
|
|
*)
|
|
echo "Unsupported macOS node task: $TASK" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
macos-swift:
|
|
permissions:
|
|
contents: read
|
|
name: "macos-swift"
|
|
needs: [preflight]
|
|
if: needs.preflight.outputs.run_macos_swift == 'true'
|
|
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-12vcpu-macos-latest' || 'macos-latest' }}
|
|
timeout-minutes: 20
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
submodules: false
|
|
|
|
- name: Install XcodeGen / SwiftLint / SwiftFormat
|
|
run: brew install xcodegen swiftlint swiftformat
|
|
|
|
- name: Detect Swift toolchain cache key
|
|
id: swift-toolchain
|
|
run: |
|
|
set -euo pipefail
|
|
xcode_version="$(xcodebuild -version | tr '\n' ' ' | sed 's/ */ /g; s/ $//')"
|
|
swift_version="$(swift --version | head -n 1)"
|
|
toolchain_key="$(printf '%s\n%s\n' "$xcode_version" "$swift_version" | shasum -a 256 | awk '{print $1}')"
|
|
echo "key=$toolchain_key" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Cache SwiftPM
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: ~/Library/Caches/org.swift.swiftpm
|
|
key: ${{ runner.os }}-swiftpm-${{ hashFiles('apps/macos/Package.resolved') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-swiftpm-
|
|
|
|
- name: Cache Swift build directory
|
|
id: swift-build-cache
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: apps/macos/.build
|
|
key: ${{ runner.os }}-swift-build-v2-${{ steps.swift-toolchain.outputs.key }}-${{ hashFiles('apps/macos/Package.swift', 'apps/macos/Package.resolved', 'apps/macos/Sources/**', 'apps/macos/Tests/**', 'apps/shared/OpenClawKit/Package.swift', 'apps/shared/OpenClawKit/Sources/**', 'Swabble/Package.swift', 'Swabble/Sources/**') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-swift-build-v2-${{ steps.swift-toolchain.outputs.key }}-
|
|
|
|
- name: Preserve Swift build cache hit
|
|
if: steps.swift-build-cache.outputs.cache-hit == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
# Exact source-hash cache hits already match these inputs; checkout
|
|
# mtimes are the only reason SwiftPM rebuilds cached products.
|
|
find apps/macos/Sources apps/macos/Tests apps/shared/OpenClawKit/Sources Swabble/Sources apps/macos/.build/checkouts \
|
|
-type f -exec touch -t 200001010000 {} +
|
|
touch -t 200001010000 \
|
|
apps/macos/Package.swift \
|
|
apps/macos/Package.resolved \
|
|
apps/shared/OpenClawKit/Package.swift \
|
|
Swabble/Package.swift
|
|
|
|
- name: Show toolchain
|
|
run: |
|
|
sw_vers
|
|
xcodebuild -version
|
|
swift --version
|
|
|
|
- name: Swift lint
|
|
run: |
|
|
swiftlint --config .swiftlint.yml
|
|
swiftformat --lint apps/macos/Sources --config .swiftformat
|
|
|
|
- name: Swift build (release)
|
|
run: |
|
|
set -euo pipefail
|
|
for attempt in 1 2 3; do
|
|
# The macOS lane validates the desktop app build; the CLI product is
|
|
# intentionally left to its own narrower surfaces instead of making
|
|
# this lane rebuild the whole package graph.
|
|
if swift build --package-path apps/macos --product OpenClaw --configuration release; then
|
|
exit 0
|
|
fi
|
|
echo "swift build failed (attempt $attempt/3). Retrying…"
|
|
sleep $((attempt * 20))
|
|
done
|
|
exit 1
|
|
|
|
- name: Swift test
|
|
run: |
|
|
set -euo pipefail
|
|
for attempt in 1 2 3; do
|
|
if swift test --package-path apps/macos --parallel --enable-code-coverage --show-codecov-path; then
|
|
exit 0
|
|
fi
|
|
echo "swift test failed (attempt $attempt/3). Retrying…"
|
|
sleep $((attempt * 20))
|
|
done
|
|
exit 1
|
|
|
|
android:
|
|
permissions:
|
|
contents: read
|
|
name: ${{ matrix.check_name }}
|
|
needs: [preflight]
|
|
if: needs.preflight.outputs.run_android_job == 'true'
|
|
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
|
|
timeout-minutes: 20
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJson(needs.preflight.outputs.android_matrix) }}
|
|
steps:
|
|
- name: Checkout
|
|
shell: bash
|
|
env:
|
|
CHECKOUT_REPO: ${{ github.repository }}
|
|
CHECKOUT_SHA: ${{ github.sha }}
|
|
CHECKOUT_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
workdir="$GITHUB_WORKSPACE"
|
|
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
|
|
|
reset_checkout_dir() {
|
|
mkdir -p "$workdir"
|
|
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
|
}
|
|
|
|
checkout_attempt() {
|
|
local attempt="$1"
|
|
|
|
reset_checkout_dir
|
|
git init "$workdir" >/dev/null
|
|
git config --global --add safe.directory "$workdir"
|
|
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
|
git -C "$workdir" config gc.auto 0
|
|
|
|
timeout --signal=TERM 30s git -C "$workdir" \
|
|
-c protocol.version=2 \
|
|
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
|
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
|
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
|
|
|
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
|
test -x "$workdir/apps/android/gradlew" || return 1
|
|
echo "checkout attempt ${attempt}/5 succeeded"
|
|
}
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if checkout_attempt "$attempt"; then
|
|
exit 0
|
|
fi
|
|
echo "checkout attempt ${attempt}/5 failed"
|
|
sleep $((attempt * 5))
|
|
done
|
|
|
|
echo "checkout failed after 5 attempts" >&2
|
|
exit 1
|
|
|
|
- name: Setup Java
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
distribution: temurin
|
|
# Keep sdkmanager on the stable JDK path for Linux CI runners.
|
|
java-version: 17
|
|
cache: gradle
|
|
cache-dependency-path: |
|
|
apps/android/**/*.gradle*
|
|
apps/android/**/gradle-wrapper.properties
|
|
apps/android/gradle/libs.versions.toml
|
|
|
|
- name: Setup Android SDK cmdline-tools
|
|
run: |
|
|
set -euo pipefail
|
|
ANDROID_SDK_ROOT="$HOME/.android-sdk"
|
|
CMDLINE_TOOLS_VERSION="12266719"
|
|
ARCHIVE="commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip"
|
|
URL="https://dl.google.com/android/repository/${ARCHIVE}"
|
|
|
|
mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools"
|
|
curl -fsSL "$URL" -o "/tmp/${ARCHIVE}"
|
|
rm -rf "$ANDROID_SDK_ROOT/cmdline-tools/latest"
|
|
unzip -q "/tmp/${ARCHIVE}" -d "$ANDROID_SDK_ROOT/cmdline-tools"
|
|
mv "$ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools" "$ANDROID_SDK_ROOT/cmdline-tools/latest"
|
|
|
|
echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV"
|
|
echo "ANDROID_HOME=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV"
|
|
echo "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin" >> "$GITHUB_PATH"
|
|
echo "$ANDROID_SDK_ROOT/platform-tools" >> "$GITHUB_PATH"
|
|
|
|
- name: Install Android SDK packages
|
|
run: |
|
|
yes | sdkmanager --sdk_root="${ANDROID_SDK_ROOT}" --licenses >/dev/null
|
|
sdkmanager --sdk_root="${ANDROID_SDK_ROOT}" --install \
|
|
"platform-tools" \
|
|
"platforms;android-36" \
|
|
"build-tools;36.0.0"
|
|
|
|
- name: Run Android ${{ matrix.task }}
|
|
working-directory: apps/android
|
|
env:
|
|
TASK: ${{ matrix.task }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
case "$TASK" in
|
|
test-play)
|
|
./gradlew --no-daemon --build-cache :app:testPlayDebugUnitTest
|
|
;;
|
|
test-third-party)
|
|
./gradlew --no-daemon --build-cache :app:testThirdPartyDebugUnitTest
|
|
;;
|
|
build-play)
|
|
./gradlew --no-daemon --build-cache :app:assemblePlayDebug
|
|
;;
|
|
*)
|
|
echo "Unsupported Android task: $TASK" >&2
|
|
exit 1
|
|
;;
|
|
esac
|