feat: integrate claude-code-base-action as local subaction (#285)

* feat: integrate claude-code-base-action as local subaction

- Copy claude-code-base-action into base-action/ directory
- Update action.yml to reference ./base-action instead of external repo
- Preserve complete base action structure for future refactoring

This eliminates the external dependency while maintaining modularity.

* feat: consolidate CI workflows and add version bump workflow

- Move base-action test workflows to main .github/workflows/
- Update workflow references to use ./base-action
- Add CI jobs for base-action (test, typecheck, prettier)
- Add bump-claude-code-version workflow for base-action
- Remove redundant .github directory from base-action

This consolidates all CI workflows in one place while maintaining
full test coverage for both the main action and base-action.

* tsc

* copy again

* fix tests

* fix: use absolute path for base-action reference

Replace relative path ./base-action with ${{ github.action_path }}/base-action
to ensure the action works correctly when used in other repositories.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: inline base-action execution to support usage in other repos

Replace uses: ./base-action with direct shell execution since GitHub Actions
doesn't support dynamic paths in composite actions. This ensures the action
works correctly when used in other repositories.

Changes:
- Install Claude Code globally before execution
- Run base-action's index.ts directly with bun
- Pass all required INPUT_* environment variables
- Maintain base-action for future separate publishing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Ashwin Bhat
2025-07-18 13:52:56 -07:00
committed by GitHub
parent 00b4a23551
commit 8335bda243
38 changed files with 3586 additions and 17 deletions

View File

@@ -0,0 +1,10 @@
{
"mcpServers": {
"test-server": {
"type": "stdio",
"command": "bun",
"args": ["simple-mcp-server.ts"],
"env": {}
}
}
}

View File

@@ -0,0 +1,2 @@
engine-strict=true
registry=https://registry.npmjs.org/

View File

@@ -0,0 +1,186 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "mcp-test",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.11.0",
},
},
},
"packages": {
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.12.0", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-m//7RlINx1F3sz3KqwY1WWzVgTcYX52HYk4bJ1hkBXV3zccAEth+jRvG8DBRrdaQuRsPAJOx2MH3zaHNCKL7Zg=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
"eventsource-parser": ["eventsource-parser@3.0.2", "", {}, "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA=="],
"express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
"express-rate-limit": ["express-rate-limit@7.5.0", "", { "peerDependencies": { "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
"mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="],
"pkce-challenge": ["pkce-challenge@5.0.0", "", {}, "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ=="],
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="],
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
"serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"zod": ["zod@3.25.32", "", {}, "sha512-OSm2xTIRfW8CV5/QKgngwmQW/8aPfGdaQFlrGoErlgg/Epm7cjb6K6VEyExfe65a3VybUOnu381edLb0dfJl0g=="],
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
}
}

View File

@@ -0,0 +1,7 @@
{
"name": "mcp-test",
"version": "1.0.0",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.11.0"
}
}

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bun
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({
name: "test-server",
version: "1.0.0",
});
server.tool("test_tool", "A simple test tool", {}, async () => {
return {
content: [
{
type: "text",
text: "Test tool response",
},
],
};
});
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
process.on("exit", () => {
server.close();
});
}
runServer().catch(console.error);

View File

