mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 02:06:43 +00:00
build(client): make @openclaw/gateway-client publishable to npm (#111707)
This commit is contained in:
@@ -204,7 +204,7 @@ jobs:
|
||||
- name: Pack publishable core packages
|
||||
id: core_package_tarballs
|
||||
env:
|
||||
CORE_PACKAGE_DIRS: packages/ai packages/gateway-protocol
|
||||
CORE_PACKAGE_DIRS: packages/ai packages/gateway-protocol packages/gateway-client
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PACKAGE_VERSION="$(node -p "require('./package.json').version")"
|
||||
@@ -386,6 +386,7 @@ jobs:
|
||||
set -euo pipefail
|
||||
AI_TARBALL_PATH=""
|
||||
GATEWAY_PROTOCOL_TARBALL_PATH=""
|
||||
GATEWAY_CLIENT_TARBALL_PATH=""
|
||||
if [[ -n "$CORE_PACKAGE_TARBALL_DIR" ]]; then
|
||||
mapfile -t AI_TARBALLS < <(find "$CORE_PACKAGE_TARBALL_DIR" -maxdepth 1 -type f -name 'openclaw-ai-*.tgz' -print | sort)
|
||||
if node -e 'const pkg = require("./package.json"); process.exit(pkg.dependencies?.["@openclaw/ai"] ? 0 : 1)'; then
|
||||
@@ -410,6 +411,17 @@ jobs:
|
||||
echo "Frozen target without a publishable @openclaw/gateway-protocol package contains unexpected artifacts." >&2
|
||||
exit 1
|
||||
fi
|
||||
mapfile -t GATEWAY_CLIENT_TARBALLS < <(find "$CORE_PACKAGE_TARBALL_DIR" -maxdepth 1 -type f -name 'openclaw-gateway-client-*.tgz' -print | sort)
|
||||
if [[ -f packages/gateway-client/package.json ]] && node -e 'const pkg = require("./packages/gateway-client/package.json"); process.exit(pkg.openclaw?.release?.publishToNpm === true ? 0 : 1)'; then
|
||||
if [[ "${#GATEWAY_CLIENT_TARBALLS[@]}" -ne 1 ]]; then
|
||||
echo "Expected exactly one prepared @openclaw/gateway-client tarball, found ${#GATEWAY_CLIENT_TARBALLS[@]}." >&2
|
||||
exit 1
|
||||
fi
|
||||
GATEWAY_CLIENT_TARBALL_PATH="${GATEWAY_CLIENT_TARBALLS[0]}"
|
||||
elif [[ "${#GATEWAY_CLIENT_TARBALLS[@]}" -ne 0 ]]; then
|
||||
echo "Frozen target without a publishable @openclaw/gateway-client package contains unexpected artifacts." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
PACK_OUTPUT="$RUNNER_TEMP/npm-pack-output.txt"
|
||||
pnpm pack --json 2>&1 | tee "$PACK_OUTPUT"
|
||||
@@ -508,6 +520,9 @@ jobs:
|
||||
GATEWAY_PROTOCOL_TARBALL_NAME=""
|
||||
GATEWAY_PROTOCOL_TARBALL_SHA256=""
|
||||
GATEWAY_PROTOCOL_PACKAGE_VERSION=""
|
||||
GATEWAY_CLIENT_TARBALL_NAME=""
|
||||
GATEWAY_CLIENT_TARBALL_SHA256=""
|
||||
GATEWAY_CLIENT_PACKAGE_VERSION=""
|
||||
ARTIFACT_DIR="$RUNNER_TEMP/openclaw-npm-preflight"
|
||||
rm -rf "$ARTIFACT_DIR"
|
||||
mkdir -p "$ARTIFACT_DIR"
|
||||
@@ -536,6 +551,18 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [[ -n "$GATEWAY_CLIENT_TARBALL_PATH" ]]; then
|
||||
GATEWAY_CLIENT_TARBALL_NAME="$(basename "$GATEWAY_CLIENT_TARBALL_PATH")"
|
||||
GATEWAY_CLIENT_TARBALL_SHA256="$(sha256sum "$GATEWAY_CLIENT_TARBALL_PATH" | awk '{print $1}')"
|
||||
GATEWAY_CLIENT_PACKAGE_VERSION="$(
|
||||
tar -xOf "$GATEWAY_CLIENT_TARBALL_PATH" package/package.json |
|
||||
node -e 'let raw = ""; process.stdin.on("data", (chunk) => (raw += chunk)); process.stdin.on("end", () => process.stdout.write(JSON.parse(raw).version));'
|
||||
)"
|
||||
if [[ "$GATEWAY_CLIENT_PACKAGE_VERSION" != "$PACKAGE_VERSION" ]]; then
|
||||
echo "Prepared @openclaw/gateway-client version ${GATEWAY_CLIENT_PACKAGE_VERSION} does not match openclaw ${PACKAGE_VERSION}." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [[ -n "$CORE_PACKAGE_TARBALL_DIR" ]]; then
|
||||
cp "$CORE_PACKAGE_TARBALL_DIR"/*.tgz "$ARTIFACT_DIR/"
|
||||
cp "$CORE_PACKAGE_TARBALL_DIR/SHA256SUMS" "$ARTIFACT_DIR/core-packages-SHA256SUMS"
|
||||
@@ -544,7 +571,7 @@ jobs:
|
||||
printf '%s\n' "$RELEASE_TAG" > "$ARTIFACT_DIR/release-tag.txt"
|
||||
printf '%s\n' "$RELEASE_SHA" > "$ARTIFACT_DIR/release-sha.txt"
|
||||
printf '%s\n' "$RELEASE_NPM_DIST_TAG" > "$ARTIFACT_DIR/release-npm-dist-tag.txt"
|
||||
ARTIFACT_DIR="$ARTIFACT_DIR" RELEASE_TAG="$RELEASE_TAG" RELEASE_SHA="$RELEASE_SHA" RELEASE_NPM_DIST_TAG="$RELEASE_NPM_DIST_TAG" PACKAGE_VERSION="$PACKAGE_VERSION" TARBALL_NAME="$TARBALL_NAME" TARBALL_SHA256="$TARBALL_SHA256" AI_PACKAGE_VERSION="$AI_PACKAGE_VERSION" AI_TARBALL_NAME="$AI_TARBALL_NAME" AI_TARBALL_SHA256="$AI_TARBALL_SHA256" GATEWAY_PROTOCOL_PACKAGE_VERSION="$GATEWAY_PROTOCOL_PACKAGE_VERSION" GATEWAY_PROTOCOL_TARBALL_NAME="$GATEWAY_PROTOCOL_TARBALL_NAME" GATEWAY_PROTOCOL_TARBALL_SHA256="$GATEWAY_PROTOCOL_TARBALL_SHA256" node <<'NODE'
|
||||
ARTIFACT_DIR="$ARTIFACT_DIR" RELEASE_TAG="$RELEASE_TAG" RELEASE_SHA="$RELEASE_SHA" RELEASE_NPM_DIST_TAG="$RELEASE_NPM_DIST_TAG" PACKAGE_VERSION="$PACKAGE_VERSION" TARBALL_NAME="$TARBALL_NAME" TARBALL_SHA256="$TARBALL_SHA256" AI_PACKAGE_VERSION="$AI_PACKAGE_VERSION" AI_TARBALL_NAME="$AI_TARBALL_NAME" AI_TARBALL_SHA256="$AI_TARBALL_SHA256" GATEWAY_PROTOCOL_PACKAGE_VERSION="$GATEWAY_PROTOCOL_PACKAGE_VERSION" GATEWAY_PROTOCOL_TARBALL_NAME="$GATEWAY_PROTOCOL_TARBALL_NAME" GATEWAY_PROTOCOL_TARBALL_SHA256="$GATEWAY_PROTOCOL_TARBALL_SHA256" GATEWAY_CLIENT_PACKAGE_VERSION="$GATEWAY_CLIENT_PACKAGE_VERSION" GATEWAY_CLIENT_TARBALL_NAME="$GATEWAY_CLIENT_TARBALL_NAME" GATEWAY_CLIENT_TARBALL_SHA256="$GATEWAY_CLIENT_TARBALL_SHA256" node <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const manifest = {
|
||||
@@ -577,6 +604,16 @@ jobs:
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(process.env.GATEWAY_CLIENT_TARBALL_NAME
|
||||
? [
|
||||
{
|
||||
packageName: "@openclaw/gateway-client",
|
||||
packageVersion: process.env.GATEWAY_CLIENT_PACKAGE_VERSION,
|
||||
tarballName: process.env.GATEWAY_CLIENT_TARBALL_NAME,
|
||||
tarballSha256: process.env.GATEWAY_CLIENT_TARBALL_SHA256,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
dependencyTarballs: process.env.AI_TARBALL_NAME
|
||||
? [
|
||||
@@ -1046,6 +1083,7 @@ jobs:
|
||||
EXPECTED_PACKAGE_VERSION="$(node -p "require('./package.json').version")"
|
||||
SEEN_AI_TARBALL="false"
|
||||
SEEN_GATEWAY_PROTOCOL_TARBALL="false"
|
||||
SEEN_GATEWAY_CLIENT_TARBALL="false"
|
||||
while IFS=$'\t' read -r package_name package_version tarball_name tarball_sha256; do
|
||||
if [[ -z "$tarball_name" || "$tarball_name" != "$(basename -- "$tarball_name")" || "$tarball_name" != *.tgz ]]; then
|
||||
echo "Prepared core package tarball name is unsafe: $tarball_name" >&2
|
||||
@@ -1060,6 +1098,10 @@ jobs:
|
||||
[[ "$SEEN_GATEWAY_PROTOCOL_TARBALL" == "false" ]] || { echo "Prepared core package manifest contains duplicate @openclaw/gateway-protocol metadata." >&2; exit 1; }
|
||||
SEEN_GATEWAY_PROTOCOL_TARBALL="true"
|
||||
;;
|
||||
"@openclaw/gateway-client")
|
||||
[[ "$SEEN_GATEWAY_CLIENT_TARBALL" == "false" ]] || { echo "Prepared core package manifest contains duplicate @openclaw/gateway-client metadata." >&2; exit 1; }
|
||||
SEEN_GATEWAY_CLIENT_TARBALL="true"
|
||||
;;
|
||||
*)
|
||||
echo "Prepared core package manifest contains unsupported package: $package_name" >&2
|
||||
exit 1
|
||||
@@ -1093,6 +1135,15 @@ jobs:
|
||||
echo "Frozen target without a publishable @openclaw/gateway-protocol package contains unexpected artifacts." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -f packages/gateway-client/package.json ]] && node -e 'const pkg = require("./packages/gateway-client/package.json"); process.exit(pkg.openclaw?.release?.publishToNpm === true ? 0 : 1)'; then
|
||||
if [[ "$SEEN_GATEWAY_CLIENT_TARBALL" != "true" ]]; then
|
||||
echo "Prepared @openclaw/gateway-client tarball is missing from the manifest." >&2
|
||||
exit 1
|
||||
fi
|
||||
elif jq -e '.corePackageTarballs[] | select(.packageName == "@openclaw/gateway-client")' "$MANIFEST_FILE" >/dev/null || find preflight-tarball -maxdepth 1 -type f -name 'openclaw-gateway-client-*.tgz' -print -quit | grep -q .; then
|
||||
echo "Frozen target without a publishable @openclaw/gateway-client package contains unexpected artifacts." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "tarball_name=$ARTIFACT_TARBALL_NAME" >> "$GITHUB_OUTPUT"
|
||||
echo "tarball_sha256=$ARTIFACT_TARBALL_SHA256" >> "$GITHUB_OUTPUT"
|
||||
echo "tarball_path=$ARTIFACT_TARBALL_PATH" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Publish the reference Gateway WebSocket client for the first time.
|
||||
- Record the client extraction from the Gateway protocol implementation in #87797.
|
||||
- Support the current Gateway wire protocol, protocol v4.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 OpenClaw Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,134 @@
|
||||
# `@openclaw/gateway-client`
|
||||
|
||||
Reference WebSocket client for the OpenClaw Gateway protocol. It provides the
|
||||
connection state machine used by OpenClaw's own Node and browser clients:
|
||||
challenge-based authentication, typed protocol frames, request correlation,
|
||||
timeouts, reconnect backoff, device-token handling, and event delivery.
|
||||
|
||||
The current wire protocol is version 4. General clients must negotiate v4 with
|
||||
`minProtocol: 4` and `maxProtocol: 4`. See the
|
||||
[Gateway protocol specification](https://docs.openclaw.ai/gateway/protocol) for
|
||||
the complete handshake, authentication, role, scope, and method contracts.
|
||||
|
||||
## Versioning
|
||||
|
||||
Package versions follow the OpenClaw calendar release train: `YYYY.M.PATCH`,
|
||||
including the OpenClaw prerelease suffix when applicable. The package version is
|
||||
separate from the negotiated wire protocol number.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @openclaw/gateway-client @openclaw/gateway-protocol
|
||||
```
|
||||
|
||||
Node consumers use the `ws` transport included as a runtime dependency. Browser
|
||||
consumers provide their platform WebSocket through the browser-safe protocol
|
||||
client surface.
|
||||
|
||||
## Entry points
|
||||
|
||||
- `@openclaw/gateway-client` exports the Node `GatewayClient`, device-auth
|
||||
helpers, readiness helpers, and timeout utilities.
|
||||
- `@openclaw/gateway-client/browser` exports the browser-safe protocol client,
|
||||
browser device-auth lifecycle, reconnect policy, and lightweight protocol
|
||||
constants. Its module graph does not import Node built-ins or `ws`.
|
||||
- `@openclaw/gateway-client/readiness` exports helpers that delay client startup
|
||||
until the event loop can process Gateway IO.
|
||||
- `@openclaw/gateway-client/timeouts` exports timeout constants and safe timer
|
||||
resolution helpers.
|
||||
|
||||
## Node quickstart
|
||||
|
||||
```ts
|
||||
import { GatewayClient } from "@openclaw/gateway-client";
|
||||
import { PROTOCOL_VERSION } from "@openclaw/gateway-protocol/version";
|
||||
|
||||
const connected = Promise.withResolvers<void>();
|
||||
const client = new GatewayClient({
|
||||
url: "ws://127.0.0.1:18789",
|
||||
token: process.env.OPENCLAW_GATEWAY_TOKEN,
|
||||
minProtocol: PROTOCOL_VERSION, // v4
|
||||
maxProtocol: PROTOCOL_VERSION, // v4
|
||||
onHelloOk: () => connected.resolve(),
|
||||
onConnectError: (error) => connected.reject(error),
|
||||
onEvent: (event) => {
|
||||
console.log(event.event, event.payload);
|
||||
},
|
||||
});
|
||||
|
||||
client.start();
|
||||
await connected.promise;
|
||||
|
||||
const status = await client.request("status", {});
|
||||
console.log(status);
|
||||
|
||||
client.stop();
|
||||
```
|
||||
|
||||
The client waits for the Gateway's `connect.challenge` event before sending its
|
||||
`connect` request. It includes the challenge nonce in device authentication and
|
||||
does not fall back to a pre-challenge handshake. `onHelloOk` fires only after the
|
||||
Gateway accepts the v4 connection, so requests should wait for that callback.
|
||||
|
||||
For remote connections, use `wss://`. Plaintext `ws://` is allowed by default
|
||||
only for loopback addresses. Authentication material and Gateway traffic must
|
||||
not cross an untrusted network without transport security.
|
||||
|
||||
## Browser clients
|
||||
|
||||
Import `@openclaw/gateway-client/browser` when the host owns the WebSocket
|
||||
adapter and device-key storage. The browser entry includes
|
||||
`GatewayProtocolClient` and `GatewayBrowserDeviceAuthLifecycle`; it deliberately
|
||||
omits the Node transport, TLS fingerprint handling, and private-network address
|
||||
policy.
|
||||
|
||||
The host is responsible for:
|
||||
|
||||
- creating a `GatewayProtocolSocket` adapter around the browser WebSocket;
|
||||
- loading and storing browser device identity and issued device tokens;
|
||||
- signing the challenge-bound device payload;
|
||||
- supplying the client identity, role, scopes, and authentication selection;
|
||||
- choosing close and reconnect behavior for product-specific errors.
|
||||
|
||||
The shared protocol client still owns frame parsing, request correlation,
|
||||
challenge ordering, timeout cleanup, sequence-gap detection, and reconnect
|
||||
scheduling.
|
||||
|
||||
## Defaults and reconnect behavior
|
||||
|
||||
The Node client starts with a 30 second request timeout, a 15 second
|
||||
connect-challenge timeout, and exponential reconnect delays from 1 second to 30
|
||||
seconds with a multiplier of 2. Server-provided startup retry hints may override
|
||||
the next delay.
|
||||
|
||||
The canonical defaults table and the server policy fields that can replace
|
||||
pre-handshake values are documented in the
|
||||
[Gateway protocol specification](https://docs.openclaw.ai/gateway/protocol#client-constants).
|
||||
|
||||
Use the `./timeouts` entry point when a host must align readiness or watchdog
|
||||
budgets with these defaults. Use the `./readiness` entry point when startup must
|
||||
wait for an event-loop probe before opening the socket.
|
||||
|
||||
## Bundled internals
|
||||
|
||||
The retry supervisor and the small `@openclaw/net-policy/ip` implementation are
|
||||
inlined into the published JavaScript and declarations. They are implementation
|
||||
details, not public exports or supported API surfaces. `ipaddr.js` remains an
|
||||
external dependency because the inlined IP helpers use its public runtime and
|
||||
types.
|
||||
|
||||
`ws`, `@openclaw/gateway-protocol`, and `ipaddr.js` remain external in the
|
||||
published distribution. Consumers should import protocol types and constants
|
||||
from `@openclaw/gateway-protocol`, not from bundled implementation paths.
|
||||
|
||||
## Contract notes
|
||||
|
||||
- The client is inert at module import and construction time. `start()` opens
|
||||
the socket; `stop()` closes it and rejects pending requests.
|
||||
- A request uses `request(method, params)` after `hello-ok`. Passing
|
||||
`timeoutMs: null` creates an intentionally unbounded request.
|
||||
- Device identity persistence, signing, proxy routing, TLS formatting, and
|
||||
logging stay host-owned through `GatewayClientHostDeps`.
|
||||
- Protocol changes are additive first. Incompatible changes require an explicit
|
||||
wire-version decision and coordinated server/client follow-through.
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "@openclaw/gateway-client",
|
||||
"version": "2026.7.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openclaw/gateway-client",
|
||||
"version": "2026.7.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ipaddr.js": "2.4.0",
|
||||
"ws": "8.21.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz",
|
||||
"integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.21.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
|
||||
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,31 @@
|
||||
{
|
||||
"name": "@openclaw/gateway-client",
|
||||
"version": "0.0.0-private",
|
||||
"private": true,
|
||||
"files": [
|
||||
"dist"
|
||||
"version": "2026.7.2",
|
||||
"description": "Reference WebSocket client for the OpenClaw Gateway protocol",
|
||||
"keywords": [
|
||||
"client",
|
||||
"gateway",
|
||||
"openclaw",
|
||||
"websocket"
|
||||
],
|
||||
"homepage": "https://github.com/openclaw/openclaw#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/openclaw/openclaw/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/openclaw/openclaw.git",
|
||||
"directory": "packages/gateway-client"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"npm-shrinkwrap.json",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"CHANGELOG.md"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"main": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.mts",
|
||||
@@ -35,8 +56,18 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@openclaw/gateway-protocol": "workspace:*",
|
||||
"@openclaw/net-policy": "workspace:*",
|
||||
"@openclaw/retry": "workspace:*",
|
||||
"ipaddr.js": "2.4.0",
|
||||
"ws": "8.21.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"openclaw": {
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+3
-6
@@ -2090,12 +2090,9 @@ importers:
|
||||
'@openclaw/gateway-protocol':
|
||||
specifier: workspace:*
|
||||
version: link:../gateway-protocol
|
||||
'@openclaw/net-policy':
|
||||
specifier: workspace:*
|
||||
version: link:../net-policy
|
||||
'@openclaw/retry':
|
||||
specifier: workspace:*
|
||||
version: link:../retry
|
||||
ipaddr.js:
|
||||
specifier: 2.4.0
|
||||
version: 2.4.0
|
||||
ws:
|
||||
specifier: 8.21.0
|
||||
version: 8.21.0
|
||||
|
||||
@@ -477,7 +477,11 @@ export function resolveReleaseCheckLocalPackageTarballs(
|
||||
const gatewayProtocolTarballs = tarballs.filter(
|
||||
(tarballPath) => localPackageNameForTarball(tarballPath) === "@openclaw/gateway-protocol",
|
||||
);
|
||||
const recognizedTarballs = aiTarballs.length + gatewayProtocolTarballs.length;
|
||||
const gatewayClientTarballs = tarballs.filter(
|
||||
(tarballPath) => localPackageNameForTarball(tarballPath) === "@openclaw/gateway-client",
|
||||
);
|
||||
const recognizedTarballs =
|
||||
aiTarballs.length + gatewayProtocolTarballs.length + gatewayClientTarballs.length;
|
||||
if (recognizedTarballs !== tarballs.length) {
|
||||
throw new Error(
|
||||
`release-check: ${RELEASE_CHECK_LOCAL_PACKAGE_TARBALL_DIR_ENV} contains an unsupported package tarball.`,
|
||||
@@ -487,9 +491,13 @@ export function resolveReleaseCheckLocalPackageTarballs(
|
||||
const aiTarballRequirement = requiresAi
|
||||
? "exactly one @openclaw/ai tarball"
|
||||
: "no @openclaw/ai tarballs";
|
||||
if (aiTarballs.length !== expectedAiTarballs || gatewayProtocolTarballs.length > 1) {
|
||||
if (
|
||||
aiTarballs.length !== expectedAiTarballs ||
|
||||
gatewayProtocolTarballs.length > 1 ||
|
||||
gatewayClientTarballs.length > 1
|
||||
) {
|
||||
throw new Error(
|
||||
`release-check: ${RELEASE_CHECK_LOCAL_PACKAGE_TARBALL_DIR_ENV} must contain ${aiTarballRequirement} and at most one @openclaw/gateway-protocol tarball; found ${aiTarballs.length} and ${gatewayProtocolTarballs.length}.`,
|
||||
`release-check: ${RELEASE_CHECK_LOCAL_PACKAGE_TARBALL_DIR_ENV} must contain ${aiTarballRequirement}, at most one @openclaw/gateway-protocol tarball, and at most one @openclaw/gateway-client tarball; found ${aiTarballs.length}, ${gatewayProtocolTarballs.length}, and ${gatewayClientTarballs.length}.`,
|
||||
);
|
||||
}
|
||||
return tarballs;
|
||||
@@ -510,6 +518,9 @@ function localPackageNameForTarball(tarballPath: string): string | undefined {
|
||||
if (/^openclaw-gateway-protocol(?:-.+)?\.tgz$/.test(filename)) {
|
||||
return "@openclaw/gateway-protocol";
|
||||
}
|
||||
if (/^openclaw-gateway-client(?:-.+)?\.tgz$/.test(filename)) {
|
||||
return "@openclaw/gateway-client";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,11 @@ type SyncPluginVersionsOptions = {
|
||||
};
|
||||
|
||||
const OPENCLAW_VERSION_RANGE_RE = /^>=\d{4}\.\d{1,2}\.\d{1,2}(?:[-.][^"\s]+)?$/u;
|
||||
const VERSION_ALIGNED_PACKAGE_DIRS = ["packages/ai", "packages/gateway-protocol"] as const;
|
||||
const VERSION_ALIGNED_PACKAGE_DIRS = [
|
||||
"packages/ai",
|
||||
"packages/gateway-client",
|
||||
"packages/gateway-protocol",
|
||||
] as const;
|
||||
|
||||
function syncOpenClawDependencyRange(
|
||||
deps: Record<string, string> | undefined,
|
||||
|
||||
@@ -497,6 +497,15 @@ describe("generate-npm-shrinkwrap", () => {
|
||||
).toEqual(["packages/gateway-protocol"]);
|
||||
});
|
||||
|
||||
it("targets the changed publishable gateway client shrinkwrap", () => {
|
||||
expect(
|
||||
shrinkwrapPackageDirsForChangedPaths([
|
||||
"packages/gateway-client/package.json",
|
||||
"packages/gateway-client/npm-shrinkwrap.json",
|
||||
]).map(repoRelativePath),
|
||||
).toEqual(["packages/gateway-client"]);
|
||||
});
|
||||
|
||||
it("targets changed tracked shrinkwraps for private packages", () => {
|
||||
expect(
|
||||
shrinkwrapPackageDirsForChangedPaths(["extensions/vault/package.json"]).map(repoRelativePath),
|
||||
@@ -509,6 +518,7 @@ describe("generate-npm-shrinkwrap", () => {
|
||||
);
|
||||
|
||||
expect(packageDirs).toContain("");
|
||||
expect(packageDirs).toContain("packages/gateway-client");
|
||||
expect(packageDirs).toContain("packages/gateway-protocol");
|
||||
expect(packageDirs).toContain("extensions/acpx");
|
||||
expect(packageDirs).toContain("extensions/vault");
|
||||
|
||||
@@ -257,4 +257,21 @@ describe("minimal npm extended-stable workflow", () => {
|
||||
"find preflight-tarball -type f -name '*.tgz'",
|
||||
);
|
||||
});
|
||||
|
||||
it("publishes gateway packages in manifest order before the root package", () => {
|
||||
const parsed = workflow();
|
||||
const preflightPack = step(
|
||||
parsed.jobs?.preflight_openclaw_npm,
|
||||
"Pack publishable core packages",
|
||||
);
|
||||
const publish = step(parsed.jobs?.publish_openclaw_npm, "Publish");
|
||||
expect(preflightPack.env?.CORE_PACKAGE_DIRS).toBe(
|
||||
"packages/ai packages/gateway-protocol packages/gateway-client",
|
||||
);
|
||||
expect(readFileSync(workflowPath, "utf8")).toContain('packageName: "@openclaw/gateway-client"');
|
||||
expect(publish.run).toContain(".corePackageTarballs[] | [.packageName, .tarballName] | @tsv");
|
||||
expect(publish.run).toContain(
|
||||
'bash scripts/openclaw-npm-publish.sh --publish "${publish_target}"',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3679,9 +3679,13 @@ describe("package artifact reuse", () => {
|
||||
expect(npmWorkflow).toContain("dependencyTarballs: process.env.AI_TARBALL_NAME");
|
||||
expect(npmWorkflow).toContain('packageName: "@openclaw/ai"');
|
||||
expect(npmWorkflow).toContain('packageName: "@openclaw/gateway-protocol"');
|
||||
expect(npmWorkflow).toContain("CORE_PACKAGE_DIRS: packages/ai packages/gateway-protocol");
|
||||
expect(npmWorkflow).toContain('packageName: "@openclaw/gateway-client"');
|
||||
expect(npmWorkflow).toContain(
|
||||
"CORE_PACKAGE_DIRS: packages/ai packages/gateway-protocol packages/gateway-client",
|
||||
);
|
||||
expect(npmWorkflow).toContain("AI_TARBALL_SHA256");
|
||||
expect(npmWorkflow).toContain("GATEWAY_PROTOCOL_TARBALL_SHA256");
|
||||
expect(npmWorkflow).toContain("GATEWAY_CLIENT_TARBALL_SHA256");
|
||||
expect(npmWorkflow).toContain("does not match openclaw");
|
||||
expect(npmWorkflow).toContain("Frozen target does not depend on @openclaw/ai");
|
||||
expect(npmWorkflow).toContain("core-packages-SHA256SUMS");
|
||||
|
||||
@@ -35,10 +35,12 @@ describe("release-check", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "openclaw-release-check-tarball-test-"));
|
||||
try {
|
||||
writeFileSync(join(root, "openclaw-ai-2026.6.33.tgz"), "fixture");
|
||||
writeFileSync(join(root, "openclaw-gateway-client-2026.6.33.tgz"), "fixture");
|
||||
writeFileSync(join(root, "openclaw-gateway-protocol-2026.6.33.tgz"), "fixture");
|
||||
writeFileSync(join(root, "SHA256SUMS"), "fixture");
|
||||
expect(resolveReleaseCheckLocalPackageTarballs(root)).toEqual([
|
||||
join(root, "openclaw-ai-2026.6.33.tgz"),
|
||||
join(root, "openclaw-gateway-client-2026.6.33.tgz"),
|
||||
join(root, "openclaw-gateway-protocol-2026.6.33.tgz"),
|
||||
]);
|
||||
expect(resolveReleaseCheckLocalPackageTarballs(undefined)).toEqual([]);
|
||||
@@ -47,12 +49,17 @@ describe("release-check", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts a gateway-only core package directory when the root does not require AI", () => {
|
||||
it("accepts gateway core packages when the root does not require AI", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "openclaw-release-check-tarball-test-"));
|
||||
try {
|
||||
const gatewayTarball = join(root, "openclaw-gateway-protocol-2026.7.2.tgz");
|
||||
const gatewayClientTarball = join(root, "openclaw-gateway-client-2026.7.2.tgz");
|
||||
writeFileSync(gatewayTarball, "fixture");
|
||||
expect(resolveReleaseCheckLocalPackageTarballs(root, false)).toEqual([gatewayTarball]);
|
||||
writeFileSync(gatewayClientTarball, "fixture");
|
||||
expect(resolveReleaseCheckLocalPackageTarballs(root, false)).toEqual([
|
||||
gatewayClientTarball,
|
||||
gatewayTarball,
|
||||
]);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
@@ -63,6 +70,7 @@ describe("release-check", () => {
|
||||
try {
|
||||
writePackedTarballInstallManifest(root, "/tmp/openclaw.tgz", [
|
||||
"/tmp/openclaw-ai.tgz",
|
||||
"/tmp/openclaw-gateway-client.tgz",
|
||||
"/tmp/openclaw-gateway-protocol.tgz",
|
||||
]);
|
||||
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
|
||||
@@ -72,6 +80,7 @@ describe("release-check", () => {
|
||||
expect(manifest.private).toBe(true);
|
||||
expect(manifest.dependencies).toEqual({
|
||||
"@openclaw/ai": "file:///tmp/openclaw-ai.tgz",
|
||||
"@openclaw/gateway-client": "file:///tmp/openclaw-gateway-client.tgz",
|
||||
"@openclaw/gateway-protocol": "file:///tmp/openclaw-gateway-protocol.tgz",
|
||||
openclaw: "file:///tmp/openclaw.tgz",
|
||||
});
|
||||
@@ -80,19 +89,20 @@ describe("release-check", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("writes a gateway-only local project when the root does not require AI", () => {
|
||||
it("writes a gateway-packages-only local project when the root does not require AI", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "openclaw-release-check-install-test-"));
|
||||
try {
|
||||
writePackedTarballInstallManifest(
|
||||
root,
|
||||
"/tmp/openclaw.tgz",
|
||||
["/tmp/openclaw-gateway-protocol.tgz"],
|
||||
["/tmp/openclaw-gateway-client.tgz", "/tmp/openclaw-gateway-protocol.tgz"],
|
||||
false,
|
||||
);
|
||||
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
|
||||
dependencies?: Record<string, string>;
|
||||
};
|
||||
expect(manifest.dependencies).toEqual({
|
||||
"@openclaw/gateway-client": "file:///tmp/openclaw-gateway-client.tgz",
|
||||
"@openclaw/gateway-protocol": "file:///tmp/openclaw-gateway-protocol.tgz",
|
||||
openclaw: "file:///tmp/openclaw.tgz",
|
||||
});
|
||||
@@ -128,7 +138,9 @@ describe("release-check", () => {
|
||||
preparedDir,
|
||||
"openclaw-gateway-protocol-2026.7.1-beta.3.tgz",
|
||||
);
|
||||
const gatewayClientTarball = join(preparedDir, "openclaw-gateway-client-2026.7.1-beta.3.tgz");
|
||||
writeFileSync(preparedTarball, "fixture");
|
||||
writeFileSync(gatewayClientTarball, "fixture");
|
||||
writeFileSync(gatewayProtocolTarball, "fixture");
|
||||
const tarballs = prepareReleaseCheckLocalPackageTarballs({
|
||||
tmpRoot: root,
|
||||
@@ -137,7 +149,7 @@ describe("release-check", () => {
|
||||
throw new Error("workspace pack should not run");
|
||||
},
|
||||
});
|
||||
expect(tarballs).toEqual([preparedTarball, gatewayProtocolTarball]);
|
||||
expect(tarballs).toEqual([preparedTarball, gatewayClientTarball, gatewayProtocolTarball]);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
+1
-1
@@ -435,7 +435,7 @@ function shouldExternalizeGatewayProtocolDependency(id: string): boolean {
|
||||
}
|
||||
|
||||
function shouldExternalizeGatewayClientDependency(id: string): boolean {
|
||||
return ["ws", "@openclaw/net-policy", "@openclaw/gateway-protocol"].some(
|
||||
return ["ws", "@openclaw/gateway-protocol"].some(
|
||||
(dependency) => id === dependency || id.startsWith(`${dependency}/`),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user