perf(ci): make push/PR preflight dependency-free and prune the store archive

The manifest planner closure and the protocol coverage script import only
node builtins and relative files (verified importing the full closure with
an empty node_modules under native type stripping), so push/PR preflight
drops the pnpm store restore and install (~30s off the barrier every lane
waits behind). Manual dispatches keep the tsx path for frozen targets, and
the coverage script inlines the record guard under the documented
dependency-free exception.

The store archive accretes every prior lockfile generation through
prefix-key restores (measured 2.05 GiB, ~36s restore in every hosted job);
the warmup writer now prunes to the current lockfile closure before saving.
This commit is contained in:
Peter Steinberger
2026-08-14 19:25:16 -07:00
parent 9d6b64cabf
commit 7e54cc9d19
4 changed files with 44 additions and 4 deletions
+13
View File
@@ -425,6 +425,19 @@ runs:
restore-keys: |
${{ github.repository }}-build-all-v1-${{ inputs.build-all-cache-scope }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-
# Prune before saving: prefix-key restores accrete every prior lockfile
# generation into the archive (measured 2.05 GiB, ~36s restore per job).
# Pruning collapses it to the current lockfile's closure; a dropped entry
# costs one registry refetch in a later job at worst.
- name: Prune pnpm store before save
if: ${{ inputs.install-deps == 'true' && inputs.use-actions-cache == 'true' && (inputs.dependency-cache != 'true' || steps.dependency-cache.outputs.cache-hit != 'true') && inputs.save-actions-cache == 'true' && runner.os != 'Windows' && steps.setup-pnpm.outputs.store-cache-hit != 'true' }}
shell: bash
working-directory: ${{ steps.package-manager.outputs.project-dir }}
run: |
du -sh "${{ steps.setup-pnpm.outputs.store-path }}" || true
pnpm store prune
du -sh "${{ steps.setup-pnpm.outputs.store-path }}" || true
- name: Save pnpm store cache
if: ${{ inputs.install-deps == 'true' && inputs.use-actions-cache == 'true' && (inputs.dependency-cache != 'true' || steps.dependency-cache.outputs.cache-hit != 'true') && inputs.save-actions-cache == 'true' && runner.os != 'Windows' && steps.setup-pnpm.outputs.store-cache-hit != 'true' }}
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
+20 -2
View File
@@ -418,12 +418,19 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
# Push/PR preflight is dependency-free: the manifest planner closure and
# the protocol coverage script import only node builtins and relative
# files, and Node strips their types natively. Only manual dispatches
# (which may check out frozen targets with older planner syntax) still
# install and run through tsx.
- name: Setup manifest pnpm
if: github.event_name == 'workflow_dispatch'
uses: ./.github/actions/setup-pnpm-store-cache
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install manifest dependencies
if: github.event_name == 'workflow_dispatch'
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
- name: Build CI manifest
@@ -455,7 +462,11 @@ jobs:
OPENCLAW_CI_EVENT_NAME: ${{ github.event_name }}
OPENCLAW_CI_RUNNER_BACKEND: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND }}
run: |
node --import tsx --input-type=module <<'EOF'
manifest_node_args=()
if [ "${GITHUB_EVENT_NAME:-}" = "workflow_dispatch" ]; then
manifest_node_args+=(--import tsx)
fi
node "${manifest_node_args[@]}" --input-type=module <<'EOF'
import { appendFileSync, existsSync, readFileSync } from "node:fs";
const eventName = process.env.OPENCLAW_CI_EVENT_NAME ?? "";
@@ -914,7 +925,14 @@ jobs:
- name: Check mobile protocol event coverage
if: steps.manifest.outputs.run_protocol_event_coverage == 'true'
run: node scripts/check-protocol-event-coverage.mjs
run: |
# Dispatches may target frozen checkouts whose script needs the tsx
# shim; push/PR checkouts run the dependency-free .mts natively.
if [ "${GITHUB_EVENT_NAME:-}" = "workflow_dispatch" ]; then
node scripts/check-protocol-event-coverage.mjs
else
node scripts/check-protocol-event-coverage.mts
fi
# Publish one immutable semantic dependency archive before same-repo
# Blacksmith jobs fan out. The GitHub backend uses the pnpm-store cache.
+7 -1
View File
@@ -19,7 +19,13 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { isRecord } from "@openclaw/normalization-core/record-coerce";
// Local record guard by design: preflight runs this script without installed
// dependencies (the dependency-free manifest contract), so the canonical
// @openclaw/normalization-core/record-coerce import cannot resolve here.
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
const GATEWAY_EVENTS_FILE = "src/gateway/server-methods-list.ts";
const GATEWAY_EVENT_CONSTANTS_FILE = "src/gateway/events.ts";
+4 -1
View File
@@ -5602,7 +5602,10 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
(step: WorkflowStep) => step.name === "Run check shard",
).run;
expect(coverageStep.run).toBe("node scripts/check-protocol-event-coverage.mjs");
// Push/PR preflight is dependency-free and runs the .mts natively;
// dispatches (frozen targets) keep the tsx shim path.
expect(coverageStep.run).toContain("node scripts/check-protocol-event-coverage.mts");
expect(coverageStep.run).toContain("node scripts/check-protocol-event-coverage.mjs");
expect(coverageStep.if).toBe("steps.manifest.outputs.run_protocol_event_coverage == 'true'");
expect(checkShardRun).not.toContain("check:protocol-coverage");
});