@@ -0,0 +1,114 @@
#!/usr/bin/env bun
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
import { preparePrompt, type PreparePromptInput } from "../src/prepare-prompt";
import { unlink, writeFile, readFile, stat } from "fs/promises";
describe("preparePrompt integration tests", () => {
beforeEach(async () => {
try {
await unlink("/tmp/claude-action/prompt.txt");
} catch {
// Ignore if file doesn't exist
}
});
afterEach(async () => {
try {
await unlink("/tmp/claude-action/prompt.txt");
} catch {
// Ignore if file doesn't exist
}
});
test("should create temporary prompt file when only prompt is provided", async () => {
const input: PreparePromptInput = {
prompt: "This is a test prompt",
promptFile: "",
};
const config = await preparePrompt(input);
expect(config.path).toBe("/tmp/claude-action/prompt.txt");
expect(config.type).toBe("inline");
const fileContent = await readFile(config.path, "utf-8");
expect(fileContent).toBe("This is a test prompt");
const fileStat = await stat(config.path);
expect(fileStat.size).toBeGreaterThan(0);
});
test("should use existing file when promptFile is provided", async () => {
const testFilePath = "/tmp/test-prompt.txt";
await writeFile(testFilePath, "Prompt from file");
const input: PreparePromptInput = {
prompt: "",
promptFile: testFilePath,
};
const config = await preparePrompt(input);
expect(config.path).toBe(testFilePath);
expect(config.type).toBe("file");
await unlink(testFilePath);
});
test("should fail when neither prompt nor promptFile is provided", async () => {
const input: PreparePromptInput = {
prompt: "",
promptFile: "",
};
await expect(preparePrompt(input)).rejects.toThrow(
"Neither 'prompt' nor 'prompt_file' was provided",
);
});
test("should fail when promptFile points to non-existent file", async () => {
const input: PreparePromptInput = {
prompt: "",
promptFile: "/tmp/non-existent-file.txt",
};
await expect(preparePrompt(input)).rejects.toThrow(
"Prompt file '/tmp/non-existent-file.txt' does not exist.",
);
});
test("should fail when prompt is empty", async () => {
const emptyFilePath = "/tmp/empty-prompt.txt";
await writeFile(emptyFilePath, "");
const input: PreparePromptInput = {
prompt: "",
promptFile: emptyFilePath,
};
await expect(preparePrompt(input)).rejects.toThrow("Prompt file is empty");
try {
await unlink(emptyFilePath);
} catch {
// Ignore cleanup errors
}
});
test("should fail when both prompt and promptFile are provided", async () => {
const testFilePath = "/tmp/test-prompt.txt";
await writeFile(testFilePath, "Prompt from file");
const input: PreparePromptInput = {
prompt: "This should cause an error",
promptFile: testFilePath,
};
await expect(preparePrompt(input)).rejects.toThrow(
"Both 'prompt' and 'prompt_file' were provided. Please specify only one.",
);
await unlink(testFilePath);
});
});

View File

