Merge remote-tracking branch 'upstream/main' into feat/gigachat
This commit is contained in:
commit
2ec5b3aab1
124
.github/workflows/ci.yml
vendored
124
.github/workflows/ci.yml
vendored
@ -309,8 +309,6 @@ jobs:
|
||||
needs: [docs-scope, changed-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
env:
|
||||
PLUGIN_EXTENSION_BOUNDARY_ENFORCE_AFTER: "2026-03-24T05:00:00Z"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
@ -323,41 +321,14 @@ jobs:
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Run plugin extension boundary guard with grace period
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
tmp_output="$(mktemp)"
|
||||
if pnpm run lint:plugins:no-extension-imports >"$tmp_output" 2>&1; then
|
||||
cat "$tmp_output"
|
||||
rm -f "$tmp_output"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
status=$?
|
||||
cat "$tmp_output"
|
||||
rm -f "$tmp_output"
|
||||
|
||||
now_epoch="$(date -u +%s)"
|
||||
enforce_epoch="$(date -u -d "$PLUGIN_EXTENSION_BOUNDARY_ENFORCE_AFTER" +%s)"
|
||||
fix_instructions="If you are an LLM agent fixing this: run 'pnpm run lint:plugins:no-extension-imports', remove src/plugins/** -> extensions/** imports where possible, and if the remaining inventory is intentional for now update test/fixtures/plugin-extension-import-boundary-inventory.json in the same PR."
|
||||
|
||||
if [ "$now_epoch" -lt "$enforce_epoch" ]; then
|
||||
echo "::warning::Plugin extension import boundary violations are temporarily allowed until ${PLUGIN_EXTENSION_BOUNDARY_ENFORCE_AFTER}. This grace period ends in one week from the rollout date. After that timestamp this job will fail unless the inventory is reduced or the baseline is intentionally updated. ${fix_instructions}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "::error::Plugin extension import boundary grace period ended at ${PLUGIN_EXTENSION_BOUNDARY_ENFORCE_AFTER}. ${fix_instructions}"
|
||||
exit "$status"
|
||||
- name: Run plugin extension boundary guard
|
||||
run: pnpm run lint:plugins:no-extension-imports
|
||||
|
||||
web-search-provider-boundary:
|
||||
name: "web-search-provider-boundary"
|
||||
needs: [docs-scope, changed-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
env:
|
||||
WEB_SEARCH_PROVIDER_BOUNDARY_ENFORCE_AFTER: "2026-03-24T05:00:00Z"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
@ -370,41 +341,14 @@ jobs:
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Run web search provider boundary guard with grace period
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
tmp_output="$(mktemp)"
|
||||
if pnpm run lint:web-search-provider-boundaries >"$tmp_output" 2>&1; then
|
||||
cat "$tmp_output"
|
||||
rm -f "$tmp_output"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
status=$?
|
||||
cat "$tmp_output"
|
||||
rm -f "$tmp_output"
|
||||
|
||||
now_epoch="$(date -u +%s)"
|
||||
enforce_epoch="$(date -u -d "$WEB_SEARCH_PROVIDER_BOUNDARY_ENFORCE_AFTER" +%s)"
|
||||
fix_instructions="If you are an LLM agent fixing this: run 'pnpm run lint:web-search-provider-boundaries', move provider-specific web-search logic out of core, and if the remaining inventory is intentional for now update test/fixtures/web-search-provider-boundary-inventory.json in the same PR."
|
||||
|
||||
if [ "$now_epoch" -lt "$enforce_epoch" ]; then
|
||||
echo "::warning::Web search provider boundary violations are temporarily allowed until ${WEB_SEARCH_PROVIDER_BOUNDARY_ENFORCE_AFTER}. This grace period ends in one week from the rollout date. After that timestamp this job will fail unless the inventory is reduced or the baseline is intentionally updated. ${fix_instructions}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "::error::Web search provider boundary grace period ended at ${WEB_SEARCH_PROVIDER_BOUNDARY_ENFORCE_AFTER}. ${fix_instructions}"
|
||||
exit "$status"
|
||||
- name: Run web search provider boundary guard
|
||||
run: pnpm run lint:web-search-provider-boundaries
|
||||
|
||||
extension-src-outside-plugin-sdk-boundary:
|
||||
name: "extension-src-outside-plugin-sdk-boundary"
|
||||
needs: [docs-scope, changed-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
env:
|
||||
EXTENSION_PLUGIN_SDK_BOUNDARY_ENFORCE_AFTER: "2026-03-24T05:00:00Z"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
@ -417,41 +361,14 @@ jobs:
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Run extension src boundary guard with grace period
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
tmp_output="$(mktemp)"
|
||||
if pnpm run lint:extensions:no-src-outside-plugin-sdk >"$tmp_output" 2>&1; then
|
||||
cat "$tmp_output"
|
||||
rm -f "$tmp_output"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
status=$?
|
||||
cat "$tmp_output"
|
||||
rm -f "$tmp_output"
|
||||
|
||||
now_epoch="$(date -u +%s)"
|
||||
enforce_epoch="$(date -u -d "$EXTENSION_PLUGIN_SDK_BOUNDARY_ENFORCE_AFTER" +%s)"
|
||||
fix_instructions="If you are an LLM agent fixing this: run 'pnpm run lint:extensions:no-src-outside-plugin-sdk', move extension imports off core src paths and onto src/plugin-sdk/**, and if the remaining inventory is intentional for now update test/fixtures/extension-src-outside-plugin-sdk-inventory.json in the same PR."
|
||||
|
||||
if [ "$now_epoch" -lt "$enforce_epoch" ]; then
|
||||
echo "::warning::Extension src boundary violations are temporarily allowed until ${EXTENSION_PLUGIN_SDK_BOUNDARY_ENFORCE_AFTER}. This grace period ends in one week from the rollout date. After that timestamp this job will fail unless the inventory is reduced or the baseline is intentionally updated. ${fix_instructions}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "::error::Extension src boundary grace period ended at ${EXTENSION_PLUGIN_SDK_BOUNDARY_ENFORCE_AFTER}. ${fix_instructions}"
|
||||
exit "$status"
|
||||
- name: Run extension src boundary guard
|
||||
run: pnpm run lint:extensions:no-src-outside-plugin-sdk
|
||||
|
||||
extension-plugin-sdk-internal-boundary:
|
||||
name: "extension-plugin-sdk-internal-boundary"
|
||||
needs: [docs-scope, changed-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
env:
|
||||
EXTENSION_PLUGIN_SDK_INTERNAL_ENFORCE_AFTER: "2026-03-24T05:00:00Z"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
@ -464,33 +381,8 @@ jobs:
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Run extension plugin-sdk-internal guard with grace period
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
tmp_output="$(mktemp)"
|
||||
if pnpm run lint:extensions:no-plugin-sdk-internal >"$tmp_output" 2>&1; then
|
||||
cat "$tmp_output"
|
||||
rm -f "$tmp_output"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
status=$?
|
||||
cat "$tmp_output"
|
||||
rm -f "$tmp_output"
|
||||
|
||||
now_epoch="$(date -u +%s)"
|
||||
enforce_epoch="$(date -u -d "$EXTENSION_PLUGIN_SDK_INTERNAL_ENFORCE_AFTER" +%s)"
|
||||
fix_instructions="If you are an LLM agent fixing this: run 'pnpm run lint:extensions:no-plugin-sdk-internal', remove extension imports of src/plugin-sdk-internal/** in favor of src/plugin-sdk/**, and if the remaining inventory is intentional for now update test/fixtures/extension-plugin-sdk-internal-inventory.json in the same PR."
|
||||
|
||||
if [ "$now_epoch" -lt "$enforce_epoch" ]; then
|
||||
echo "::warning::Extension plugin-sdk-internal boundary violations are temporarily allowed until ${EXTENSION_PLUGIN_SDK_INTERNAL_ENFORCE_AFTER}. This grace period ends in one week from the rollout date. After that timestamp this job will fail unless the inventory is reduced or the baseline is intentionally updated. ${fix_instructions}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "::error::Extension plugin-sdk-internal boundary grace period ended at ${EXTENSION_PLUGIN_SDK_INTERNAL_ENFORCE_AFTER}. ${fix_instructions}"
|
||||
exit "$status"
|
||||
- name: Run extension plugin-sdk-internal guard
|
||||
run: pnpm run lint:extensions:no-plugin-sdk-internal
|
||||
|
||||
build-smoke:
|
||||
name: "build-smoke"
|
||||
|
||||
214
.github/workflows/plugin-npm-release.yml
vendored
Normal file
214
.github/workflows/plugin-npm-release.yml
vendored
Normal file
@ -0,0 +1,214 @@
|
||||
name: Plugin NPM Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/workflows/plugin-npm-release.yml"
|
||||
- "extensions/**"
|
||||
- "package.json"
|
||||
- "scripts/lib/plugin-npm-release.ts"
|
||||
- "scripts/plugin-npm-publish.sh"
|
||||
- "scripts/plugin-npm-release-check.ts"
|
||||
- "scripts/plugin-npm-release-plan.ts"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish_scope:
|
||||
description: Publish the selected plugins or all publishable plugins from the ref
|
||||
required: true
|
||||
default: selected
|
||||
type: choice
|
||||
options:
|
||||
- selected
|
||||
- all-publishable
|
||||
ref:
|
||||
description: Commit SHA on main to publish from (copy from the preview run)
|
||||
required: true
|
||||
type: string
|
||||
plugins:
|
||||
description: Comma-separated plugin package names to publish when publish_scope=selected
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: plugin-npm-release-${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.23.0"
|
||||
|
||||
jobs:
|
||||
preview_plugins_npm:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
ref_sha: ${{ steps.ref.outputs.sha }}
|
||||
has_candidates: ${{ steps.plan.outputs.has_candidates }}
|
||||
candidate_count: ${{ steps.plan.outputs.candidate_count }}
|
||||
matrix: ${{ steps.plan.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Resolve checked-out ref
|
||||
id: ref
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate ref is on main
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
git merge-base --is-ancestor HEAD origin/main
|
||||
|
||||
- name: Validate publishable plugin metadata
|
||||
env:
|
||||
PUBLISH_SCOPE: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_scope || '' }}
|
||||
RELEASE_PLUGINS: ${{ github.event_name == 'workflow_dispatch' && inputs.plugins || '' }}
|
||||
BASE_REF: ${{ github.event_name != 'workflow_dispatch' && github.event.before || '' }}
|
||||
HEAD_REF: ${{ steps.ref.outputs.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -n "${PUBLISH_SCOPE}" ]]; then
|
||||
release_args=(--selection-mode "${PUBLISH_SCOPE}")
|
||||
if [[ -n "${RELEASE_PLUGINS}" ]]; then
|
||||
release_args+=(--plugins "${RELEASE_PLUGINS}")
|
||||
fi
|
||||
pnpm release:plugins:npm:check -- "${release_args[@]}"
|
||||
elif [[ -n "${BASE_REF}" ]]; then
|
||||
pnpm release:plugins:npm:check -- --base-ref "${BASE_REF}" --head-ref "${HEAD_REF}"
|
||||
else
|
||||
pnpm release:plugins:npm:check
|
||||
fi
|
||||
|
||||
- name: Resolve plugin release plan
|
||||
id: plan
|
||||
env:
|
||||
PUBLISH_SCOPE: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_scope || '' }}
|
||||
RELEASE_PLUGINS: ${{ github.event_name == 'workflow_dispatch' && inputs.plugins || '' }}
|
||||
BASE_REF: ${{ github.event_name != 'workflow_dispatch' && github.event.before || '' }}
|
||||
HEAD_REF: ${{ steps.ref.outputs.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p .local
|
||||
if [[ -n "${PUBLISH_SCOPE}" ]]; then
|
||||
plan_args=(--selection-mode "${PUBLISH_SCOPE}")
|
||||
if [[ -n "${RELEASE_PLUGINS}" ]]; then
|
||||
plan_args+=(--plugins "${RELEASE_PLUGINS}")
|
||||
fi
|
||||
node --import tsx scripts/plugin-npm-release-plan.ts "${plan_args[@]}" > .local/plugin-npm-release-plan.json
|
||||
elif [[ -n "${BASE_REF}" ]]; then
|
||||
node --import tsx scripts/plugin-npm-release-plan.ts --base-ref "${BASE_REF}" --head-ref "${HEAD_REF}" > .local/plugin-npm-release-plan.json
|
||||
else
|
||||
node --import tsx scripts/plugin-npm-release-plan.ts > .local/plugin-npm-release-plan.json
|
||||
fi
|
||||
|
||||
cat .local/plugin-npm-release-plan.json
|
||||
|
||||
candidate_count="$(jq -r '.candidates | length' .local/plugin-npm-release-plan.json)"
|
||||
has_candidates="false"
|
||||
if [[ "${candidate_count}" != "0" ]]; then
|
||||
has_candidates="true"
|
||||
fi
|
||||
matrix_json="$(jq -c '.candidates' .local/plugin-npm-release-plan.json)"
|
||||
|
||||
{
|
||||
echo "candidate_count=${candidate_count}"
|
||||
echo "has_candidates=${has_candidates}"
|
||||
echo "matrix=${matrix_json}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "Plugin release candidates:"
|
||||
jq -r '.candidates[]? | "- \(.packageName)@\(.version) [\(.publishTag)] from \(.packageDir)"' .local/plugin-npm-release-plan.json
|
||||
|
||||
echo "Already published / skipped:"
|
||||
jq -r '.skippedPublished[]? | "- \(.packageName)@\(.version)"' .local/plugin-npm-release-plan.json
|
||||
|
||||
preview_plugin_pack:
|
||||
needs: preview_plugins_npm
|
||||
if: needs.preview_plugins_npm.outputs.has_candidates == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
plugin: ${{ fromJson(needs.preview_plugins_npm.outputs.matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preview_plugins_npm.outputs.ref_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
install-deps: "false"
|
||||
|
||||
- name: Preview publish command
|
||||
run: bash scripts/plugin-npm-publish.sh --dry-run "${{ matrix.plugin.packageDir }}"
|
||||
|
||||
- name: Preview npm pack contents
|
||||
working-directory: ${{ matrix.plugin.packageDir }}
|
||||
run: npm pack --dry-run --json --ignore-scripts
|
||||
|
||||
publish_plugins_npm:
|
||||
needs: [preview_plugins_npm, preview_plugin_pack]
|
||||
if: github.event_name == 'workflow_dispatch' && needs.preview_plugins_npm.outputs.has_candidates == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm-release
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
plugin: ${{ fromJson(needs.preview_plugins_npm.outputs.matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preview_plugins_npm.outputs.ref_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
install-deps: "false"
|
||||
|
||||
- name: Ensure version is not already published
|
||||
env:
|
||||
PACKAGE_NAME: ${{ matrix.plugin.packageName }}
|
||||
PACKAGE_VERSION: ${{ matrix.plugin.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version >/dev/null 2>&1; then
|
||||
echo "${PACKAGE_NAME}@${PACKAGE_VERSION} is already published on npm."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Publish
|
||||
run: bash scripts/plugin-npm-publish.sh --publish "${{ matrix.plugin.packageDir }}"
|
||||
@ -159,6 +159,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Tools/image generation: standardize the stock image create/edit path on the core `image_generate` tool. The old `nano-banana-pro` docs/examples are gone; if you previously copied that sample-skill config, switch to `agents.defaults.imageGenerationModel` for built-in image generation or install a separate third-party skill explicitly.
|
||||
- Skills/image generation: remove the bundled `nano-banana-pro` skill wrapper. Use `agents.defaults.imageGenerationModel.primary: "google/gemini-3-pro-image-preview"` for the native Nano Banana-style path instead.
|
||||
- Plugins/message discovery: require `ChannelMessageActionAdapter.describeMessageTool(...)` for shared `message` tool discovery. The legacy `listActions`, `getCapabilities`, and `getToolSchema` adapter methods are removed. Plugin authors should migrate message discovery to `describeMessageTool(...)` and keep channel-specific action runtime code inside the owning plugin package. Thanks @gumadeiras.
|
||||
- Exec/env sandbox: block build-tool JVM injection (`MAVEN_OPTS`, `SBT_OPTS`, `GRADLE_OPTS`, `ANT_OPTS`), glibc tunable exploitation (`GLIBC_TUNABLES`), and .NET dependency resolution hijack (`DOTNET_ADDITIONAL_DEPS`) from the host exec environment, and restrict Gradle init script redirect (`GRADLE_USER_HOME`) as an override-only block so user-configured Gradle homes still propagate. (#49702)
|
||||
|
||||
## 2026.3.13
|
||||
|
||||
|
||||
@ -28,11 +28,18 @@ enum HostEnvSecurityPolicy {
|
||||
"_JAVA_OPTIONS",
|
||||
"JDK_JAVA_OPTIONS",
|
||||
"PYTHONBREAKPOINT",
|
||||
"DOTNET_STARTUP_HOOKS"
|
||||
"DOTNET_STARTUP_HOOKS",
|
||||
"DOTNET_ADDITIONAL_DEPS",
|
||||
"GLIBC_TUNABLES",
|
||||
"MAVEN_OPTS",
|
||||
"SBT_OPTS",
|
||||
"GRADLE_OPTS",
|
||||
"ANT_OPTS"
|
||||
]
|
||||
|
||||
static let blockedOverrideKeys: Set<String> = [
|
||||
"HOME",
|
||||
"GRADLE_USER_HOME",
|
||||
"ZDOTDIR",
|
||||
"GIT_SSH_COMMAND",
|
||||
"GIT_SSH",
|
||||
|
||||
@ -32,6 +32,9 @@
|
||||
"npmSpec": "@openclaw/bluebubbles",
|
||||
"localPath": "extensions/bluebubbles",
|
||||
"defaultChoice": "npm"
|
||||
},
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,9 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
],
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,9 @@
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts"
|
||||
"setupEntry": "./setup-entry.ts",
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,9 @@
|
||||
"npmSpec": "@openclaw/feishu",
|
||||
"localPath": "extensions/feishu",
|
||||
"defaultChoice": "npm"
|
||||
},
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,9 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
],
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,9 @@
|
||||
"localPath": "extensions/matrix",
|
||||
"defaultChoice": "npm"
|
||||
},
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
},
|
||||
"releaseChecks": {
|
||||
"rootDependencyMirrorAllowlist": [
|
||||
"@matrix-org/matrix-sdk-crypto-nodejs",
|
||||
|
||||
@ -29,6 +29,9 @@
|
||||
"localPath": "extensions/msteams",
|
||||
"defaultChoice": "npm"
|
||||
},
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
},
|
||||
"releaseChecks": {
|
||||
"rootDependencyMirrorAllowlist": [
|
||||
"@microsoft/agents-hosting"
|
||||
|
||||
@ -29,6 +29,9 @@
|
||||
"npmSpec": "@openclaw/nextcloud-talk",
|
||||
"localPath": "extensions/nextcloud-talk",
|
||||
"defaultChoice": "npm"
|
||||
},
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +27,9 @@
|
||||
"localPath": "extensions/nostr",
|
||||
"defaultChoice": "npm"
|
||||
},
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
},
|
||||
"releaseChecks": {
|
||||
"rootDependencyMirrorAllowlist": [
|
||||
"nostr-tools"
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
type ProviderResolveDynamicModelContext,
|
||||
type ProviderRuntimeModel,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
|
||||
import { applyXaiModelCompat, DEFAULT_CONTEXT_TOKENS } from "openclaw/plugin-sdk/provider-models";
|
||||
import {
|
||||
getOpenRouterModelCapabilities,
|
||||
|
||||
@ -28,9 +28,13 @@ vi.mock("openclaw/plugin-sdk/runtime-env", async (importOriginal) => {
|
||||
vi.mock("../sticker-cache.js", () => ({
|
||||
cacheSticker: () => {},
|
||||
getCachedSticker: () => null,
|
||||
getCacheStats: () => ({ count: 0 }),
|
||||
searchStickers: () => [],
|
||||
getAllCachedStickers: () => [],
|
||||
describeStickerImage: async () => null,
|
||||
}));
|
||||
|
||||
let resolveMedia: typeof import("./delivery.js").resolveMedia;
|
||||
import { resolveMedia } from "./delivery.js";
|
||||
|
||||
const MAX_MEDIA_BYTES = 10_000_000;
|
||||
const BOT_TOKEN = "tok123";
|
||||
@ -165,9 +169,7 @@ async function flushRetryTimers() {
|
||||
}
|
||||
|
||||
describe("resolveMedia getFile retry", () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
({ resolveMedia } = await import("./delivery.js"));
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
fetchRemoteMedia.mockReset();
|
||||
saveMediaBuffer.mockReset();
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"description": "OpenClaw Tlon/Urbit channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@tloncorp/api": "https://codeload.github.com/tloncorp/api-beta/tar.gz/7eede1c1a756977b09f96aa14a92e2b06318ae87",
|
||||
"@tloncorp/api": "https://codeload.github.com/tloncorp/api-beta/tar.gz/c121deb82d97970418508691585aea4f71abcf9c",
|
||||
"@tloncorp/tlon-skill": "0.2.2",
|
||||
"@urbit/aura": "^3.0.0",
|
||||
"zod": "^4.3.6"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
|
||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||
import { applyXaiModelCompat } from "openclaw/plugin-sdk/provider-models";
|
||||
import { applyVeniceConfig, VENICE_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
|
||||
@ -12,6 +12,9 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
],
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
|
||||
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
|
||||
import { applyXaiModelCompat } from "openclaw/plugin-sdk/provider-models";
|
||||
import { createToolStreamWrapper } from "openclaw/plugin-sdk/provider-stream";
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
type SecretInput,
|
||||
upsertAuthProfile,
|
||||
validateApiKeyInput,
|
||||
} from "openclaw/plugin-sdk/provider-auth";
|
||||
} from "openclaw/plugin-sdk/provider-auth-api-key";
|
||||
import { DEFAULT_CONTEXT_TOKENS, normalizeModelCompat } from "openclaw/plugin-sdk/provider-models";
|
||||
import { createZaiToolStreamWrapper } from "openclaw/plugin-sdk/provider-stream";
|
||||
import { fetchZaiUsage, resolveLegacyPiAgentAccessToken } from "openclaw/plugin-sdk/provider-usage";
|
||||
|
||||
@ -29,6 +29,9 @@
|
||||
"npmSpec": "@openclaw/zalo",
|
||||
"localPath": "extensions/zalo",
|
||||
"defaultChoice": "npm"
|
||||
},
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,9 @@
|
||||
"localPath": "extensions/zalouser",
|
||||
"defaultChoice": "npm"
|
||||
},
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
},
|
||||
"releaseChecks": {
|
||||
"rootDependencyMirrorAllowlist": [
|
||||
"zca-js"
|
||||
|
||||
@ -414,6 +414,10 @@
|
||||
"types": "./dist/plugin-sdk/provider-auth.d.ts",
|
||||
"default": "./dist/plugin-sdk/provider-auth.js"
|
||||
},
|
||||
"./plugin-sdk/provider-auth-api-key": {
|
||||
"types": "./dist/plugin-sdk/provider-auth-api-key.d.ts",
|
||||
"default": "./dist/plugin-sdk/provider-auth-api-key.js"
|
||||
},
|
||||
"./plugin-sdk/provider-auth-login": {
|
||||
"types": "./dist/plugin-sdk/provider-auth-login.d.ts",
|
||||
"default": "./dist/plugin-sdk/provider-auth-login.js"
|
||||
@ -507,7 +511,7 @@
|
||||
"build:plugin-sdk:dts": "tsc -p tsconfig.plugin-sdk.dts.json || true",
|
||||
"build:strict-smoke": "pnpm canvas:a2ui:bundle && node scripts/tsdown-build.mjs && node scripts/runtime-postbuild.mjs && pnpm build:plugin-sdk:dts",
|
||||
"canvas:a2ui:bundle": "bash scripts/bundle-a2ui.sh",
|
||||
"check": "pnpm check:host-env-policy:swift && pnpm format:check && pnpm tsgo && pnpm plugin-sdk:check-exports && pnpm lint && pnpm lint:tmp:no-random-messaging && pnpm lint:tmp:channel-agnostic-boundaries && pnpm lint:tmp:no-raw-channel-fetch && pnpm lint:agent:ingress-owner && pnpm lint:plugins:no-register-http-handler && pnpm lint:plugins:no-monolithic-plugin-sdk-entry-imports && pnpm lint:plugins:no-extension-src-imports && pnpm lint:plugins:no-extension-test-core-imports && pnpm lint:webhook:no-low-level-body-read && pnpm lint:auth:no-pairing-store-group && pnpm lint:auth:pairing-account-scope",
|
||||
"check": "pnpm check:host-env-policy:swift && pnpm format:check && pnpm tsgo && pnpm plugin-sdk:check-exports && pnpm lint && pnpm lint:tmp:no-random-messaging && pnpm lint:tmp:channel-agnostic-boundaries && pnpm lint:tmp:no-raw-channel-fetch && pnpm lint:agent:ingress-owner && pnpm lint:plugins:no-register-http-handler && pnpm lint:plugins:no-monolithic-plugin-sdk-entry-imports && pnpm lint:plugins:no-extension-src-imports && pnpm lint:plugins:no-extension-test-core-imports && pnpm lint:plugins:no-extension-imports && pnpm lint:extensions:no-src-outside-plugin-sdk && pnpm lint:extensions:no-plugin-sdk-internal && pnpm lint:web-search-provider-boundaries && pnpm lint:webhook:no-low-level-body-read && pnpm lint:auth:no-pairing-store-group && pnpm lint:auth:pairing-account-scope",
|
||||
"check:docs": "pnpm format:docs:check && pnpm lint:docs && pnpm docs:check-i18n-glossary && pnpm docs:check-links",
|
||||
"check:host-env-policy:swift": "node scripts/generate-host-env-security-policy-swift.mjs --check",
|
||||
"check:loc": "node --import tsx scripts/check-ts-max-loc.ts --max 500",
|
||||
@ -589,6 +593,8 @@
|
||||
"protocol:gen:swift": "node --import tsx scripts/protocol-gen-swift.ts",
|
||||
"release:check": "pnpm config:docs:check && node --import tsx scripts/release-check.ts",
|
||||
"release:openclaw:npm:check": "node --import tsx scripts/openclaw-npm-release-check.ts",
|
||||
"release:plugins:npm:check": "node --import tsx scripts/plugin-npm-release-check.ts",
|
||||
"release:plugins:npm:plan": "node --import tsx scripts/plugin-npm-release-plan.ts",
|
||||
"start": "node scripts/run-node.mjs",
|
||||
"test": "node scripts/test-parallel.mjs",
|
||||
"test:all": "pnpm lint && pnpm build && pnpm test && pnpm test:e2e && pnpm test:live && pnpm test:docker:all",
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@ -533,8 +533,8 @@ importers:
|
||||
extensions/tlon:
|
||||
dependencies:
|
||||
'@tloncorp/api':
|
||||
specifier: https://codeload.github.com/tloncorp/api-beta/tar.gz/7eede1c1a756977b09f96aa14a92e2b06318ae87
|
||||
version: https://codeload.github.com/tloncorp/api-beta/tar.gz/7eede1c1a756977b09f96aa14a92e2b06318ae87
|
||||
specifier: https://codeload.github.com/tloncorp/api-beta/tar.gz/c121deb82d97970418508691585aea4f71abcf9c
|
||||
version: https://codeload.github.com/tloncorp/api-beta/tar.gz/c121deb82d97970418508691585aea4f71abcf9c
|
||||
'@tloncorp/tlon-skill':
|
||||
specifier: 0.2.2
|
||||
version: 0.2.2
|
||||
@ -3429,8 +3429,8 @@ packages:
|
||||
resolution: {integrity: sha512-5Kc5CM2Ysn3vTTArBs2vESUt0AQiWZA86yc1TI3B+lxXmtEq133C1nxXNOgnzhrivdPZIh3zLj5gDnZjoLL5GA==}
|
||||
engines: {node: '>=12.17.0'}
|
||||
|
||||
'@tloncorp/api@https://codeload.github.com/tloncorp/api-beta/tar.gz/7eede1c1a756977b09f96aa14a92e2b06318ae87':
|
||||
resolution: {tarball: https://codeload.github.com/tloncorp/api-beta/tar.gz/7eede1c1a756977b09f96aa14a92e2b06318ae87}
|
||||
'@tloncorp/api@https://codeload.github.com/tloncorp/api-beta/tar.gz/c121deb82d97970418508691585aea4f71abcf9c':
|
||||
resolution: {tarball: https://codeload.github.com/tloncorp/api-beta/tar.gz/c121deb82d97970418508691585aea4f71abcf9c}
|
||||
version: 0.0.2
|
||||
|
||||
'@tloncorp/tlon-skill-darwin-arm64@0.2.2':
|
||||
@ -10855,7 +10855,7 @@ snapshots:
|
||||
|
||||
'@tinyhttp/content-disposition@2.2.4': {}
|
||||
|
||||
'@tloncorp/api@https://codeload.github.com/tloncorp/api-beta/tar.gz/7eede1c1a756977b09f96aa14a92e2b06318ae87':
|
||||
'@tloncorp/api@https://codeload.github.com/tloncorp/api-beta/tar.gz/c121deb82d97970418508691585aea4f71abcf9c':
|
||||
dependencies:
|
||||
'@aws-sdk/client-s3': 3.1000.0
|
||||
'@aws-sdk/s3-request-presigner': 3.1000.0
|
||||
|
||||
@ -43,6 +43,7 @@ function isCodeFile(fileName) {
|
||||
function isTestLikeFile(relativePath) {
|
||||
return (
|
||||
/(^|\/)(__tests__|fixtures)\//.test(relativePath) ||
|
||||
/(^|\/)[^/]*test-support\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(relativePath) ||
|
||||
/\.(test|spec)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(relativePath)
|
||||
);
|
||||
}
|
||||
@ -190,7 +191,20 @@ export async function collectExtensionPluginSdkBoundaryInventory(mode) {
|
||||
}
|
||||
|
||||
export async function readExpectedInventory(mode) {
|
||||
return JSON.parse(await fs.readFile(baselinePathByMode[mode], "utf8"));
|
||||
try {
|
||||
return JSON.parse(await fs.readFile(baselinePathByMode[mode], "utf8"));
|
||||
} catch (error) {
|
||||
if (
|
||||
(mode === "plugin-sdk-internal" || mode === "src-outside-plugin-sdk") &&
|
||||
error &&
|
||||
typeof error === "object" &&
|
||||
"code" in error &&
|
||||
error.code === "ENOENT"
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function diffInventory(expected, actual) {
|
||||
|
||||
@ -214,7 +214,14 @@ export async function collectWebSearchProviderBoundaryInventory() {
|
||||
}
|
||||
|
||||
export async function readExpectedInventory() {
|
||||
return JSON.parse(await fs.readFile(baselinePath, "utf8"));
|
||||
try {
|
||||
return JSON.parse(await fs.readFile(baselinePath, "utf8"));
|
||||
} catch (error) {
|
||||
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function diffInventory(expected, actual) {
|
||||
|
||||
394
scripts/lib/plugin-npm-release.ts
Normal file
394
scripts/lib/plugin-npm-release.ts
Normal file
@ -0,0 +1,394 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { parseReleaseVersion } from "../openclaw-npm-release-check.ts";
|
||||
|
||||
export type PluginPackageJson = {
|
||||
name?: string;
|
||||
version?: string;
|
||||
private?: boolean;
|
||||
openclaw?: {
|
||||
extensions?: string[];
|
||||
install?: {
|
||||
npmSpec?: string;
|
||||
};
|
||||
release?: {
|
||||
publishToNpm?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type PublishablePluginPackage = {
|
||||
extensionId: string;
|
||||
packageDir: string;
|
||||
packageName: string;
|
||||
version: string;
|
||||
channel: "stable" | "beta";
|
||||
publishTag: "latest" | "beta";
|
||||
installNpmSpec?: string;
|
||||
};
|
||||
|
||||
export type PluginReleasePlanItem = PublishablePluginPackage & {
|
||||
alreadyPublished: boolean;
|
||||
};
|
||||
|
||||
export type PluginReleasePlan = {
|
||||
all: PluginReleasePlanItem[];
|
||||
candidates: PluginReleasePlanItem[];
|
||||
skippedPublished: PluginReleasePlanItem[];
|
||||
};
|
||||
|
||||
export type PluginReleaseSelectionMode = "selected" | "all-publishable";
|
||||
|
||||
export type GitRangeSelection = {
|
||||
baseRef: string;
|
||||
headRef: string;
|
||||
};
|
||||
|
||||
export type ParsedPluginReleaseArgs = {
|
||||
selection: string[];
|
||||
selectionMode?: PluginReleaseSelectionMode;
|
||||
pluginsFlagProvided: boolean;
|
||||
baseRef?: string;
|
||||
headRef?: string;
|
||||
};
|
||||
|
||||
type PublishablePluginPackageCandidate = {
|
||||
extensionId: string;
|
||||
packageDir: string;
|
||||
packageJson: PluginPackageJson;
|
||||
};
|
||||
|
||||
function readPluginPackageJson(path: string): PluginPackageJson {
|
||||
return JSON.parse(readFileSync(path, "utf8")) as PluginPackageJson;
|
||||
}
|
||||
|
||||
export function parsePluginReleaseSelection(value: string | undefined): string[] {
|
||||
if (!value?.trim()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
...new Set(
|
||||
value
|
||||
.split(/[,\s]+/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
].toSorted();
|
||||
}
|
||||
|
||||
export function parsePluginReleaseSelectionMode(
|
||||
value: string | undefined,
|
||||
): PluginReleaseSelectionMode {
|
||||
if (value === "selected" || value === "all-publishable") {
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Unknown selection mode: ${value ?? "<missing>"}. Expected "selected" or "all-publishable".`,
|
||||
);
|
||||
}
|
||||
|
||||
export function parsePluginReleaseArgs(argv: string[]): ParsedPluginReleaseArgs {
|
||||
let selection: string[] = [];
|
||||
let selectionMode: PluginReleaseSelectionMode | undefined;
|
||||
let pluginsFlagProvided = false;
|
||||
let baseRef: string | undefined;
|
||||
let headRef: string | undefined;
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
if (arg === "--") {
|
||||
continue;
|
||||
}
|
||||
if (arg === "--plugins") {
|
||||
selection = parsePluginReleaseSelection(argv[index + 1]);
|
||||
pluginsFlagProvided = true;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--selection-mode") {
|
||||
selectionMode = parsePluginReleaseSelectionMode(argv[index + 1]);
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--base-ref") {
|
||||
baseRef = argv[index + 1];
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--head-ref") {
|
||||
headRef = argv[index + 1];
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
|
||||
if (pluginsFlagProvided && selection.length === 0) {
|
||||
throw new Error("`--plugins` must include at least one package name.");
|
||||
}
|
||||
if (selectionMode === "selected" && !pluginsFlagProvided) {
|
||||
throw new Error("`--selection-mode selected` requires `--plugins`.");
|
||||
}
|
||||
if (selectionMode === "all-publishable" && pluginsFlagProvided) {
|
||||
throw new Error("`--selection-mode all-publishable` must not be combined with `--plugins`.");
|
||||
}
|
||||
if (selection.length > 0 && (baseRef || headRef)) {
|
||||
throw new Error("Use either --plugins or --base-ref/--head-ref, not both.");
|
||||
}
|
||||
if (selectionMode && (baseRef || headRef)) {
|
||||
throw new Error("Use either --selection-mode or --base-ref/--head-ref, not both.");
|
||||
}
|
||||
if ((baseRef && !headRef) || (!baseRef && headRef)) {
|
||||
throw new Error("Both --base-ref and --head-ref are required together.");
|
||||
}
|
||||
|
||||
return { selection, selectionMode, pluginsFlagProvided, baseRef, headRef };
|
||||
}
|
||||
|
||||
export function collectPublishablePluginPackageErrors(
|
||||
candidate: PublishablePluginPackageCandidate,
|
||||
): string[] {
|
||||
const { packageJson } = candidate;
|
||||
const errors: string[] = [];
|
||||
const packageName = packageJson.name?.trim() ?? "";
|
||||
const packageVersion = packageJson.version?.trim() ?? "";
|
||||
const extensions = packageJson.openclaw?.extensions ?? [];
|
||||
|
||||
if (!packageName.startsWith("@openclaw/")) {
|
||||
errors.push(
|
||||
`package name must start with "@openclaw/"; found "${packageName || "<missing>"}".`,
|
||||
);
|
||||
}
|
||||
if (packageJson.private === true) {
|
||||
errors.push("package.json private must not be true.");
|
||||
}
|
||||
if (!packageVersion) {
|
||||
errors.push("package.json version must be non-empty.");
|
||||
} else if (parseReleaseVersion(packageVersion) === null) {
|
||||
errors.push(
|
||||
`package.json version must match YYYY.M.D or YYYY.M.D-beta.N; found "${packageVersion}".`,
|
||||
);
|
||||
}
|
||||
if (!Array.isArray(extensions) || extensions.length === 0) {
|
||||
errors.push("openclaw.extensions must contain at least one entry.");
|
||||
}
|
||||
if (extensions.some((entry) => typeof entry !== "string" || !entry.trim())) {
|
||||
errors.push("openclaw.extensions must contain only non-empty strings.");
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
export function collectPublishablePluginPackages(
|
||||
rootDir = resolve("."),
|
||||
): PublishablePluginPackage[] {
|
||||
const extensionsDir = join(rootDir, "extensions");
|
||||
const dirs = readdirSync(extensionsDir, { withFileTypes: true }).filter((entry) =>
|
||||
entry.isDirectory(),
|
||||
);
|
||||
|
||||
const publishable: PublishablePluginPackage[] = [];
|
||||
const validationErrors: string[] = [];
|
||||
|
||||
for (const dir of dirs) {
|
||||
const packageDir = join("extensions", dir.name);
|
||||
const absolutePackageDir = join(extensionsDir, dir.name);
|
||||
const packageJsonPath = join(absolutePackageDir, "package.json");
|
||||
let packageJson: PluginPackageJson;
|
||||
try {
|
||||
packageJson = readPluginPackageJson(packageJsonPath);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (packageJson.openclaw?.release?.publishToNpm !== true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const candidate = {
|
||||
extensionId: dir.name,
|
||||
packageDir,
|
||||
packageJson,
|
||||
} satisfies PublishablePluginPackageCandidate;
|
||||
const errors = collectPublishablePluginPackageErrors(candidate);
|
||||
if (errors.length > 0) {
|
||||
validationErrors.push(...errors.map((error) => `${dir.name}: ${error}`));
|
||||
continue;
|
||||
}
|
||||
|
||||
const version = packageJson.version!.trim();
|
||||
const parsedVersion = parseReleaseVersion(version);
|
||||
if (parsedVersion === null) {
|
||||
validationErrors.push(
|
||||
`${dir.name}: package.json version must match YYYY.M.D or YYYY.M.D-beta.N; found "${version}".`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
publishable.push({
|
||||
extensionId: dir.name,
|
||||
packageDir,
|
||||
packageName: packageJson.name!.trim(),
|
||||
version,
|
||||
channel: parsedVersion.channel,
|
||||
publishTag: parsedVersion.channel === "beta" ? "beta" : "latest",
|
||||
installNpmSpec: packageJson.openclaw?.install?.npmSpec?.trim() || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
if (validationErrors.length > 0) {
|
||||
throw new Error(
|
||||
`Publishable plugin metadata validation failed:\n${validationErrors.map((error) => `- ${error}`).join("\n")}`,
|
||||
);
|
||||
}
|
||||
|
||||
return publishable.toSorted((left, right) => left.packageName.localeCompare(right.packageName));
|
||||
}
|
||||
|
||||
export function resolveSelectedPublishablePluginPackages(params: {
|
||||
plugins: PublishablePluginPackage[];
|
||||
selection: string[];
|
||||
}): PublishablePluginPackage[] {
|
||||
if (params.selection.length === 0) {
|
||||
return params.plugins;
|
||||
}
|
||||
|
||||
const byName = new Map(params.plugins.map((plugin) => [plugin.packageName, plugin]));
|
||||
const selected: PublishablePluginPackage[] = [];
|
||||
const missing: string[] = [];
|
||||
|
||||
for (const packageName of params.selection) {
|
||||
const plugin = byName.get(packageName);
|
||||
if (!plugin) {
|
||||
missing.push(packageName);
|
||||
continue;
|
||||
}
|
||||
selected.push(plugin);
|
||||
}
|
||||
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Unknown or non-publishable plugin package selection: ${missing.join(", ")}.`);
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
export function collectChangedExtensionIdsFromPaths(paths: readonly string[]): string[] {
|
||||
const extensionIds = new Set<string>();
|
||||
|
||||
for (const path of paths) {
|
||||
const normalized = path.trim().replaceAll("\\", "/");
|
||||
const match = /^extensions\/([^/]+)\//.exec(normalized);
|
||||
if (match?.[1]) {
|
||||
extensionIds.add(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return [...extensionIds].toSorted();
|
||||
}
|
||||
|
||||
function isNullGitRef(ref: string | undefined): boolean {
|
||||
return !ref || /^0+$/.test(ref);
|
||||
}
|
||||
|
||||
export function collectChangedExtensionIdsFromGitRange(params: {
|
||||
rootDir?: string;
|
||||
gitRange: GitRangeSelection;
|
||||
}): string[] {
|
||||
const rootDir = params.rootDir ?? resolve(".");
|
||||
const { baseRef, headRef } = params.gitRange;
|
||||
|
||||
if (isNullGitRef(baseRef) || isNullGitRef(headRef)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const changedPaths = execFileSync(
|
||||
"git",
|
||||
["diff", "--name-only", "--diff-filter=ACMR", baseRef, headRef, "--", "extensions"],
|
||||
{
|
||||
cwd: rootDir,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
},
|
||||
)
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
return collectChangedExtensionIdsFromPaths(changedPaths);
|
||||
}
|
||||
|
||||
export function resolveChangedPublishablePluginPackages(params: {
|
||||
plugins: PublishablePluginPackage[];
|
||||
changedExtensionIds: readonly string[];
|
||||
}): PublishablePluginPackage[] {
|
||||
if (params.changedExtensionIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const changed = new Set(params.changedExtensionIds);
|
||||
return params.plugins.filter((plugin) => changed.has(plugin.extensionId));
|
||||
}
|
||||
|
||||
export function isPluginVersionPublished(packageName: string, version: string): boolean {
|
||||
const tempDir = mkdtempSync(join(tmpdir(), "openclaw-plugin-npm-view-"));
|
||||
const userconfigPath = join(tempDir, "npmrc");
|
||||
writeFileSync(userconfigPath, "");
|
||||
|
||||
try {
|
||||
execFileSync(
|
||||
"npm",
|
||||
["view", `${packageName}@${version}`, "version", "--userconfig", userconfigPath],
|
||||
{
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
},
|
||||
);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
export function collectPluginReleasePlan(params?: {
|
||||
rootDir?: string;
|
||||
selection?: string[];
|
||||
selectionMode?: PluginReleaseSelectionMode;
|
||||
gitRange?: GitRangeSelection;
|
||||
}): PluginReleasePlan {
|
||||
const allPublishable = collectPublishablePluginPackages(params?.rootDir);
|
||||
const selectedPublishable =
|
||||
params?.selectionMode === "all-publishable"
|
||||
? allPublishable
|
||||
: params?.selection && params.selection.length > 0
|
||||
? resolveSelectedPublishablePluginPackages({
|
||||
plugins: allPublishable,
|
||||
selection: params.selection,
|
||||
})
|
||||
: params?.gitRange
|
||||
? resolveChangedPublishablePluginPackages({
|
||||
plugins: allPublishable,
|
||||
changedExtensionIds: collectChangedExtensionIdsFromGitRange({
|
||||
rootDir: params.rootDir,
|
||||
gitRange: params.gitRange,
|
||||
}),
|
||||
})
|
||||
: allPublishable;
|
||||
|
||||
const all = selectedPublishable.map((plugin) => ({
|
||||
...plugin,
|
||||
alreadyPublished: isPluginVersionPublished(plugin.packageName, plugin.version),
|
||||
}));
|
||||
|
||||
return {
|
||||
all,
|
||||
candidates: all.filter((plugin) => !plugin.alreadyPublished),
|
||||
skippedPublished: all.filter((plugin) => plugin.alreadyPublished),
|
||||
};
|
||||
}
|
||||
@ -93,6 +93,7 @@
|
||||
"json-store",
|
||||
"keyed-async-queue",
|
||||
"provider-auth",
|
||||
"provider-auth-api-key",
|
||||
"provider-auth-login",
|
||||
"provider-catalog",
|
||||
"provider-models",
|
||||
|
||||
45
scripts/plugin-npm-publish.sh
Normal file
45
scripts/plugin-npm-publish.sh
Normal file
@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
mode="${1:-}"
|
||||
package_dir="${2:-}"
|
||||
|
||||
if [[ "${mode}" != "--dry-run" && "${mode}" != "--publish" ]]; then
|
||||
echo "usage: bash scripts/plugin-npm-publish.sh [--dry-run|--publish] <package-dir>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ -z "${package_dir}" ]]; then
|
||||
echo "missing package dir" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
package_name="$(node -e 'const pkg = require(require("node:path").resolve(process.argv[1], "package.json")); console.log(pkg.name)' "${package_dir}")"
|
||||
package_version="$(node -e 'const pkg = require(require("node:path").resolve(process.argv[1], "package.json")); console.log(pkg.version)' "${package_dir}")"
|
||||
publish_cmd=(npm publish --access public --provenance)
|
||||
release_channel="stable"
|
||||
|
||||
if [[ "${package_version}" == *-beta.* ]]; then
|
||||
publish_cmd=(npm publish --access public --tag beta --provenance)
|
||||
release_channel="beta"
|
||||
fi
|
||||
|
||||
echo "Resolved package dir: ${package_dir}"
|
||||
echo "Resolved package name: ${package_name}"
|
||||
echo "Resolved package version: ${package_version}"
|
||||
echo "Resolved release channel: ${release_channel}"
|
||||
echo "Publish auth: GitHub OIDC trusted publishing"
|
||||
|
||||
printf 'Publish command:'
|
||||
printf ' %q' "${publish_cmd[@]}"
|
||||
printf '\n'
|
||||
|
||||
if [[ "${mode}" == "--dry-run" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
(
|
||||
cd "${package_dir}"
|
||||
"${publish_cmd[@]}"
|
||||
)
|
||||
47
scripts/plugin-npm-release-check.ts
Normal file
47
scripts/plugin-npm-release-check.ts
Normal file
@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env -S node --import tsx
|
||||
|
||||
import { pathToFileURL } from "node:url";
|
||||
import {
|
||||
collectChangedExtensionIdsFromGitRange,
|
||||
collectPublishablePluginPackages,
|
||||
parsePluginReleaseArgs,
|
||||
resolveChangedPublishablePluginPackages,
|
||||
resolveSelectedPublishablePluginPackages,
|
||||
} from "./lib/plugin-npm-release.ts";
|
||||
|
||||
export function runPluginNpmReleaseCheck(argv: string[]) {
|
||||
const { selection, selectionMode, baseRef, headRef } = parsePluginReleaseArgs(argv);
|
||||
const publishable = collectPublishablePluginPackages();
|
||||
const selected =
|
||||
selectionMode === "all-publishable"
|
||||
? publishable
|
||||
: selection.length > 0
|
||||
? resolveSelectedPublishablePluginPackages({
|
||||
plugins: publishable,
|
||||
selection,
|
||||
})
|
||||
: baseRef && headRef
|
||||
? resolveChangedPublishablePluginPackages({
|
||||
plugins: publishable,
|
||||
changedExtensionIds: collectChangedExtensionIdsFromGitRange({
|
||||
gitRange: { baseRef, headRef },
|
||||
}),
|
||||
})
|
||||
: publishable;
|
||||
|
||||
console.log("plugin-npm-release-check: publishable plugin metadata looks OK.");
|
||||
if (baseRef && headRef && selected.length === 0) {
|
||||
console.log(
|
||||
` - no publishable plugin package changes detected between ${baseRef} and ${headRef}`,
|
||||
);
|
||||
}
|
||||
for (const plugin of selected) {
|
||||
console.log(
|
||||
` - ${plugin.packageName}@${plugin.version} (${plugin.channel}, ${plugin.extensionId})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
||||
runPluginNpmReleaseCheck(process.argv.slice(2));
|
||||
}
|
||||
18
scripts/plugin-npm-release-plan.ts
Normal file
18
scripts/plugin-npm-release-plan.ts
Normal file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env -S node --import tsx
|
||||
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { collectPluginReleasePlan, parsePluginReleaseArgs } from "./lib/plugin-npm-release.ts";
|
||||
|
||||
export function collectPluginNpmReleasePlan(argv: string[]) {
|
||||
const { selection, selectionMode, baseRef, headRef } = parsePluginReleaseArgs(argv);
|
||||
return collectPluginReleasePlan({
|
||||
selection,
|
||||
selectionMode,
|
||||
gitRange: baseRef && headRef ? { baseRef, headRef } : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
||||
const plan = collectPluginNpmReleasePlan(process.argv.slice(2));
|
||||
console.log(JSON.stringify(plan, null, 2));
|
||||
}
|
||||
@ -34,15 +34,6 @@ const appcastPath = resolve("appcast.xml");
|
||||
const laneBuildMin = 1_000_000_000;
|
||||
const laneFloorAdoptionDateKey = 20260227;
|
||||
|
||||
function normalizePluginSyncVersion(version: string): string {
|
||||
const normalized = version.trim().replace(/^v/, "");
|
||||
const base = /^([0-9]+\.[0-9]+\.[0-9]+)/.exec(normalized)?.[1];
|
||||
if (base) {
|
||||
return base;
|
||||
}
|
||||
return normalized.replace(/[-+].*$/, "");
|
||||
}
|
||||
|
||||
export function collectBundledExtensionRootDependencyGapErrors(params: {
|
||||
rootPackage: PackageJson;
|
||||
extensions: BundledExtension[];
|
||||
@ -190,54 +181,6 @@ export function collectPackUnpackedSizeErrors(results: Iterable<PackResult>): st
|
||||
return errors;
|
||||
}
|
||||
|
||||
function checkPluginVersions() {
|
||||
const rootPackagePath = resolve("package.json");
|
||||
const rootPackage = JSON.parse(readFileSync(rootPackagePath, "utf8")) as PackageJson;
|
||||
const targetVersion = rootPackage.version;
|
||||
const targetBaseVersion = targetVersion ? normalizePluginSyncVersion(targetVersion) : null;
|
||||
|
||||
if (!targetVersion || !targetBaseVersion) {
|
||||
console.error("release-check: root package.json missing version.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const extensionsDir = resolve("extensions");
|
||||
const entries = readdirSync(extensionsDir, { withFileTypes: true }).filter((entry) =>
|
||||
entry.isDirectory(),
|
||||
);
|
||||
|
||||
const mismatches: string[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const packagePath = join(extensionsDir, entry.name, "package.json");
|
||||
let pkg: PackageJson;
|
||||
try {
|
||||
pkg = JSON.parse(readFileSync(packagePath, "utf8")) as PackageJson;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!pkg.name || !pkg.version) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (normalizePluginSyncVersion(pkg.version) !== targetBaseVersion) {
|
||||
mismatches.push(`${pkg.name} (${pkg.version})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (mismatches.length > 0) {
|
||||
console.error(
|
||||
`release-check: plugin versions must match release base ${targetBaseVersion} (root ${targetVersion}):`,
|
||||
);
|
||||
for (const item of mismatches) {
|
||||
console.error(` - ${item}`);
|
||||
}
|
||||
console.error("release-check: run `pnpm plugins:sync` to align plugin versions.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function extractTag(item: string, tag: string): string | null {
|
||||
const escapedTag = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const regex = new RegExp(`<${escapedTag}>([^<]+)</${escapedTag}>`);
|
||||
@ -393,7 +336,6 @@ async function checkPluginSdkExports() {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
checkPluginVersions();
|
||||
checkAppcastSparkleVersions();
|
||||
await checkPluginSdkExports();
|
||||
checkBundledExtensionRootDependencyMirrors();
|
||||
|
||||
@ -487,7 +487,7 @@ const createTargetedEntry = (owner, isolated, filters) => {
|
||||
"run",
|
||||
"--config",
|
||||
"vitest.channels.config.ts",
|
||||
...(forceForks ? ["--pool=forks"] : []),
|
||||
...(forceForks ? ["--pool=forks"] : useVmForks ? ["--pool=vmForks"] : []),
|
||||
...filters,
|
||||
],
|
||||
};
|
||||
|
||||
@ -22,13 +22,17 @@ type SupportedOpenAICompatFields = Pick<
|
||||
| "supportsUsageInStreaming"
|
||||
| "supportsStrictMode"
|
||||
| "maxTokensField"
|
||||
| "thinkingFormat"
|
||||
| "requiresToolResultName"
|
||||
| "requiresAssistantAfterToolResult"
|
||||
| "requiresThinkingAsText"
|
||||
>;
|
||||
|
||||
type SupportedThinkingFormat =
|
||||
| NonNullable<OpenAICompletionsCompat["thinkingFormat"]>
|
||||
| "qwen-chat-template";
|
||||
|
||||
export type ModelCompatConfig = SupportedOpenAICompatFields & {
|
||||
thinkingFormat?: SupportedThinkingFormat;
|
||||
supportsTools?: boolean;
|
||||
toolSchemaProfile?: "xai";
|
||||
nativeWebSearchTool?: boolean;
|
||||
|
||||
@ -22,10 +22,17 @@
|
||||
"_JAVA_OPTIONS",
|
||||
"JDK_JAVA_OPTIONS",
|
||||
"PYTHONBREAKPOINT",
|
||||
"DOTNET_STARTUP_HOOKS"
|
||||
"DOTNET_STARTUP_HOOKS",
|
||||
"DOTNET_ADDITIONAL_DEPS",
|
||||
"GLIBC_TUNABLES",
|
||||
"MAVEN_OPTS",
|
||||
"SBT_OPTS",
|
||||
"GRADLE_OPTS",
|
||||
"ANT_OPTS"
|
||||
],
|
||||
"blockedOverrideKeys": [
|
||||
"HOME",
|
||||
"GRADLE_USER_HOME",
|
||||
"ZDOTDIR",
|
||||
"GIT_SSH_COMMAND",
|
||||
"GIT_SSH",
|
||||
|
||||
@ -58,8 +58,21 @@ describe("isDangerousHostEnvVarName", () => {
|
||||
expect(isDangerousHostEnvVarName("pythonbreakpoint")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("DOTNET_STARTUP_HOOKS")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("dotnet_startup_hooks")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("DOTNET_ADDITIONAL_DEPS")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("dotnet_additional_deps")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("GLIBC_TUNABLES")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("glibc_tunables")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("MAVEN_OPTS")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("maven_opts")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("SBT_OPTS")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("sbt_opts")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("GRADLE_OPTS")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("gradle_opts")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("ANT_OPTS")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("ant_opts")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("PATH")).toBe(false);
|
||||
expect(isDangerousHostEnvVarName("FOO")).toBe(false);
|
||||
expect(isDangerousHostEnvVarName("GRADLE_USER_HOME")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@ -197,6 +210,8 @@ describe("isDangerousHostEnvOverrideVarName", () => {
|
||||
expect(isDangerousHostEnvOverrideVarName("editor")).toBe(true);
|
||||
expect(isDangerousHostEnvOverrideVarName("NPM_CONFIG_USERCONFIG")).toBe(true);
|
||||
expect(isDangerousHostEnvOverrideVarName("git_config_global")).toBe(true);
|
||||
expect(isDangerousHostEnvOverrideVarName("GRADLE_USER_HOME")).toBe(true);
|
||||
expect(isDangerousHostEnvOverrideVarName("gradle_user_home")).toBe(true);
|
||||
expect(isDangerousHostEnvOverrideVarName("BASH_ENV")).toBe(false);
|
||||
expect(isDangerousHostEnvOverrideVarName("FOO")).toBe(false);
|
||||
});
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
// Public agent/model/runtime helpers for plugins that integrate with core agent flows.
|
||||
|
||||
export * from "../agents/agent-scope.js";
|
||||
export * from "../agents/auth-profiles.js";
|
||||
export * from "../agents/current-time.js";
|
||||
export * from "../agents/date-time.js";
|
||||
export * from "../agents/defaults.js";
|
||||
@ -25,3 +24,52 @@ export * from "../agents/vllm-defaults.js";
|
||||
// Intentional public runtime surface: channel plugins use ingress agent helpers directly.
|
||||
export * from "../agents/agent-command.js";
|
||||
export * from "../tts/tts.js";
|
||||
|
||||
export {
|
||||
CLAUDE_CLI_PROFILE_ID,
|
||||
CODEX_CLI_PROFILE_ID,
|
||||
dedupeProfileIds,
|
||||
listProfilesForProvider,
|
||||
markAuthProfileGood,
|
||||
setAuthProfileOrder,
|
||||
upsertAuthProfile,
|
||||
upsertAuthProfileWithLock,
|
||||
repairOAuthProfileIdMismatch,
|
||||
suggestOAuthProfileIdForLegacyDefault,
|
||||
clearRuntimeAuthProfileStoreSnapshots,
|
||||
ensureAuthProfileStore,
|
||||
loadAuthProfileStoreForSecretsRuntime,
|
||||
loadAuthProfileStoreForRuntime,
|
||||
replaceRuntimeAuthProfileStoreSnapshots,
|
||||
loadAuthProfileStore,
|
||||
saveAuthProfileStore,
|
||||
calculateAuthProfileCooldownMs,
|
||||
clearAuthProfileCooldown,
|
||||
clearExpiredCooldowns,
|
||||
getSoonestCooldownExpiry,
|
||||
isProfileInCooldown,
|
||||
markAuthProfileCooldown,
|
||||
markAuthProfileFailure,
|
||||
markAuthProfileUsed,
|
||||
resolveProfilesUnavailableReason,
|
||||
resolveProfileUnusableUntilForDisplay,
|
||||
resolveApiKeyForProfile,
|
||||
resolveAuthProfileDisplayLabel,
|
||||
formatAuthDoctorHint,
|
||||
resolveAuthProfileEligibility,
|
||||
resolveAuthProfileOrder,
|
||||
resolveAuthStorePathForDisplay,
|
||||
} from "../agents/auth-profiles.js";
|
||||
export type {
|
||||
ApiKeyCredential,
|
||||
AuthCredentialReasonCode,
|
||||
AuthProfileCredential,
|
||||
AuthProfileEligibilityReasonCode,
|
||||
AuthProfileFailureReason,
|
||||
AuthProfileIdRepairResult,
|
||||
AuthProfileStore,
|
||||
OAuthCredential,
|
||||
ProfileUsageStats,
|
||||
TokenCredential,
|
||||
TokenExpiryState,
|
||||
} from "../agents/auth-profiles.js";
|
||||
|
||||
21
src/plugin-sdk/provider-auth-api-key.ts
Normal file
21
src/plugin-sdk/provider-auth-api-key.ts
Normal file
@ -0,0 +1,21 @@
|
||||
// Public API-key onboarding helpers for provider plugins.
|
||||
|
||||
export type { OpenClawConfig } from "../config/config.js";
|
||||
export type { SecretInput } from "../config/types.secrets.js";
|
||||
|
||||
export { upsertAuthProfile } from "../agents/auth-profiles.js";
|
||||
export {
|
||||
formatApiKeyPreview,
|
||||
normalizeApiKeyInput,
|
||||
validateApiKeyInput,
|
||||
ensureApiKeyFromOptionEnvOrPrompt,
|
||||
normalizeSecretInputModeInput,
|
||||
promptSecretRefForSetup,
|
||||
resolveSecretInputModeForEnvSelection,
|
||||
} from "../plugins/provider-auth-input.js";
|
||||
export { applyAuthProfileConfig, buildApiKeyCredential } from "../plugins/provider-auth-helpers.js";
|
||||
export { createProviderApiKeyAuthMethod } from "../plugins/provider-api-key-auth.js";
|
||||
export {
|
||||
normalizeOptionalSecretInput,
|
||||
normalizeSecretInput,
|
||||
} from "../utils/normalize-secret-input.js";
|
||||
@ -1,20 +1,18 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
collectExtensionPluginSdkBoundaryInventory,
|
||||
diffInventory,
|
||||
} from "../scripts/check-extension-plugin-sdk-boundary.mjs";
|
||||
import { collectExtensionPluginSdkBoundaryInventory } from "../scripts/check-extension-plugin-sdk-boundary.mjs";
|
||||
|
||||
const repoRoot = process.cwd();
|
||||
const scriptPath = path.join(repoRoot, "scripts", "check-extension-plugin-sdk-boundary.mjs");
|
||||
|
||||
function readBaseline(fileName: string) {
|
||||
return JSON.parse(readFileSync(path.join(repoRoot, "test", "fixtures", fileName), "utf8"));
|
||||
}
|
||||
|
||||
describe("extension src outside plugin-sdk boundary inventory", () => {
|
||||
it("is currently empty", async () => {
|
||||
const inventory = await collectExtensionPluginSdkBoundaryInventory("src-outside-plugin-sdk");
|
||||
|
||||
expect(inventory).toEqual([]);
|
||||
});
|
||||
|
||||
it("produces stable sorted output", async () => {
|
||||
const first = await collectExtensionPluginSdkBoundaryInventory("src-outside-plugin-sdk");
|
||||
const second = await collectExtensionPluginSdkBoundaryInventory("src-outside-plugin-sdk");
|
||||
@ -33,31 +31,7 @@ describe("extension src outside plugin-sdk boundary inventory", () => {
|
||||
).toEqual(first);
|
||||
});
|
||||
|
||||
it("captures known current production violations", async () => {
|
||||
const inventory = await collectExtensionPluginSdkBoundaryInventory("src-outside-plugin-sdk");
|
||||
|
||||
expect(inventory).toContainEqual(
|
||||
expect.objectContaining({
|
||||
file: "extensions/brave/src/brave-web-search-provider.ts",
|
||||
resolvedPath: "src/agents/tools/common.js",
|
||||
}),
|
||||
);
|
||||
expect(inventory).toContainEqual(
|
||||
expect.objectContaining({
|
||||
file: "extensions/discord/src/runtime-api.ts",
|
||||
resolvedPath: "src/config/types.secrets.js",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("matches the checked-in baseline", async () => {
|
||||
const expected = readBaseline("extension-src-outside-plugin-sdk-inventory.json");
|
||||
const actual = await collectExtensionPluginSdkBoundaryInventory("src-outside-plugin-sdk");
|
||||
|
||||
expect(diffInventory(expected, actual)).toEqual({ missing: [], unexpected: [] });
|
||||
});
|
||||
|
||||
it("script json output matches the baseline exactly", () => {
|
||||
it("script json output is empty", () => {
|
||||
const stdout = execFileSync(
|
||||
process.execPath,
|
||||
[scriptPath, "--mode=src-outside-plugin-sdk", "--json"],
|
||||
@ -67,9 +41,7 @@ describe("extension src outside plugin-sdk boundary inventory", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(JSON.parse(stdout)).toEqual(
|
||||
readBaseline("extension-src-outside-plugin-sdk-inventory.json"),
|
||||
);
|
||||
expect(JSON.parse(stdout)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -80,14 +52,7 @@ describe("extension plugin-sdk-internal boundary inventory", () => {
|
||||
expect(inventory).toEqual([]);
|
||||
});
|
||||
|
||||
it("matches the checked-in empty baseline", async () => {
|
||||
const expected = readBaseline("extension-plugin-sdk-internal-inventory.json");
|
||||
const actual = await collectExtensionPluginSdkBoundaryInventory("plugin-sdk-internal");
|
||||
|
||||
expect(diffInventory(expected, actual)).toEqual({ missing: [], unexpected: [] });
|
||||
});
|
||||
|
||||
it("script json output matches the empty baseline exactly", () => {
|
||||
it("script json output is empty", () => {
|
||||
const stdout = execFileSync(
|
||||
process.execPath,
|
||||
[scriptPath, "--mode=plugin-sdk-internal", "--json"],
|
||||
@ -97,8 +62,6 @@ describe("extension plugin-sdk-internal boundary inventory", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(JSON.parse(stdout)).toEqual(
|
||||
readBaseline("extension-plugin-sdk-internal-inventory.json"),
|
||||
);
|
||||
expect(JSON.parse(stdout)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1 +0,0 @@
|
||||
[]
|
||||
@ -1,418 +0,0 @@
|
||||
[
|
||||
{
|
||||
"file": "extensions/discord/src/directory-config.ts",
|
||||
"line": 7,
|
||||
"kind": "import",
|
||||
"specifier": "../../../src/channels/read-only-account-inspect.discord.runtime.js",
|
||||
"resolvedPath": "src/channels/read-only-account-inspect.discord.runtime.js",
|
||||
"reason": "imports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/discord/src/directory-config.ts",
|
||||
"line": 8,
|
||||
"kind": "import",
|
||||
"specifier": "../../../src/channels/read-only-account-inspect.js",
|
||||
"resolvedPath": "src/channels/read-only-account-inspect.js",
|
||||
"reason": "imports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 10,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/agents/tools/common.js",
|
||||
"resolvedPath": "src/agents/tools/common.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 23,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/mention-gating.js",
|
||||
"resolvedPath": "src/channels/mention-gating.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 30,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/plugins/config-schema.js",
|
||||
"resolvedPath": "src/channels/plugins/config-schema.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 34,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/plugins/config-helpers.js",
|
||||
"resolvedPath": "src/channels/plugins/config-helpers.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 38,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/plugins/directory-config-helpers.js",
|
||||
"resolvedPath": "src/channels/plugins/directory-config-helpers.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 39,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/plugins/helpers.js",
|
||||
"resolvedPath": "src/channels/plugins/helpers.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 40,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/plugins/media-limits.js",
|
||||
"resolvedPath": "src/channels/plugins/media-limits.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 46,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/plugins/setup-wizard-helpers.js",
|
||||
"resolvedPath": "src/channels/plugins/setup-wizard-helpers.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 47,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/plugins/pairing-message.js",
|
||||
"resolvedPath": "src/channels/plugins/pairing-message.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 52,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/plugins/setup-helpers.js",
|
||||
"resolvedPath": "src/channels/plugins/setup-helpers.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 53,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/plugins/account-helpers.js",
|
||||
"resolvedPath": "src/channels/plugins/account-helpers.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 59,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/plugins/types.js",
|
||||
"resolvedPath": "src/channels/plugins/types.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 60,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/plugins/types.plugin.js",
|
||||
"resolvedPath": "src/channels/plugins/types.plugin.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 61,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/registry.js",
|
||||
"resolvedPath": "src/channels/registry.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 62,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/reply-prefix.js",
|
||||
"resolvedPath": "src/channels/reply-prefix.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 63,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/config/config.js",
|
||||
"resolvedPath": "src/config/config.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 64,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/config/dangerous-name-matching.js",
|
||||
"resolvedPath": "src/config/dangerous-name-matching.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 70,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/config/runtime-group-policy.js",
|
||||
"resolvedPath": "src/config/runtime-group-policy.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 75,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/config/types.js",
|
||||
"resolvedPath": "src/config/types.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 76,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/config/types.secrets.js",
|
||||
"resolvedPath": "src/config/types.secrets.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 77,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/config/zod-schema.providers-core.js",
|
||||
"resolvedPath": "src/config/zod-schema.providers-core.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 78,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/infra/net/fetch-guard.js",
|
||||
"resolvedPath": "src/infra/net/fetch-guard.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 79,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/infra/outbound/target-errors.js",
|
||||
"resolvedPath": "src/infra/outbound/target-errors.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 80,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/plugins/config-schema.js",
|
||||
"resolvedPath": "src/plugins/config-schema.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 81,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/plugins/runtime/types.js",
|
||||
"resolvedPath": "src/plugins/runtime/types.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 82,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/plugins/types.js",
|
||||
"resolvedPath": "src/plugins/types.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 83,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/routing/session-key.js",
|
||||
"resolvedPath": "src/routing/session-key.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 84,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/security/dm-policy-shared.js",
|
||||
"resolvedPath": "src/security/dm-policy-shared.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 85,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/terminal/links.js",
|
||||
"resolvedPath": "src/terminal/links.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 86,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/wizard/prompts.js",
|
||||
"resolvedPath": "src/wizard/prompts.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/googlechat/runtime-api.ts",
|
||||
"line": 89,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/pairing/pairing-challenge.js",
|
||||
"resolvedPath": "src/pairing/pairing-challenge.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/imessage/runtime-api.ts",
|
||||
"line": 1,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/config/types.imessage.js",
|
||||
"resolvedPath": "src/config/types.imessage.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/imessage/runtime-api.ts",
|
||||
"line": 2,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/plugins/types.plugin.js",
|
||||
"resolvedPath": "src/channels/plugins/types.plugin.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/imessage/runtime-api.ts",
|
||||
"line": 15,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/plugins/media-limits.js",
|
||||
"resolvedPath": "src/channels/plugins/media-limits.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/imessage/runtime-api.ts",
|
||||
"line": 19,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/channels/plugins/normalize/imessage.js",
|
||||
"resolvedPath": "src/channels/plugins/normalize/imessage.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/imessage/runtime-api.ts",
|
||||
"line": 20,
|
||||
"kind": "export",
|
||||
"specifier": "../../src/config/zod-schema.providers-core.js",
|
||||
"resolvedPath": "src/config/zod-schema.providers-core.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/slack/src/directory-config.ts",
|
||||
"line": 8,
|
||||
"kind": "import",
|
||||
"specifier": "../../../src/channels/plugins/normalize/slack.js",
|
||||
"resolvedPath": "src/channels/plugins/normalize/slack.js",
|
||||
"reason": "imports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/slack/src/directory-config.ts",
|
||||
"line": 9,
|
||||
"kind": "import",
|
||||
"specifier": "../../../src/channels/read-only-account-inspect.js",
|
||||
"resolvedPath": "src/channels/read-only-account-inspect.js",
|
||||
"reason": "imports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/slack/src/directory-config.ts",
|
||||
"line": 10,
|
||||
"kind": "import",
|
||||
"specifier": "../../../src/channels/read-only-account-inspect.slack.runtime.js",
|
||||
"resolvedPath": "src/channels/read-only-account-inspect.slack.runtime.js",
|
||||
"reason": "imports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/slack/src/runtime-api.ts",
|
||||
"line": 1,
|
||||
"kind": "export",
|
||||
"specifier": "../../../src/config/config.js",
|
||||
"resolvedPath": "src/config/config.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/slack/src/runtime-api.ts",
|
||||
"line": 2,
|
||||
"kind": "export",
|
||||
"specifier": "../../../src/config/types.slack.js",
|
||||
"resolvedPath": "src/config/types.slack.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/slack/src/runtime-api.ts",
|
||||
"line": 3,
|
||||
"kind": "export",
|
||||
"specifier": "../../../src/channels/plugins/types.js",
|
||||
"resolvedPath": "src/channels/plugins/types.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/slack/src/runtime-api.ts",
|
||||
"line": 19,
|
||||
"kind": "export",
|
||||
"specifier": "../../../src/channels/plugins/normalize/slack.js",
|
||||
"resolvedPath": "src/channels/plugins/normalize/slack.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/slack/src/runtime-api.ts",
|
||||
"line": 23,
|
||||
"kind": "export",
|
||||
"specifier": "../../../src/channels/account-snapshot-fields.js",
|
||||
"resolvedPath": "src/channels/account-snapshot-fields.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/slack/src/runtime-api.ts",
|
||||
"line": 24,
|
||||
"kind": "export",
|
||||
"specifier": "../../../src/config/zod-schema.providers-core.js",
|
||||
"resolvedPath": "src/config/zod-schema.providers-core.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/slack/src/runtime-api.ts",
|
||||
"line": 32,
|
||||
"kind": "export",
|
||||
"specifier": "../../../src/agents/tools/common.js",
|
||||
"resolvedPath": "src/agents/tools/common.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/slack/src/runtime-api.ts",
|
||||
"line": 33,
|
||||
"kind": "export",
|
||||
"specifier": "../../../src/agents/date-time.js",
|
||||
"resolvedPath": "src/agents/date-time.js",
|
||||
"reason": "re-exports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/telegram/src/directory-config.ts",
|
||||
"line": 9,
|
||||
"kind": "import",
|
||||
"specifier": "../../../src/channels/read-only-account-inspect.js",
|
||||
"resolvedPath": "src/channels/read-only-account-inspect.js",
|
||||
"reason": "imports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/telegram/src/directory-config.ts",
|
||||
"line": 10,
|
||||
"kind": "import",
|
||||
"specifier": "../../../src/channels/read-only-account-inspect.telegram.runtime.js",
|
||||
"resolvedPath": "src/channels/read-only-account-inspect.telegram.runtime.js",
|
||||
"reason": "imports core src path outside plugin-sdk from an extension"
|
||||
},
|
||||
{
|
||||
"file": "extensions/whatsapp/src/directory-config.ts",
|
||||
"line": 6,
|
||||
"kind": "import",
|
||||
"specifier": "../../../src/whatsapp/normalize.js",
|
||||
"resolvedPath": "src/whatsapp/normalize.js",
|
||||
"reason": "imports core src path outside plugin-sdk from an extension"
|
||||
}
|
||||
]
|
||||
@ -1 +0,0 @@
|
||||
[]
|
||||
217
test/plugin-npm-release.test.ts
Normal file
217
test/plugin-npm-release.test.ts
Normal file
@ -0,0 +1,217 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
collectChangedExtensionIdsFromPaths,
|
||||
collectPublishablePluginPackageErrors,
|
||||
parsePluginReleaseArgs,
|
||||
parsePluginReleaseSelection,
|
||||
parsePluginReleaseSelectionMode,
|
||||
resolveChangedPublishablePluginPackages,
|
||||
resolveSelectedPublishablePluginPackages,
|
||||
type PublishablePluginPackage,
|
||||
} from "../scripts/lib/plugin-npm-release.ts";
|
||||
|
||||
describe("parsePluginReleaseSelection", () => {
|
||||
it("returns an empty list for blank input", () => {
|
||||
expect(parsePluginReleaseSelection("")).toEqual([]);
|
||||
expect(parsePluginReleaseSelection(" ")).toEqual([]);
|
||||
expect(parsePluginReleaseSelection(undefined)).toEqual([]);
|
||||
});
|
||||
|
||||
it("dedupes and sorts comma or whitespace separated package names", () => {
|
||||
expect(
|
||||
parsePluginReleaseSelection(" @openclaw/zalo, @openclaw/feishu @openclaw/zalo "),
|
||||
).toEqual(["@openclaw/feishu", "@openclaw/zalo"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parsePluginReleaseSelectionMode", () => {
|
||||
it("accepts the supported explicit selection modes", () => {
|
||||
expect(parsePluginReleaseSelectionMode("selected")).toBe("selected");
|
||||
expect(parsePluginReleaseSelectionMode("all-publishable")).toBe("all-publishable");
|
||||
});
|
||||
|
||||
it("rejects unsupported selection modes", () => {
|
||||
expect(() => parsePluginReleaseSelectionMode("all")).toThrowError(
|
||||
'Unknown selection mode: all. Expected "selected" or "all-publishable".',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parsePluginReleaseArgs", () => {
|
||||
it("rejects blank explicit plugin selections", () => {
|
||||
expect(() => parsePluginReleaseArgs(["--plugins", " "])).toThrowError(
|
||||
"`--plugins` must include at least one package name.",
|
||||
);
|
||||
});
|
||||
|
||||
it("requires plugin names for selected explicit publish mode", () => {
|
||||
expect(() => parsePluginReleaseArgs(["--selection-mode", "selected"])).toThrowError(
|
||||
"`--selection-mode selected` requires `--plugins`.",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects plugin names when all-publishable mode is selected", () => {
|
||||
expect(() =>
|
||||
parsePluginReleaseArgs([
|
||||
"--selection-mode",
|
||||
"all-publishable",
|
||||
"--plugins",
|
||||
"@openclaw/zalo",
|
||||
]),
|
||||
).toThrowError("`--selection-mode all-publishable` must not be combined with `--plugins`.");
|
||||
});
|
||||
|
||||
it("parses explicit all-publishable mode", () => {
|
||||
expect(parsePluginReleaseArgs(["--selection-mode", "all-publishable"])).toMatchObject({
|
||||
selectionMode: "all-publishable",
|
||||
selection: [],
|
||||
pluginsFlagProvided: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("collectPublishablePluginPackageErrors", () => {
|
||||
it("accepts a valid publishable plugin package candidate", () => {
|
||||
expect(
|
||||
collectPublishablePluginPackageErrors({
|
||||
extensionId: "zalo",
|
||||
packageDir: "extensions/zalo",
|
||||
packageJson: {
|
||||
name: "@openclaw/zalo",
|
||||
version: "2026.3.15",
|
||||
openclaw: {
|
||||
extensions: ["./index.ts"],
|
||||
release: {
|
||||
publishToNpm: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("flags invalid publishable plugin metadata", () => {
|
||||
expect(
|
||||
collectPublishablePluginPackageErrors({
|
||||
extensionId: "broken",
|
||||
packageDir: "extensions/broken",
|
||||
packageJson: {
|
||||
name: "broken",
|
||||
version: "latest",
|
||||
private: true,
|
||||
openclaw: {
|
||||
extensions: [""],
|
||||
release: {
|
||||
publishToNpm: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toEqual([
|
||||
'package name must start with "@openclaw/"; found "broken".',
|
||||
"package.json private must not be true.",
|
||||
'package.json version must match YYYY.M.D or YYYY.M.D-beta.N; found "latest".',
|
||||
"openclaw.extensions must contain only non-empty strings.",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveSelectedPublishablePluginPackages", () => {
|
||||
const publishablePlugins: PublishablePluginPackage[] = [
|
||||
{
|
||||
extensionId: "feishu",
|
||||
packageDir: "extensions/feishu",
|
||||
packageName: "@openclaw/feishu",
|
||||
version: "2026.3.15",
|
||||
channel: "stable",
|
||||
publishTag: "latest",
|
||||
},
|
||||
{
|
||||
extensionId: "zalo",
|
||||
packageDir: "extensions/zalo",
|
||||
packageName: "@openclaw/zalo",
|
||||
version: "2026.3.15-beta.1",
|
||||
channel: "beta",
|
||||
publishTag: "beta",
|
||||
},
|
||||
];
|
||||
|
||||
it("returns all publishable plugins when no selection is provided", () => {
|
||||
expect(
|
||||
resolveSelectedPublishablePluginPackages({
|
||||
plugins: publishablePlugins,
|
||||
selection: [],
|
||||
}),
|
||||
).toEqual(publishablePlugins);
|
||||
});
|
||||
|
||||
it("filters by selected publishable package names", () => {
|
||||
expect(
|
||||
resolveSelectedPublishablePluginPackages({
|
||||
plugins: publishablePlugins,
|
||||
selection: ["@openclaw/zalo"],
|
||||
}),
|
||||
).toEqual([publishablePlugins[1]]);
|
||||
});
|
||||
|
||||
it("throws when the selection contains an unknown package name", () => {
|
||||
expect(() =>
|
||||
resolveSelectedPublishablePluginPackages({
|
||||
plugins: publishablePlugins,
|
||||
selection: ["@openclaw/missing"],
|
||||
}),
|
||||
).toThrowError("Unknown or non-publishable plugin package selection: @openclaw/missing.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("collectChangedExtensionIdsFromPaths", () => {
|
||||
it("extracts unique extension ids from changed extension paths", () => {
|
||||
expect(
|
||||
collectChangedExtensionIdsFromPaths([
|
||||
"extensions/zalo/index.ts",
|
||||
"extensions/zalo/package.json",
|
||||
"extensions/feishu/src/client.ts",
|
||||
"docs/reference/RELEASING.md",
|
||||
]),
|
||||
).toEqual(["feishu", "zalo"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveChangedPublishablePluginPackages", () => {
|
||||
const publishablePlugins: PublishablePluginPackage[] = [
|
||||
{
|
||||
extensionId: "feishu",
|
||||
packageDir: "extensions/feishu",
|
||||
packageName: "@openclaw/feishu",
|
||||
version: "2026.3.15",
|
||||
channel: "stable",
|
||||
publishTag: "latest",
|
||||
},
|
||||
{
|
||||
extensionId: "zalo",
|
||||
packageDir: "extensions/zalo",
|
||||
packageName: "@openclaw/zalo",
|
||||
version: "2026.3.15-beta.1",
|
||||
channel: "beta",
|
||||
publishTag: "beta",
|
||||
},
|
||||
];
|
||||
|
||||
it("returns only changed publishable plugins", () => {
|
||||
expect(
|
||||
resolveChangedPublishablePluginPackages({
|
||||
plugins: publishablePlugins,
|
||||
changedExtensionIds: ["zalo"],
|
||||
}),
|
||||
).toEqual([publishablePlugins[1]]);
|
||||
});
|
||||
|
||||
it("returns an empty list when no publishable plugins changed", () => {
|
||||
expect(
|
||||
resolveChangedPublishablePluginPackages({
|
||||
plugins: publishablePlugins,
|
||||
changedExtensionIds: [],
|
||||
}),
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
@ -1,24 +1,10 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
collectWebSearchProviderBoundaryInventory,
|
||||
diffInventory,
|
||||
} from "../scripts/check-web-search-provider-boundaries.mjs";
|
||||
import { collectWebSearchProviderBoundaryInventory } from "../scripts/check-web-search-provider-boundaries.mjs";
|
||||
|
||||
const repoRoot = process.cwd();
|
||||
const scriptPath = path.join(repoRoot, "scripts", "check-web-search-provider-boundaries.mjs");
|
||||
const baselinePath = path.join(
|
||||
repoRoot,
|
||||
"test",
|
||||
"fixtures",
|
||||
"web-search-provider-boundary-inventory.json",
|
||||
);
|
||||
|
||||
function readBaseline() {
|
||||
return JSON.parse(readFileSync(baselinePath, "utf8"));
|
||||
}
|
||||
|
||||
describe("web search provider boundary inventory", () => {
|
||||
it("has no remaining production inventory in core", async () => {
|
||||
@ -49,20 +35,12 @@ describe("web search provider boundary inventory", () => {
|
||||
).toEqual(first);
|
||||
});
|
||||
|
||||
it("matches the checked-in baseline", async () => {
|
||||
const expected = readBaseline();
|
||||
const actual = await collectWebSearchProviderBoundaryInventory();
|
||||
|
||||
expect(diffInventory(expected, actual)).toEqual({ missing: [], unexpected: [] });
|
||||
expect(actual).toEqual([]);
|
||||
});
|
||||
|
||||
it("script json output matches the baseline exactly", () => {
|
||||
it("script json output is empty", () => {
|
||||
const stdout = execFileSync(process.execPath, [scriptPath, "--json"], {
|
||||
cwd: repoRoot,
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
expect(JSON.parse(stdout)).toEqual(readBaseline());
|
||||
expect(JSON.parse(stdout)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user