Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/publish-plugin-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ on:
- scripts/publish-plugin.test.sh
- scripts/requires-pull-requests.sh
- scripts/requires-pull-requests.test.sh
- scripts/validate-marketplace.sh
- scripts/validate-marketplace.test.sh
- tools/claude-code/**
- .github/workflows/reusable-publish-plugin.yml
- .github/workflows/publish-plugin-test.yml

Expand All @@ -25,3 +28,7 @@ jobs:
persist-credentials: false
- run: bash scripts/publish-plugin.test.sh
- run: bash scripts/requires-pull-requests.test.sh
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "22"
- run: bash scripts/validate-marketplace.test.sh
7 changes: 1 addition & 6 deletions .github/workflows/reusable-publish-plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,7 @@ jobs:
# The marketplace's own release rule: `claude plugin validate .` passes.
- name: Validate the marketplace
if: steps.copy.outputs.changed == 'true'
run: |
# Locked install (tools/claude-code/package-lock.json), integrity-
# checked; the CLI ships as prebuilt optional packages, no scripts.
(cd tooling/tools/claude-code && npm ci --ignore-scripts --no-audit --no-fund)
export PATH="$PWD/tooling/tools/claude-code/node_modules/.bin:$PATH"
cd marketplace && claude plugin validate .
run: bash tooling/scripts/validate-marketplace.sh tooling/tools/claude-code marketplace

- name: Create the publish pull request
if: steps.copy.outputs.changed == 'true' && !inputs.dry_run
Expand Down
15 changes: 15 additions & 0 deletions scripts/validate-marketplace.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# ABOUTME: Installs the locked Claude Code CLI and runs `claude plugin validate` on a marketplace checkout.
# ABOUTME: Usage: validate-marketplace.sh <tools/claude-code dir> <marketplace dir>; exits non-zero if validation fails.
set -euo pipefail

tool_dir=$(cd "$1" && pwd)
marketplace=$2

# Locked install (tools/claude-code/package-lock.json), integrity-
# checked; the CLI ships as prebuilt optional packages, no scripts.
(cd "$tool_dir" && npm ci --ignore-scripts --no-audit --no-fund)
# Without its postinstall the package's `claude` bin is a stub that refuses to run;
# cli-wrapper.cjs runs the platform package's binary from the same locked install.
cli="$tool_dir/node_modules/@anthropic-ai/claude-code/cli-wrapper.cjs"
cd "$marketplace" && node "$cli" plugin validate .
71 changes: 71 additions & 0 deletions scripts/validate-marketplace.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
# ABOUTME: Tests for validate-marketplace.sh, run against the real locked Claude Code CLI in tools/claude-code.
# ABOUTME: Run with `bash scripts/validate-marketplace.test.sh`; needs Node.js and npm; exits non-zero if any test fails.
set -uo pipefail

SCRIPT="$(cd "$(dirname "$0")" && pwd)/validate-marketplace.sh"
TOOL="$(cd "$(dirname "$0")/../tools/claude-code" && pwd)"
failures=0

check() {
local label=$1; shift
if ! "$@"; then
echo " FAIL: $label"
failures=$((failures + 1))
fi
}

run_test() {
local name=$1
local work
work=$(mktemp -d)
echo "$name"
pushd "$work" > /dev/null || exit 1
"$name" || failures=$((failures + 1))
popd > /dev/null || exit 1
rm -rf "$work"
}

# A copy of the locked tool, so the install never lands in the checkout.
make_tool() {
mkdir tool
cp "$TOOL/package.json" "$TOOL/package-lock.json" tool/
}

make_marketplace() {
mkdir -p marketplace/.claude-plugin marketplace/plugins/example/.claude-plugin
cat > marketplace/.claude-plugin/marketplace.json <<'JSON'
{"name": "example-marketplace", "description": "Example", "owner": {"name": "Example"},
"plugins": [{"name": "example", "source": "./plugins/example", "description": "Example", "category": "productivity"}]}
JSON
echo '{"name": "example", "version": "1.0.0", "description": "Example", "author": {"name": "Example"}}' \
> marketplace/plugins/example/.claude-plugin/plugin.json
}

test_a_valid_marketplace_passes() {
make_tool
make_marketplace
local out
if ! out=$(bash "$SCRIPT" tool marketplace 2>&1); then echo "$out"; echo " FAIL: script failed"; return 1; fi
check "reports validation passed" grep -q "Validation passed" <<<"$out"
}

test_an_invalid_marketplace_is_refused() {
make_tool
make_marketplace
echo '{"name": "example-marketplace", "owner": {"name": "Example"}, "plugins": "not a list"}' \
> marketplace/.claude-plugin/marketplace.json
local out
if out=$(bash "$SCRIPT" tool marketplace 2>&1); then echo " FAIL: script succeeded"; return 1; fi
check "names the invalid field" grep -q "plugins: Invalid input" <<<"$out"
}

for t in $(declare -F | awk '{print $3}' | grep '^test_'); do
run_test "$t"
done

if [ "$failures" -gt 0 ]; then
echo "$failures failure(s)"
exit 1
fi
echo "all passed"
Loading