@@ -0,0 +1,297 @@
#!/usr/bin/env bun
import { describe, test, expect } from "bun:test";
import { prepareRunConfig, type ClaudeOptions } from "../src/run-claude";
describe("prepareRunConfig", () => {
test("should prepare config with basic arguments", () => {
const options: ClaudeOptions = {};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.claudeArgs.slice(0, 4)).toEqual([
"-p",
"--verbose",
"--output-format",
"stream-json",
]);
});
test("should include promptPath", () => {
const options: ClaudeOptions = {};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.promptPath).toBe("/tmp/test-prompt.txt");
});
test("should include allowed tools in command arguments", () => {
const options: ClaudeOptions = {
allowedTools: "Bash,Read",
};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.claudeArgs).toContain("--allowedTools");
expect(prepared.claudeArgs).toContain("Bash,Read");
});
test("should include disallowed tools in command arguments", () => {
const options: ClaudeOptions = {
disallowedTools: "Bash,Read",
};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.claudeArgs).toContain("--disallowedTools");
expect(prepared.claudeArgs).toContain("Bash,Read");
});
test("should include max turns in command arguments", () => {
const options: ClaudeOptions = {
maxTurns: "5",
};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.claudeArgs).toContain("--max-turns");
expect(prepared.claudeArgs).toContain("5");
});
test("should include mcp config in command arguments", () => {
const options: ClaudeOptions = {
mcpConfig: "/path/to/mcp-config.json",
};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.claudeArgs).toContain("--mcp-config");
expect(prepared.claudeArgs).toContain("/path/to/mcp-config.json");
});
test("should include system prompt in command arguments", () => {
const options: ClaudeOptions = {
systemPrompt: "You are a senior backend engineer.",
};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.claudeArgs).toContain("--system-prompt");
expect(prepared.claudeArgs).toContain("You are a senior backend engineer.");
});
test("should include append system prompt in command arguments", () => {
const options: ClaudeOptions = {
appendSystemPrompt:
"After writing code, be sure to code review yourself.",
};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.claudeArgs).toContain("--append-system-prompt");
expect(prepared.claudeArgs).toContain(
"After writing code, be sure to code review yourself.",
);
});
test("should include fallback model in command arguments", () => {
const options: ClaudeOptions = {
fallbackModel: "claude-sonnet-4-20250514",
};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.claudeArgs).toContain("--fallback-model");
expect(prepared.claudeArgs).toContain("claude-sonnet-4-20250514");
});
test("should use provided prompt path", () => {
const options: ClaudeOptions = {};
const prepared = prepareRunConfig("/custom/prompt/path.txt", options);
expect(prepared.promptPath).toBe("/custom/prompt/path.txt");
});
test("should not include optional arguments when not set", () => {
const options: ClaudeOptions = {};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.claudeArgs).not.toContain("--allowedTools");
expect(prepared.claudeArgs).not.toContain("--disallowedTools");
expect(prepared.claudeArgs).not.toContain("--max-turns");
expect(prepared.claudeArgs).not.toContain("--mcp-config");
expect(prepared.claudeArgs).not.toContain("--system-prompt");
expect(prepared.claudeArgs).not.toContain("--append-system-prompt");
expect(prepared.claudeArgs).not.toContain("--fallback-model");
});
test("should preserve order of claude arguments", () => {
const options: ClaudeOptions = {
allowedTools: "Bash,Read",
maxTurns: "3",
};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.claudeArgs).toEqual([
"-p",
"--verbose",
"--output-format",
"stream-json",
"--allowedTools",
"Bash,Read",
"--max-turns",
"3",
]);
});
test("should preserve order with all options including fallback model", () => {
const options: ClaudeOptions = {
allowedTools: "Bash,Read",
disallowedTools: "Write",
maxTurns: "3",
mcpConfig: "/path/to/config.json",
systemPrompt: "You are a helpful assistant",
appendSystemPrompt: "Be concise",
fallbackModel: "claude-sonnet-4-20250514",
};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.claudeArgs).toEqual([
"-p",
"--verbose",
"--output-format",
"stream-json",
"--allowedTools",
"Bash,Read",
"--disallowedTools",
"Write",
"--max-turns",
"3",
"--mcp-config",
"/path/to/config.json",
"--system-prompt",
"You are a helpful assistant",
"--append-system-prompt",
"Be concise",
"--fallback-model",
"claude-sonnet-4-20250514",
]);
});
describe("maxTurns validation", () => {
test("should accept valid maxTurns value", () => {
const options: ClaudeOptions = { maxTurns: "5" };
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.claudeArgs).toContain("--max-turns");
expect(prepared.claudeArgs).toContain("5");
});
test("should throw error for non-numeric maxTurns", () => {
const options: ClaudeOptions = { maxTurns: "abc" };
expect(() => prepareRunConfig("/tmp/test-prompt.txt", options)).toThrow(
"maxTurns must be a positive number, got: abc",
);
});
test("should throw error for negative maxTurns", () => {
const options: ClaudeOptions = { maxTurns: "-1" };
expect(() => prepareRunConfig("/tmp/test-prompt.txt", options)).toThrow(
"maxTurns must be a positive number, got: -1",
);
});
test("should throw error for zero maxTurns", () => {
const options: ClaudeOptions = { maxTurns: "0" };
expect(() => prepareRunConfig("/tmp/test-prompt.txt", options)).toThrow(
"maxTurns must be a positive number, got: 0",
);
});
});
describe("timeoutMinutes validation", () => {
test("should accept valid timeoutMinutes value", () => {
const options: ClaudeOptions = { timeoutMinutes: "15" };
expect(() =>
prepareRunConfig("/tmp/test-prompt.txt", options),
).not.toThrow();
});
test("should throw error for non-numeric timeoutMinutes", () => {
const options: ClaudeOptions = { timeoutMinutes: "abc" };
expect(() => prepareRunConfig("/tmp/test-prompt.txt", options)).toThrow(
"timeoutMinutes must be a positive number, got: abc",
);
});
test("should throw error for negative timeoutMinutes", () => {
const options: ClaudeOptions = { timeoutMinutes: "-5" };
expect(() => prepareRunConfig("/tmp/test-prompt.txt", options)).toThrow(
"timeoutMinutes must be a positive number, got: -5",
);
});
test("should throw error for zero timeoutMinutes", () => {
const options: ClaudeOptions = { timeoutMinutes: "0" };
expect(() => prepareRunConfig("/tmp/test-prompt.txt", options)).toThrow(
"timeoutMinutes must be a positive number, got: 0",
);
});
});
describe("custom environment variables", () => {
test("should parse empty claudeEnv correctly", () => {
const options: ClaudeOptions = { claudeEnv: "" };
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.env).toEqual({});
});
test("should parse single environment variable", () => {
const options: ClaudeOptions = { claudeEnv: "API_KEY: secret123" };
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.env).toEqual({ API_KEY: "secret123" });
});
test("should parse multiple environment variables", () => {
const options: ClaudeOptions = {
claudeEnv: "API_KEY: secret123\nDEBUG: true\nUSER: testuser",
};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.env).toEqual({
API_KEY: "secret123",
DEBUG: "true",
USER: "testuser",
});
});
test("should handle environment variables with spaces around values", () => {
const options: ClaudeOptions = {
claudeEnv: "API_KEY: secret123 \n DEBUG : true ",
};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.env).toEqual({
API_KEY: "secret123",
DEBUG: "true",
});
});
test("should skip empty lines and comments", () => {
const options: ClaudeOptions = {
claudeEnv:
"API_KEY: secret123\n\n# This is a comment\nDEBUG: true\n# Another comment",
};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.env).toEqual({
API_KEY: "secret123",
DEBUG: "true",
});
});
test("should skip lines without colons", () => {
const options: ClaudeOptions = {
claudeEnv: "API_KEY: secret123\nINVALID_LINE\nDEBUG: true",
};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.env).toEqual({
API_KEY: "secret123",
DEBUG: "true",
});
});
test("should handle undefined claudeEnv", () => {
const options: ClaudeOptions = {};
const prepared = prepareRunConfig("/tmp/test-prompt.txt", options);
expect(prepared.env).toEqual({});
});
});
});

View File

@@ -0,0 +1,150 @@
#!/usr/bin/env bun
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
import { setupClaudeCodeSettings } from "../src/setup-claude-code-settings";
import { tmpdir } from "os";
import { mkdir, writeFile, readFile, rm } from "fs/promises";
import { join } from "path";
const testHomeDir = join(
tmpdir(),
"claude-code-test-home",
Date.now().toString(),
);
const settingsPath = join(testHomeDir, ".claude", "settings.json");
const testSettingsDir = join(testHomeDir, ".claude-test");
const testSettingsPath = join(testSettingsDir, "test-settings.json");
describe("setupClaudeCodeSettings", () => {
beforeEach(async () => {
// Create test home directory and test settings directory
await mkdir(testHomeDir, { recursive: true });
await mkdir(testSettingsDir, { recursive: true });
});
afterEach(async () => {
// Clean up test home directory
await rm(testHomeDir, { recursive: true, force: true });
});
test("should always set enableAllProjectMcpServers to true when no input", async () => {
await setupClaudeCodeSettings(undefined, testHomeDir);
const settingsContent = await readFile(settingsPath, "utf-8");
const settings = JSON.parse(settingsContent);
expect(settings.enableAllProjectMcpServers).toBe(true);
});
test("should merge settings from JSON string input", async () => {
const inputSettings = JSON.stringify({
model: "claude-sonnet-4-20250514",
env: { API_KEY: "test-key" },
});
await setupClaudeCodeSettings(inputSettings, testHomeDir);
const settingsContent = await readFile(settingsPath, "utf-8");
const settings = JSON.parse(settingsContent);
expect(settings.enableAllProjectMcpServers).toBe(true);
expect(settings.model).toBe("claude-sonnet-4-20250514");
expect(settings.env).toEqual({ API_KEY: "test-key" });
});
test("should merge settings from file path input", async () => {
const testSettings = {
hooks: {
PreToolUse: [
{
matcher: "Bash",
hooks: [{ type: "command", command: "echo test" }],
},
],
},
permissions: {
allow: ["Bash", "Read"],
},
};
await writeFile(testSettingsPath, JSON.stringify(testSettings, null, 2));
await setupClaudeCodeSettings(testSettingsPath, testHomeDir);
const settingsContent = await readFile(settingsPath, "utf-8");
const settings = JSON.parse(settingsContent);
expect(settings.enableAllProjectMcpServers).toBe(true);
expect(settings.hooks).toEqual(testSettings.hooks);
expect(settings.permissions).toEqual(testSettings.permissions);
});
test("should override enableAllProjectMcpServers even if false in input", async () => {
const inputSettings = JSON.stringify({
enableAllProjectMcpServers: false,
model: "test-model",
});
await setupClaudeCodeSettings(inputSettings, testHomeDir);
const settingsContent = await readFile(settingsPath, "utf-8");
const settings = JSON.parse(settingsContent);
expect(settings.enableAllProjectMcpServers).toBe(true);
expect(settings.model).toBe("test-model");
});
test("should throw error for invalid JSON string", async () => {
expect(() =>
setupClaudeCodeSettings("{ invalid json", testHomeDir),
).toThrow();
});
test("should throw error for non-existent file path", async () => {
expect(() =>
setupClaudeCodeSettings("/non/existent/file.json", testHomeDir),
).toThrow();
});
test("should handle empty string input", async () => {
await setupClaudeCodeSettings("", testHomeDir);
const settingsContent = await readFile(settingsPath, "utf-8");
const settings = JSON.parse(settingsContent);
expect(settings.enableAllProjectMcpServers).toBe(true);
});
test("should handle whitespace-only input", async () => {
await setupClaudeCodeSettings(" \n\t ", testHomeDir);
const settingsContent = await readFile(settingsPath, "utf-8");
const settings = JSON.parse(settingsContent);
expect(settings.enableAllProjectMcpServers).toBe(true);
});
test("should merge with existing settings", async () => {
// First, create some existing settings
await setupClaudeCodeSettings(
JSON.stringify({ existingKey: "existingValue" }),
testHomeDir,
);
// Then, add new settings
const newSettings = JSON.stringify({
newKey: "newValue",
model: "claude-opus-4-20250514",
});
await setupClaudeCodeSettings(newSettings, testHomeDir);
const settingsContent = await readFile(settingsPath, "utf-8");
const settings = JSON.parse(settingsContent);
expect(settings.enableAllProjectMcpServers).toBe(true);
expect(settings.existingKey).toBe("existingValue");
expect(settings.newKey).toBe("newValue");
expect(settings.model).toBe("claude-opus-4-20250514");
});
});

View File

@@ -0,0 +1,214 @@
#!/usr/bin/env bun
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
import { validateEnvironmentVariables } from "../src/validate-env";
describe("validateEnvironmentVariables", () => {
let originalEnv: NodeJS.ProcessEnv;
beforeEach(() => {
// Save the original environment
originalEnv = { ...process.env };
// Clear relevant environment variables
delete process.env.ANTHROPIC_API_KEY;
delete process.env.CLAUDE_CODE_USE_BEDROCK;
delete process.env.CLAUDE_CODE_USE_VERTEX;
delete process.env.AWS_REGION;
delete process.env.AWS_ACCESS_KEY_ID;
delete process.env.AWS_SECRET_ACCESS_KEY;
delete process.env.AWS_SESSION_TOKEN;
delete process.env.ANTHROPIC_BEDROCK_BASE_URL;
delete process.env.ANTHROPIC_VERTEX_PROJECT_ID;
delete process.env.CLOUD_ML_REGION;
delete process.env.GOOGLE_APPLICATION_CREDENTIALS;
delete process.env.ANTHROPIC_VERTEX_BASE_URL;
});
afterEach(() => {
// Restore the original environment
process.env = originalEnv;
});
describe("Direct Anthropic API", () => {
test("should pass when ANTHROPIC_API_KEY is provided", () => {
process.env.ANTHROPIC_API_KEY = "test-api-key";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should fail when ANTHROPIC_API_KEY is missing", () => {
expect(() => validateEnvironmentVariables()).toThrow(
"Either ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN is required when using direct Anthropic API.",
);
});
});
describe("AWS Bedrock", () => {
test("should pass when all required Bedrock variables are provided", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_REGION = "us-east-1";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should pass with optional Bedrock variables", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_REGION = "us-east-1";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
process.env.AWS_SESSION_TOKEN = "test-session-token";
process.env.ANTHROPIC_BEDROCK_BASE_URL = "https://test.url";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should construct Bedrock base URL from AWS_REGION when ANTHROPIC_BEDROCK_BASE_URL is not provided", () => {
// This test verifies our action.yml change, which constructs:
// ANTHROPIC_BEDROCK_BASE_URL: ${{ env.ANTHROPIC_BEDROCK_BASE_URL || (env.AWS_REGION && format('https://bedrock-runtime.{0}.amazonaws.com', env.AWS_REGION)) }}
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_REGION = "us-west-2";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
// ANTHROPIC_BEDROCK_BASE_URL is intentionally not set
// The actual URL construction happens in the composite action in action.yml
// This test is a placeholder to document the behavior
expect(() => validateEnvironmentVariables()).not.toThrow();
// In the actual action, ANTHROPIC_BEDROCK_BASE_URL would be:
// https://bedrock-runtime.us-west-2.amazonaws.com
});
test("should fail when AWS_REGION is missing", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
expect(() => validateEnvironmentVariables()).toThrow(
"AWS_REGION is required when using AWS Bedrock.",
);
});
test("should fail when AWS_ACCESS_KEY_ID is missing", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_REGION = "us-east-1";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
expect(() => validateEnvironmentVariables()).toThrow(
"AWS_ACCESS_KEY_ID is required when using AWS Bedrock.",
);
});
test("should fail when AWS_SECRET_ACCESS_KEY is missing", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.AWS_REGION = "us-east-1";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
expect(() => validateEnvironmentVariables()).toThrow(
"AWS_SECRET_ACCESS_KEY is required when using AWS Bedrock.",
);
});
test("should report all missing Bedrock variables", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
expect(() => validateEnvironmentVariables()).toThrow(
/AWS_REGION is required when using AWS Bedrock.*AWS_ACCESS_KEY_ID is required when using AWS Bedrock.*AWS_SECRET_ACCESS_KEY is required when using AWS Bedrock/s,
);
});
});
describe("Google Vertex AI", () => {
test("should pass when all required Vertex variables are provided", () => {
process.env.CLAUDE_CODE_USE_VERTEX = "1";
process.env.ANTHROPIC_VERTEX_PROJECT_ID = "test-project";
process.env.CLOUD_ML_REGION = "us-central1";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should pass with optional Vertex variables", () => {
process.env.CLAUDE_CODE_USE_VERTEX = "1";
process.env.ANTHROPIC_VERTEX_PROJECT_ID = "test-project";
process.env.CLOUD_ML_REGION = "us-central1";
process.env.GOOGLE_APPLICATION_CREDENTIALS = "/path/to/creds.json";
process.env.ANTHROPIC_VERTEX_BASE_URL = "https://test.url";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should fail when ANTHROPIC_VERTEX_PROJECT_ID is missing", () => {
process.env.CLAUDE_CODE_USE_VERTEX = "1";
process.env.CLOUD_ML_REGION = "us-central1";
expect(() => validateEnvironmentVariables()).toThrow(
"ANTHROPIC_VERTEX_PROJECT_ID is required when using Google Vertex AI.",
);
});
test("should fail when CLOUD_ML_REGION is missing", () => {
process.env.CLAUDE_CODE_USE_VERTEX = "1";
process.env.ANTHROPIC_VERTEX_PROJECT_ID = "test-project";
expect(() => validateEnvironmentVariables()).toThrow(
"CLOUD_ML_REGION is required when using Google Vertex AI.",
);
});
test("should report all missing Vertex variables", () => {
process.env.CLAUDE_CODE_USE_VERTEX = "1";
expect(() => validateEnvironmentVariables()).toThrow(
/ANTHROPIC_VERTEX_PROJECT_ID is required when using Google Vertex AI.*CLOUD_ML_REGION is required when using Google Vertex AI/s,
);
});
});
describe("Multiple providers", () => {
test("should fail when both Bedrock and Vertex are enabled", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
process.env.CLAUDE_CODE_USE_VERTEX = "1";
// Provide all required vars to isolate the mutual exclusion error
process.env.AWS_REGION = "us-east-1";
process.env.AWS_ACCESS_KEY_ID = "test-access-key";
process.env.AWS_SECRET_ACCESS_KEY = "test-secret-key";
process.env.ANTHROPIC_VERTEX_PROJECT_ID = "test-project";
process.env.CLOUD_ML_REGION = "us-central1";
expect(() => validateEnvironmentVariables()).toThrow(
"Cannot use both Bedrock and Vertex AI simultaneously. Please set only one provider.",
);
});
});
describe("Error message formatting", () => {
test("should format error message properly with multiple errors", () => {
process.env.CLAUDE_CODE_USE_BEDROCK = "1";
// Missing all required Bedrock vars
let error: Error | undefined;
try {
validateEnvironmentVariables();
} catch (e) {
error = e as Error;
}
expect(error).toBeDefined();
expect(error!.message).toMatch(
/^Environment variable validation failed:/,
);
expect(error!.message).toContain(
" - AWS_REGION is required when using AWS Bedrock.",
);
expect(error!.message).toContain(
" - AWS_ACCESS_KEY_ID is required when using AWS Bedrock.",
);
expect(error!.message).toContain(
" - AWS_SECRET_ACCESS_KEY is required when using AWS Bedrock.",
);
});
});
});