Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c8b03a427 | |||
| 92225d30c5 | |||
| 3630ba6618 | |||
| 6a03bb8e1b | |||
| 3139f541e4 | |||
| c5b9c7cfc4 | |||
| 7a14716481 |
@@ -34,7 +34,7 @@ jobs:
|
|||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install --no-frozen-lockfile
|
||||||
|
|
||||||
- name: Get package version
|
- name: Get package version
|
||||||
id: version
|
id: version
|
||||||
@@ -95,24 +95,14 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ steps.version.outputs.tag }}
|
tag_name: ${{ steps.version.outputs.tag }}
|
||||||
release_name: "@pullfrog/action ${{ steps.version.outputs.tag }}"
|
release_name: "${{ steps.version.outputs.tag }}"
|
||||||
body: |
|
body: |
|
||||||
## 📦 @pullfrog/action ${{ steps.version.outputs.version }}
|
## 📦 @pullfrog/action ${{ steps.version.outputs.version }}
|
||||||
|
|
||||||
### Usage in GitHub Actions
|
### Usage in GitHub Actions
|
||||||
|
|
||||||
Use the major version tag for automatic updates:
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: pullfrog/action@${{ steps.version.outputs.major_tag }}
|
- uses: pullfrog/action@${{ steps.version.outputs.major_tag }}
|
||||||
with:
|
|
||||||
message: "Your message here"
|
|
||||||
```
|
|
||||||
|
|
||||||
Or pin to this specific version:
|
|
||||||
```yaml
|
|
||||||
- uses: pullfrog/action@${{ steps.version.outputs.tag }}
|
|
||||||
with:
|
|
||||||
message: "Your message here"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Installation via npm
|
### Installation via npm
|
||||||
@@ -123,11 +113,11 @@ jobs:
|
|||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
- name: Publish to npm
|
# - name: Publish to npm
|
||||||
if: steps.check_tag.outputs.exists == 'false'
|
# if: steps.check_tag.outputs.exists == 'false'
|
||||||
run: npm publish --access public
|
# run: npm publish --access public
|
||||||
env:
|
# env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
- name: Summary
|
- name: Summary
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ echo "🔨 Building action..."
|
|||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
# Add the built files to the commit
|
# Add the built files to the commit
|
||||||
git add index.cjs entry.cjs
|
git add entry.cjs
|
||||||
|
|||||||
@@ -55,6 +55,14 @@ pnpm dev # Watch mode
|
|||||||
|
|
||||||
The action is bundled into `entry.cjs` with all dependencies included, eliminating runtime dependency on node_modules.
|
The action is bundled into `entry.cjs` with all dependencies included, eliminating runtime dependency on node_modules.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Create `.env` in `/action`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Claude API key
|
||||||
|
```
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
- **entry.cjs**: Bundled action entry point (self-contained)
|
- **entry.cjs**: Bundled action entry point (self-contained)
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ inputs:
|
|||||||
anthropic_api_key:
|
anthropic_api_key:
|
||||||
description: "Anthropic API key for Claude Code authentication"
|
description: "Anthropic API key for Claude Code authentication"
|
||||||
required: false
|
required: false
|
||||||
|
github_token:
|
||||||
|
description: "GitHub token for repository access"
|
||||||
|
required: false
|
||||||
|
github_installation_token:
|
||||||
|
description: "GitHub App installation token"
|
||||||
|
required: false
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "node20"
|
using: "node20"
|
||||||
|
|||||||
+4
-1
@@ -59,7 +59,10 @@ export class ClaudeAgent implements Agent {
|
|||||||
],
|
],
|
||||||
env: { ANTHROPIC_API_KEY: this.apiKey },
|
env: { ANTHROPIC_API_KEY: this.apiKey },
|
||||||
timeout: 120000, // 2 minute timeout
|
timeout: 120000, // 2 minute timeout
|
||||||
onStdout: (chunk) => process.stdout.write(chunk),
|
onStdout: (chunk) => {
|
||||||
|
// no logs
|
||||||
|
// process.stdout.write(chunk)
|
||||||
|
},
|
||||||
onStderr: (chunk) => process.stderr.write(chunk),
|
onStderr: (chunk) => process.stderr.write(chunk),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13047,7 +13047,7 @@ var require_fetch = __commonJS({
|
|||||||
this.emit("terminated", error2);
|
this.emit("terminated", error2);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
function fetch(input, init = {}) {
|
function fetch2(input, init = {}) {
|
||||||
webidl.argumentLengthCheck(arguments, 1, { header: "globalThis.fetch" });
|
webidl.argumentLengthCheck(arguments, 1, { header: "globalThis.fetch" });
|
||||||
const p = createDeferredPromise();
|
const p = createDeferredPromise();
|
||||||
let requestObject;
|
let requestObject;
|
||||||
@@ -13977,7 +13977,7 @@ var require_fetch = __commonJS({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
module2.exports = {
|
module2.exports = {
|
||||||
fetch,
|
fetch: fetch2,
|
||||||
Fetch,
|
Fetch,
|
||||||
fetching,
|
fetching,
|
||||||
finalizeAndReportTiming
|
finalizeAndReportTiming
|
||||||
@@ -17233,7 +17233,7 @@ var require_undici = __commonJS({
|
|||||||
module2.exports.getGlobalDispatcher = getGlobalDispatcher;
|
module2.exports.getGlobalDispatcher = getGlobalDispatcher;
|
||||||
if (util.nodeMajor > 16 || util.nodeMajor === 16 && util.nodeMinor >= 8) {
|
if (util.nodeMajor > 16 || util.nodeMajor === 16 && util.nodeMinor >= 8) {
|
||||||
let fetchImpl = null;
|
let fetchImpl = null;
|
||||||
module2.exports.fetch = async function fetch(resource) {
|
module2.exports.fetch = async function fetch2(resource) {
|
||||||
if (!fetchImpl) {
|
if (!fetchImpl) {
|
||||||
fetchImpl = require_fetch().fetch;
|
fetchImpl = require_fetch().fetch;
|
||||||
}
|
}
|
||||||
@@ -17582,12 +17582,12 @@ var require_lib = __commonJS({
|
|||||||
throw new Error("Client has already been disposed.");
|
throw new Error("Client has already been disposed.");
|
||||||
}
|
}
|
||||||
const parsedUrl = new URL(requestUrl);
|
const parsedUrl = new URL(requestUrl);
|
||||||
let info3 = this._prepareRequest(verb, parsedUrl, headers);
|
let info4 = this._prepareRequest(verb, parsedUrl, headers);
|
||||||
const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) ? this._maxRetries + 1 : 1;
|
const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) ? this._maxRetries + 1 : 1;
|
||||||
let numTries = 0;
|
let numTries = 0;
|
||||||
let response;
|
let response;
|
||||||
do {
|
do {
|
||||||
response = yield this.requestRaw(info3, data);
|
response = yield this.requestRaw(info4, data);
|
||||||
if (response && response.message && response.message.statusCode === HttpCodes.Unauthorized) {
|
if (response && response.message && response.message.statusCode === HttpCodes.Unauthorized) {
|
||||||
let authenticationHandler;
|
let authenticationHandler;
|
||||||
for (const handler of this.handlers) {
|
for (const handler of this.handlers) {
|
||||||
@@ -17597,7 +17597,7 @@ var require_lib = __commonJS({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (authenticationHandler) {
|
if (authenticationHandler) {
|
||||||
return authenticationHandler.handleAuthentication(this, info3, data);
|
return authenticationHandler.handleAuthentication(this, info4, data);
|
||||||
} else {
|
} else {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -17620,8 +17620,8 @@ var require_lib = __commonJS({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info3 = this._prepareRequest(verb, parsedRedirectUrl, headers);
|
info4 = this._prepareRequest(verb, parsedRedirectUrl, headers);
|
||||||
response = yield this.requestRaw(info3, data);
|
response = yield this.requestRaw(info4, data);
|
||||||
redirectsRemaining--;
|
redirectsRemaining--;
|
||||||
}
|
}
|
||||||
if (!response.message.statusCode || !HttpResponseRetryCodes.includes(response.message.statusCode)) {
|
if (!response.message.statusCode || !HttpResponseRetryCodes.includes(response.message.statusCode)) {
|
||||||
@@ -17650,7 +17650,7 @@ var require_lib = __commonJS({
|
|||||||
* @param info
|
* @param info
|
||||||
* @param data
|
* @param data
|
||||||
*/
|
*/
|
||||||
requestRaw(info3, data) {
|
requestRaw(info4, data) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
function callbackForResult(err, res) {
|
function callbackForResult(err, res) {
|
||||||
@@ -17662,7 +17662,7 @@ var require_lib = __commonJS({
|
|||||||
resolve(res);
|
resolve(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.requestRawWithCallback(info3, data, callbackForResult);
|
this.requestRawWithCallback(info4, data, callbackForResult);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -17672,12 +17672,12 @@ var require_lib = __commonJS({
|
|||||||
* @param data
|
* @param data
|
||||||
* @param onResult
|
* @param onResult
|
||||||
*/
|
*/
|
||||||
requestRawWithCallback(info3, data, onResult) {
|
requestRawWithCallback(info4, data, onResult) {
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
if (!info3.options.headers) {
|
if (!info4.options.headers) {
|
||||||
info3.options.headers = {};
|
info4.options.headers = {};
|
||||||
}
|
}
|
||||||
info3.options.headers["Content-Length"] = Buffer.byteLength(data, "utf8");
|
info4.options.headers["Content-Length"] = Buffer.byteLength(data, "utf8");
|
||||||
}
|
}
|
||||||
let callbackCalled = false;
|
let callbackCalled = false;
|
||||||
function handleResult(err, res) {
|
function handleResult(err, res) {
|
||||||
@@ -17686,7 +17686,7 @@ var require_lib = __commonJS({
|
|||||||
onResult(err, res);
|
onResult(err, res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const req = info3.httpModule.request(info3.options, (msg) => {
|
const req = info4.httpModule.request(info4.options, (msg) => {
|
||||||
const res = new HttpClientResponse(msg);
|
const res = new HttpClientResponse(msg);
|
||||||
handleResult(void 0, res);
|
handleResult(void 0, res);
|
||||||
});
|
});
|
||||||
@@ -17698,7 +17698,7 @@ var require_lib = __commonJS({
|
|||||||
if (socket) {
|
if (socket) {
|
||||||
socket.end();
|
socket.end();
|
||||||
}
|
}
|
||||||
handleResult(new Error(`Request timeout: ${info3.options.path}`));
|
handleResult(new Error(`Request timeout: ${info4.options.path}`));
|
||||||
});
|
});
|
||||||
req.on("error", function(err) {
|
req.on("error", function(err) {
|
||||||
handleResult(err);
|
handleResult(err);
|
||||||
@@ -17734,27 +17734,27 @@ var require_lib = __commonJS({
|
|||||||
return this._getProxyAgentDispatcher(parsedUrl, proxyUrl);
|
return this._getProxyAgentDispatcher(parsedUrl, proxyUrl);
|
||||||
}
|
}
|
||||||
_prepareRequest(method, requestUrl, headers) {
|
_prepareRequest(method, requestUrl, headers) {
|
||||||
const info3 = {};
|
const info4 = {};
|
||||||
info3.parsedUrl = requestUrl;
|
info4.parsedUrl = requestUrl;
|
||||||
const usingSsl = info3.parsedUrl.protocol === "https:";
|
const usingSsl = info4.parsedUrl.protocol === "https:";
|
||||||
info3.httpModule = usingSsl ? https : http;
|
info4.httpModule = usingSsl ? https : http;
|
||||||
const defaultPort = usingSsl ? 443 : 80;
|
const defaultPort = usingSsl ? 443 : 80;
|
||||||
info3.options = {};
|
info4.options = {};
|
||||||
info3.options.host = info3.parsedUrl.hostname;
|
info4.options.host = info4.parsedUrl.hostname;
|
||||||
info3.options.port = info3.parsedUrl.port ? parseInt(info3.parsedUrl.port) : defaultPort;
|
info4.options.port = info4.parsedUrl.port ? parseInt(info4.parsedUrl.port) : defaultPort;
|
||||||
info3.options.path = (info3.parsedUrl.pathname || "") + (info3.parsedUrl.search || "");
|
info4.options.path = (info4.parsedUrl.pathname || "") + (info4.parsedUrl.search || "");
|
||||||
info3.options.method = method;
|
info4.options.method = method;
|
||||||
info3.options.headers = this._mergeHeaders(headers);
|
info4.options.headers = this._mergeHeaders(headers);
|
||||||
if (this.userAgent != null) {
|
if (this.userAgent != null) {
|
||||||
info3.options.headers["user-agent"] = this.userAgent;
|
info4.options.headers["user-agent"] = this.userAgent;
|
||||||
}
|
}
|
||||||
info3.options.agent = this._getAgent(info3.parsedUrl);
|
info4.options.agent = this._getAgent(info4.parsedUrl);
|
||||||
if (this.handlers) {
|
if (this.handlers) {
|
||||||
for (const handler of this.handlers) {
|
for (const handler of this.handlers) {
|
||||||
handler.prepareRequest(info3.options);
|
handler.prepareRequest(info4.options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return info3;
|
return info4;
|
||||||
}
|
}
|
||||||
_mergeHeaders(headers) {
|
_mergeHeaders(headers) {
|
||||||
if (this.requestOptions && this.requestOptions.headers) {
|
if (this.requestOptions && this.requestOptions.headers) {
|
||||||
@@ -19675,7 +19675,7 @@ var require_core = __commonJS({
|
|||||||
process.env["PATH"] = `${inputPath}${path.delimiter}${process.env["PATH"]}`;
|
process.env["PATH"] = `${inputPath}${path.delimiter}${process.env["PATH"]}`;
|
||||||
}
|
}
|
||||||
exports2.addPath = addPath;
|
exports2.addPath = addPath;
|
||||||
function getInput2(name, options) {
|
function getInput3(name, options) {
|
||||||
const val = process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || "";
|
const val = process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || "";
|
||||||
if (options && options.required && !val) {
|
if (options && options.required && !val) {
|
||||||
throw new Error(`Input required and not supplied: ${name}`);
|
throw new Error(`Input required and not supplied: ${name}`);
|
||||||
@@ -19685,9 +19685,9 @@ var require_core = __commonJS({
|
|||||||
}
|
}
|
||||||
return val.trim();
|
return val.trim();
|
||||||
}
|
}
|
||||||
exports2.getInput = getInput2;
|
exports2.getInput = getInput3;
|
||||||
function getMultilineInput(name, options) {
|
function getMultilineInput(name, options) {
|
||||||
const inputs = getInput2(name, options).split("\n").filter((x) => x !== "");
|
const inputs = getInput3(name, options).split("\n").filter((x) => x !== "");
|
||||||
if (options && options.trimWhitespace === false) {
|
if (options && options.trimWhitespace === false) {
|
||||||
return inputs;
|
return inputs;
|
||||||
}
|
}
|
||||||
@@ -19697,7 +19697,7 @@ var require_core = __commonJS({
|
|||||||
function getBooleanInput(name, options) {
|
function getBooleanInput(name, options) {
|
||||||
const trueValue = ["true", "True", "TRUE"];
|
const trueValue = ["true", "True", "TRUE"];
|
||||||
const falseValue = ["false", "False", "FALSE"];
|
const falseValue = ["false", "False", "FALSE"];
|
||||||
const val = getInput2(name, options);
|
const val = getInput3(name, options);
|
||||||
if (trueValue.includes(val))
|
if (trueValue.includes(val))
|
||||||
return true;
|
return true;
|
||||||
if (falseValue.includes(val))
|
if (falseValue.includes(val))
|
||||||
@@ -19706,7 +19706,7 @@ var require_core = __commonJS({
|
|||||||
Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
|
Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
|
||||||
}
|
}
|
||||||
exports2.getBooleanInput = getBooleanInput;
|
exports2.getBooleanInput = getBooleanInput;
|
||||||
function setOutput2(name, value) {
|
function setOutput(name, value) {
|
||||||
const filePath = process.env["GITHUB_OUTPUT"] || "";
|
const filePath = process.env["GITHUB_OUTPUT"] || "";
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
return (0, file_command_1.issueFileCommand)("OUTPUT", (0, file_command_1.prepareKeyValueMessage)(name, value));
|
return (0, file_command_1.issueFileCommand)("OUTPUT", (0, file_command_1.prepareKeyValueMessage)(name, value));
|
||||||
@@ -19714,7 +19714,7 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
|
|||||||
process.stdout.write(os.EOL);
|
process.stdout.write(os.EOL);
|
||||||
(0, command_1.issueCommand)("set-output", { name }, (0, utils_1.toCommandValue)(value));
|
(0, command_1.issueCommand)("set-output", { name }, (0, utils_1.toCommandValue)(value));
|
||||||
}
|
}
|
||||||
exports2.setOutput = setOutput2;
|
exports2.setOutput = setOutput;
|
||||||
function setCommandEcho(enabled) {
|
function setCommandEcho(enabled) {
|
||||||
(0, command_1.issue)("echo", enabled ? "on" : "off");
|
(0, command_1.issue)("echo", enabled ? "on" : "off");
|
||||||
}
|
}
|
||||||
@@ -19744,10 +19744,10 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
|
|||||||
(0, command_1.issueCommand)("notice", (0, utils_1.toCommandProperties)(properties), message instanceof Error ? message.toString() : message);
|
(0, command_1.issueCommand)("notice", (0, utils_1.toCommandProperties)(properties), message instanceof Error ? message.toString() : message);
|
||||||
}
|
}
|
||||||
exports2.notice = notice;
|
exports2.notice = notice;
|
||||||
function info3(message) {
|
function info4(message) {
|
||||||
process.stdout.write(message + os.EOL);
|
process.stdout.write(message + os.EOL);
|
||||||
}
|
}
|
||||||
exports2.info = info3;
|
exports2.info = info4;
|
||||||
function startGroup2(name) {
|
function startGroup2(name) {
|
||||||
(0, command_1.issue)("group", name);
|
(0, command_1.issue)("group", name);
|
||||||
}
|
}
|
||||||
@@ -19781,12 +19781,12 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
|
|||||||
return process.env[`STATE_${name}`] || "";
|
return process.env[`STATE_${name}`] || "";
|
||||||
}
|
}
|
||||||
exports2.getState = getState;
|
exports2.getState = getState;
|
||||||
function getIDToken(aud) {
|
function getIDToken2(aud) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
return yield oidc_utils_1.OidcClient.getIDToken(aud);
|
return yield oidc_utils_1.OidcClient.getIDToken(aud);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
exports2.getIDToken = getIDToken;
|
exports2.getIDToken = getIDToken2;
|
||||||
var summary_1 = require_summary();
|
var summary_1 = require_summary();
|
||||||
Object.defineProperty(exports2, "summary", { enumerable: true, get: function() {
|
Object.defineProperty(exports2, "summary", { enumerable: true, get: function() {
|
||||||
return summary_1.summary;
|
return summary_1.summary;
|
||||||
@@ -25494,14 +25494,55 @@ var require_src = __commonJS({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// entry.ts
|
// entry.ts
|
||||||
var core3 = __toESM(require_core(), 1);
|
var core4 = __toESM(require_core(), 1);
|
||||||
|
|
||||||
// main.ts
|
// main.ts
|
||||||
var core2 = __toESM(require_core(), 1);
|
var core3 = __toESM(require_core(), 1);
|
||||||
|
|
||||||
// agents/claude.ts
|
// agents/claude.ts
|
||||||
var import_promises = require("node:fs/promises");
|
var import_promises = require("node:fs/promises");
|
||||||
|
var core2 = __toESM(require_core(), 1);
|
||||||
|
|
||||||
|
// utils/github.ts
|
||||||
var core = __toESM(require_core(), 1);
|
var core = __toESM(require_core(), 1);
|
||||||
|
async function setupGitHubInstallationToken() {
|
||||||
|
const inputToken = core.getInput("github_installation_token");
|
||||||
|
const envToken = process.env.GITHUB_INSTALLATION_TOKEN;
|
||||||
|
const existingToken = inputToken || envToken;
|
||||||
|
if (existingToken) {
|
||||||
|
core.info("Using provided GitHub installation token");
|
||||||
|
return existingToken;
|
||||||
|
}
|
||||||
|
core.info("No cached installation token found, generating OIDC token...");
|
||||||
|
try {
|
||||||
|
const oidcToken = await core.getIDToken("pullfrog-api");
|
||||||
|
core.info("OIDC token generated successfully");
|
||||||
|
const apiUrl = process.env.API_URL || "https://pullfrog.ai";
|
||||||
|
const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${oidcToken}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!tokenResponse.ok) {
|
||||||
|
const errorText = await tokenResponse.text();
|
||||||
|
throw new Error(
|
||||||
|
`Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText} - ${errorText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const tokenData = await tokenResponse.json();
|
||||||
|
core.info(
|
||||||
|
`Installation token obtained for ${tokenData.owner}/${tokenData.repository || "all repositories"}`
|
||||||
|
);
|
||||||
|
process.env.GITHUB_INSTALLATION_TOKEN = tokenData.token;
|
||||||
|
return tokenData.token;
|
||||||
|
} catch (error2) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to setup GitHub installation token: ${error2 instanceof Error ? error2.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// utils/subprocess.ts
|
// utils/subprocess.ts
|
||||||
var import_node_child_process = require("node:child_process");
|
var import_node_child_process = require("node:child_process");
|
||||||
@@ -25700,10 +25741,10 @@ var ClaudeAgent = class {
|
|||||||
*/
|
*/
|
||||||
async install() {
|
async install() {
|
||||||
if (await this.isClaudeInstalled()) {
|
if (await this.isClaudeInstalled()) {
|
||||||
core.info("Claude Code is already installed, skipping installation");
|
core2.info("Claude Code is already installed, skipping installation");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
core.info("Installing Claude Code...");
|
core2.info("Installing Claude Code...");
|
||||||
try {
|
try {
|
||||||
const result = await spawn({
|
const result = await spawn({
|
||||||
cmd: "bash",
|
cmd: "bash",
|
||||||
@@ -25714,7 +25755,8 @@ var ClaudeAgent = class {
|
|||||||
env: { ANTHROPIC_API_KEY: this.apiKey },
|
env: { ANTHROPIC_API_KEY: this.apiKey },
|
||||||
timeout: 12e4,
|
timeout: 12e4,
|
||||||
// 2 minute timeout
|
// 2 minute timeout
|
||||||
onStdout: (chunk) => process.stdout.write(chunk),
|
onStdout: (chunk) => {
|
||||||
|
},
|
||||||
onStderr: (chunk) => process.stderr.write(chunk)
|
onStderr: (chunk) => process.stderr.write(chunk)
|
||||||
});
|
});
|
||||||
if (result.exitCode !== 0) {
|
if (result.exitCode !== 0) {
|
||||||
@@ -25722,7 +25764,7 @@ var ClaudeAgent = class {
|
|||||||
`Installation failed with exit code ${result.exitCode}: ${result.stderr}`
|
`Installation failed with exit code ${result.exitCode}: ${result.stderr}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
core.info("Claude Code installed successfully");
|
core2.info("Claude Code installed successfully");
|
||||||
} catch (error2) {
|
} catch (error2) {
|
||||||
throw new Error(`Failed to install Claude Code: ${error2}`);
|
throw new Error(`Failed to install Claude Code: ${error2}`);
|
||||||
}
|
}
|
||||||
@@ -25731,7 +25773,7 @@ var ClaudeAgent = class {
|
|||||||
* Execute Claude Code with the given prompt
|
* Execute Claude Code with the given prompt
|
||||||
*/
|
*/
|
||||||
async execute(prompt) {
|
async execute(prompt) {
|
||||||
core.info("Running Claude Code...");
|
core2.info("Running Claude Code...");
|
||||||
try {
|
try {
|
||||||
const claudePath = `${process.env.HOME}/.local/bin/claude`;
|
const claudePath = `${process.env.HOME}/.local/bin/claude`;
|
||||||
console.log(boxString(prompt, { title: "Prompt" }));
|
console.log(boxString(prompt, { title: "Prompt" }));
|
||||||
@@ -25746,7 +25788,7 @@ var ClaudeAgent = class {
|
|||||||
const env = {
|
const env = {
|
||||||
ANTHROPIC_API_KEY: this.apiKey
|
ANTHROPIC_API_KEY: this.apiKey
|
||||||
};
|
};
|
||||||
core.startGroup("\u{1F504} Run details");
|
core2.startGroup("\u{1F504} Run details");
|
||||||
this.runStats = {
|
this.runStats = {
|
||||||
toolsUsed: 0,
|
toolsUsed: 0,
|
||||||
turns: 0,
|
turns: 0,
|
||||||
@@ -25780,11 +25822,11 @@ Stderr: ${result.stderr}`
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const duration = Date.now() - this.runStats.startTime;
|
const duration = Date.now() - this.runStats.startTime;
|
||||||
core.info(
|
core2.info(
|
||||||
`\u{1F4CA} Run Summary: ${this.runStats.toolsUsed} tools used, ${this.runStats.turns} turns, ${duration}ms duration`
|
`\u{1F4CA} Run Summary: ${this.runStats.toolsUsed} tools used, ${this.runStats.turns} turns, ${duration}ms duration`
|
||||||
);
|
);
|
||||||
core.info("\u2705 Task complete.");
|
core2.info("\u2705 Task complete.");
|
||||||
core.endGroup();
|
core2.endGroup();
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
output: finalResult,
|
output: finalResult,
|
||||||
@@ -25797,7 +25839,7 @@ Stderr: ${result.stderr}`
|
|||||||
};
|
};
|
||||||
} catch (error2) {
|
} catch (error2) {
|
||||||
try {
|
try {
|
||||||
core.endGroup();
|
core2.endGroup();
|
||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
const errorMessage = error2 instanceof Error ? error2.message : "Unknown error";
|
const errorMessage = error2 instanceof Error ? error2.message : "Unknown error";
|
||||||
@@ -25815,8 +25857,8 @@ function processJSONChunk(chunk, agent) {
|
|||||||
switch (parsedChunk.type) {
|
switch (parsedChunk.type) {
|
||||||
case "system":
|
case "system":
|
||||||
if (parsedChunk.subtype === "init") {
|
if (parsedChunk.subtype === "init") {
|
||||||
core.info(`\u{1F680} Starting Claude Code session...`);
|
core2.info(`\u{1F680} Starting Claude Code session...`);
|
||||||
core.info(
|
core2.info(
|
||||||
tableString([
|
tableString([
|
||||||
["model", parsedChunk.model],
|
["model", parsedChunk.model],
|
||||||
["cwd", parsedChunk.cwd],
|
["cwd", parsedChunk.cwd],
|
||||||
@@ -25845,7 +25887,7 @@ function processJSONChunk(chunk, agent) {
|
|||||||
for (const content of parsedChunk.message.content) {
|
for (const content of parsedChunk.message.content) {
|
||||||
if (content.type === "text") {
|
if (content.type === "text") {
|
||||||
if (content.text.trim()) {
|
if (content.text.trim()) {
|
||||||
core.info(
|
core2.info(
|
||||||
boxString(content.text.trim(), { title: "Claude Code" })
|
boxString(content.text.trim(), { title: "Claude Code" })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -25854,44 +25896,44 @@ function processJSONChunk(chunk, agent) {
|
|||||||
agent.runStats.toolsUsed++;
|
agent.runStats.toolsUsed++;
|
||||||
}
|
}
|
||||||
const toolName = content.name;
|
const toolName = content.name;
|
||||||
core.info(`\u2192 ${toolName}`);
|
core2.info(`\u2192 ${toolName}`);
|
||||||
if (content.input) {
|
if (content.input) {
|
||||||
const input = content.input;
|
const input = content.input;
|
||||||
if (input.description) {
|
if (input.description) {
|
||||||
core.info(` \u2514\u2500 ${input.description}`);
|
core2.info(` \u2514\u2500 ${input.description}`);
|
||||||
}
|
}
|
||||||
if (input.command) {
|
if (input.command) {
|
||||||
core.info(` \u2514\u2500 command: ${input.command}`);
|
core2.info(` \u2514\u2500 command: ${input.command}`);
|
||||||
}
|
}
|
||||||
if (input.file_path) {
|
if (input.file_path) {
|
||||||
core.info(` \u2514\u2500 file: ${input.file_path}`);
|
core2.info(` \u2514\u2500 file: ${input.file_path}`);
|
||||||
}
|
}
|
||||||
if (input.content) {
|
if (input.content) {
|
||||||
const contentPreview = input.content.length > 100 ? `${input.content.substring(0, 100)}...` : input.content;
|
const contentPreview = input.content.length > 100 ? `${input.content.substring(0, 100)}...` : input.content;
|
||||||
core.info(` \u2514\u2500 content: ${contentPreview}`);
|
core2.info(` \u2514\u2500 content: ${contentPreview}`);
|
||||||
}
|
}
|
||||||
if (input.query) {
|
if (input.query) {
|
||||||
core.info(` \u2514\u2500 query: ${input.query}`);
|
core2.info(` \u2514\u2500 query: ${input.query}`);
|
||||||
}
|
}
|
||||||
if (input.pattern) {
|
if (input.pattern) {
|
||||||
core.info(` \u2514\u2500 pattern: ${input.pattern}`);
|
core2.info(` \u2514\u2500 pattern: ${input.pattern}`);
|
||||||
}
|
}
|
||||||
if (input.url) {
|
if (input.url) {
|
||||||
core.info(` \u2514\u2500 url: ${input.url}`);
|
core2.info(` \u2514\u2500 url: ${input.url}`);
|
||||||
}
|
}
|
||||||
if (input.edits && Array.isArray(input.edits)) {
|
if (input.edits && Array.isArray(input.edits)) {
|
||||||
core.info(` \u2514\u2500 edits: ${input.edits.length} changes`);
|
core2.info(` \u2514\u2500 edits: ${input.edits.length} changes`);
|
||||||
input.edits.forEach((edit, index) => {
|
input.edits.forEach((edit, index) => {
|
||||||
if (edit.file_path) {
|
if (edit.file_path) {
|
||||||
core.info(` ${index + 1}. ${edit.file_path}`);
|
core2.info(` ${index + 1}. ${edit.file_path}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (input.task) {
|
if (input.task) {
|
||||||
core.info(` \u2514\u2500 task: ${input.task}`);
|
core2.info(` \u2514\u2500 task: ${input.task}`);
|
||||||
}
|
}
|
||||||
if (input.bash_command) {
|
if (input.bash_command) {
|
||||||
core.info(` \u2514\u2500 bash_command: ${input.bash_command}`);
|
core2.info(` \u2514\u2500 bash_command: ${input.bash_command}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25903,7 +25945,7 @@ function processJSONChunk(chunk, agent) {
|
|||||||
for (const content of parsedChunk.message.content) {
|
for (const content of parsedChunk.message.content) {
|
||||||
if (content.type === "tool_result") {
|
if (content.type === "tool_result") {
|
||||||
if (content.is_error) {
|
if (content.is_error) {
|
||||||
core.warning(`\u274C Tool error: ${content.content}`);
|
core2.warning(`\u274C Tool error: ${content.content}`);
|
||||||
} else {
|
} else {
|
||||||
const _resultContent = content.content.trim();
|
const _resultContent = content.content.trim();
|
||||||
}
|
}
|
||||||
@@ -25913,7 +25955,7 @@ function processJSONChunk(chunk, agent) {
|
|||||||
break;
|
break;
|
||||||
case "result":
|
case "result":
|
||||||
if (parsedChunk.subtype === "success") {
|
if (parsedChunk.subtype === "success") {
|
||||||
core.info(
|
core2.info(
|
||||||
tableString([
|
tableString([
|
||||||
[
|
[
|
||||||
"Cost",
|
"Cost",
|
||||||
@@ -25926,30 +25968,31 @@ function processJSONChunk(chunk, agent) {
|
|||||||
])
|
])
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
core.error(`\u274C Failed: ${parsedChunk.error || "Unknown error"}`);
|
core2.error(`\u274C Failed: ${parsedChunk.error || "Unknown error"}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
core.debug(`\u{1F4E6} Unknown chunk type: ${parsedChunk.type}`);
|
core2.debug(`\u{1F4E6} Unknown chunk type: ${parsedChunk.type}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (error2) {
|
} catch (error2) {
|
||||||
core.debug(`Failed to parse chunk: ${error2}`);
|
core2.debug(`Failed to parse chunk: ${error2}`);
|
||||||
core.debug(`Raw chunk: ${chunk.substring(0, 200)}...`);
|
core2.debug(`Raw chunk: ${chunk.substring(0, 200)}...`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// main.ts
|
// main.ts
|
||||||
async function main(params) {
|
async function main(params) {
|
||||||
try {
|
try {
|
||||||
const anthropicApiKey = params.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
|
const { inputs, env, cwd } = params;
|
||||||
if (!anthropicApiKey) {
|
if (cwd !== process.cwd()) {
|
||||||
throw new Error("anthropic_api_key is required");
|
process.chdir(cwd);
|
||||||
}
|
}
|
||||||
core2.info(`\u2192 Starting agent run with Claude Code`);
|
Object.assign(process.env, env);
|
||||||
const agent = new ClaudeAgent({ apiKey: anthropicApiKey });
|
core3.info(`\u2192 Starting agent run with Claude Code`);
|
||||||
|
const agent = new ClaudeAgent({ apiKey: inputs.anthropic_api_key });
|
||||||
await agent.install();
|
await agent.install();
|
||||||
const result = await agent.execute(params.prompt);
|
const result = await agent.execute(inputs.prompt);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -25973,30 +26016,37 @@ async function main(params) {
|
|||||||
// entry.ts
|
// entry.ts
|
||||||
async function run() {
|
async function run() {
|
||||||
try {
|
try {
|
||||||
const prompt = core3.getInput("prompt", { required: true });
|
const prompt = core4.getInput("prompt", { required: true });
|
||||||
const anthropicApiKey = core3.getInput("anthropic_api_key", {
|
const anthropicApiKey = core4.getInput("anthropic_api_key");
|
||||||
required: true
|
|
||||||
});
|
|
||||||
if (!anthropicApiKey) {
|
|
||||||
throw new Error("anthropic_api_key is required");
|
|
||||||
}
|
|
||||||
if (!prompt) {
|
if (!prompt) {
|
||||||
throw new Error("prompt is required");
|
throw new Error("prompt is required");
|
||||||
}
|
}
|
||||||
const params = {
|
const inputs = {
|
||||||
prompt,
|
prompt,
|
||||||
anthropicApiKey
|
anthropic_api_key: anthropicApiKey
|
||||||
|
};
|
||||||
|
const githubToken = core4.getInput("github_token") || process.env.GITHUB_TOKEN;
|
||||||
|
if (githubToken) {
|
||||||
|
inputs.github_token = githubToken;
|
||||||
|
}
|
||||||
|
const githubInstallationToken = core4.getInput("github_installation_token") || process.env.GITHUB_INSTALLATION_TOKEN;
|
||||||
|
if (githubInstallationToken) {
|
||||||
|
inputs.github_installation_token = githubInstallationToken;
|
||||||
|
} else {
|
||||||
|
await setupGitHubInstallationToken();
|
||||||
|
}
|
||||||
|
const params = {
|
||||||
|
inputs,
|
||||||
|
env: {},
|
||||||
|
cwd: process.cwd()
|
||||||
};
|
};
|
||||||
const result = await main(params);
|
const result = await main(params);
|
||||||
core3.setOutput("status", result.success ? "success" : "failed");
|
|
||||||
core3.setOutput("prompt", prompt);
|
|
||||||
core3.setOutput("output", result.output || "");
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || "Agent execution failed");
|
throw new Error(result.error || "Agent execution failed");
|
||||||
}
|
}
|
||||||
} catch (error2) {
|
} catch (error2) {
|
||||||
const errorMessage = error2 instanceof Error ? error2.message : "Unknown error occurred";
|
const errorMessage = error2 instanceof Error ? error2.message : "Unknown error occurred";
|
||||||
core3.setFailed(`Action failed: ${errorMessage}`);
|
core4.setFailed(`Action failed: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
run().catch((error2) => {
|
run().catch((error2) => {
|
||||||
|
|||||||
@@ -7,43 +7,55 @@
|
|||||||
|
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import { main } from "./main";
|
import { main } from "./main";
|
||||||
|
import { setupGitHubInstallationToken } from "./utils";
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
async function run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Get inputs from GitHub Actions
|
// Get inputs from GitHub Actions
|
||||||
const prompt = core.getInput("prompt", { required: true });
|
const prompt = core.getInput("prompt", { required: true });
|
||||||
const anthropicApiKey = core.getInput("anthropic_api_key", {
|
const anthropicApiKey = core.getInput("anthropic_api_key");
|
||||||
required: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!anthropicApiKey) {
|
|
||||||
throw new Error("anthropic_api_key is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!prompt) {
|
if (!prompt) {
|
||||||
throw new Error("prompt is required");
|
throw new Error("prompt is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create params object
|
// Create params object with new structure
|
||||||
const params = {
|
const inputs: any = {
|
||||||
prompt,
|
prompt,
|
||||||
anthropicApiKey,
|
anthropic_api_key: anthropicApiKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add optional properties only if they exist
|
||||||
|
const githubToken = core.getInput("github_token") || process.env.GITHUB_TOKEN;
|
||||||
|
if (githubToken) {
|
||||||
|
inputs.github_token = githubToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
const githubInstallationToken =
|
||||||
|
core.getInput("github_installation_token") || process.env.GITHUB_INSTALLATION_TOKEN;
|
||||||
|
if (githubInstallationToken) {
|
||||||
|
inputs.github_installation_token = githubInstallationToken;
|
||||||
|
} else {
|
||||||
|
// Setup GitHub installation token
|
||||||
|
await setupGitHubInstallationToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
inputs,
|
||||||
|
env: {} as Record<string, string>,
|
||||||
|
cwd: process.cwd(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Run the main function
|
// Run the main function
|
||||||
const result = await main(params);
|
const result = await main(params);
|
||||||
|
|
||||||
// Set outputs
|
// TODO: Set outputs
|
||||||
core.setOutput("status", result.success ? "success" : "failed");
|
|
||||||
core.setOutput("prompt", prompt);
|
|
||||||
core.setOutput("output", result.output || "");
|
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || "Agent execution failed");
|
throw new Error(result.error || "Agent execution failed");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage =
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
||||||
error instanceof Error ? error.message : "Unknown error occurred";
|
|
||||||
core.setFailed(`Action failed: ${errorMessage}`);
|
core.setFailed(`Action failed: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -1,7 +1,13 @@
|
|||||||
import type { MainParams } from "../main";
|
import type { MainParams } from "../main";
|
||||||
|
|
||||||
const testParams = {
|
const testParams = {
|
||||||
prompt: "List all files in the current directory, then create a file called dynamic-test.txt with the content 'This was loaded from a TypeScript file!', then delete it."
|
inputs: {
|
||||||
|
prompt:
|
||||||
|
"List all files in the current directory, then create a file called dynamic-test.txt with the content 'This was loaded from a TypeScript file!', then delete it.",
|
||||||
|
anthropic_api_key: "sk-test-key",
|
||||||
|
},
|
||||||
|
env: {},
|
||||||
|
cwd: process.cwd(),
|
||||||
} satisfies MainParams;
|
} satisfies MainParams;
|
||||||
|
|
||||||
export default testParams;
|
export default testParams;
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
* This exports the main function for programmatic usage
|
* This exports the main function for programmatic usage
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { main } from "./main";
|
|
||||||
export { ClaudeAgent } from "./agents";
|
export { ClaudeAgent } from "./agents";
|
||||||
export type { Agent, AgentConfig, AgentResult } from "./agents/types";
|
export type { Agent, AgentConfig, AgentResult } from "./agents/types";
|
||||||
|
export {
|
||||||
|
type ExecutionInputs,
|
||||||
|
type MainParams,
|
||||||
|
type MainResult,
|
||||||
|
main,
|
||||||
|
} from "./main";
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import { ClaudeAgent } from "./agents";
|
import { ClaudeAgent } from "./agents";
|
||||||
|
|
||||||
export interface MainParams {
|
// Expected environment variables that should be passed as inputs
|
||||||
|
export const EXPECTED_INPUTS: string[] = [
|
||||||
|
"ANTHROPIC_API_KEY",
|
||||||
|
"GITHUB_TOKEN",
|
||||||
|
"GITHUB_INSTALLATION_TOKEN"
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface ExecutionInputs {
|
||||||
prompt: string;
|
prompt: string;
|
||||||
anthropicApiKey?: string;
|
anthropic_api_key: string;
|
||||||
|
github_token?: string;
|
||||||
|
github_installation_token?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MainParams {
|
||||||
|
inputs: ExecutionInputs;
|
||||||
|
env: Record<string, string>;
|
||||||
|
cwd: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MainResult {
|
export interface MainResult {
|
||||||
@@ -12,24 +27,28 @@ export interface MainResult {
|
|||||||
error?: string | undefined;
|
error?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function main(params: MainParams): Promise<MainResult> {
|
export async function main(params: MainParams): Promise<MainResult> {
|
||||||
try {
|
try {
|
||||||
// Use provided API key or fall back to environment variable
|
// Extract inputs from params
|
||||||
const anthropicApiKey =
|
const { inputs, env, cwd } = params;
|
||||||
params.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
|
|
||||||
|
|
||||||
if (!anthropicApiKey) {
|
// Set working directory if different from current
|
||||||
throw new Error("anthropic_api_key is required");
|
if (cwd !== process.cwd()) {
|
||||||
|
process.chdir(cwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set environment variables
|
||||||
|
Object.assign(process.env, env);
|
||||||
|
|
||||||
core.info(`→ Starting agent run with Claude Code`);
|
core.info(`→ Starting agent run with Claude Code`);
|
||||||
|
|
||||||
// Create and install the Claude agent
|
// Create and install the Claude agent
|
||||||
const agent = new ClaudeAgent({ apiKey: anthropicApiKey });
|
const agent = new ClaudeAgent({ apiKey: inputs.anthropic_api_key });
|
||||||
await agent.install();
|
await agent.install();
|
||||||
|
|
||||||
// Execute the agent with the prompt
|
// Execute the agent with the prompt
|
||||||
const result = await agent.execute(params.prompt);
|
const result = await agent.execute(inputs.prompt);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return {
|
return {
|
||||||
@@ -44,8 +63,7 @@ export async function main(params: MainParams): Promise<MainResult> {
|
|||||||
output: result.output || "",
|
output: result.output || "",
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage =
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
||||||
error instanceof Error ? error.message : "Unknown error occurred";
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
|
|||||||
+5
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@pullfrog/action",
|
"name": "@pullfrog/action",
|
||||||
"version": "0.0.7",
|
"version": "0.0.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
@@ -17,21 +17,22 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
"build": "node esbuild.config.js",
|
"build": "node esbuild.config.js",
|
||||||
"build:npm": "zshy",
|
"build:npm": "zshy",
|
||||||
"build:dev": "node esbuild.config.js",
|
"build:dev": "node esbuild.config.js",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"play": "tsx --env-file=../.env play.ts"
|
"play": "tsx play.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.11.1",
|
||||||
|
"dotenv": "^17.2.2",
|
||||||
"execa": "^9.6.0",
|
"execa": "^9.6.0",
|
||||||
"table": "^6.9.0"
|
"table": "^6.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.10.0",
|
"@types/node": "^20.10.0",
|
||||||
"commander": "^14.0.0",
|
"commander": "^14.0.0",
|
||||||
"dotenv": "^17.2.2",
|
|
||||||
"esbuild": "^0.25.9",
|
"esbuild": "^0.25.9",
|
||||||
"husky": "^9.0.0",
|
"husky": "^9.0.0",
|
||||||
"typescript": "^5.3.0",
|
"typescript": "^5.3.0",
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
#!/usr/bin/env tsx
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
|
import { dirname, extname, join, resolve } from "node:path";
|
||||||
import { execSync } from "node:child_process";
|
|
||||||
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
||||||
import { dirname, join, resolve, extname } from "node:path";
|
|
||||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
|
import { config } from "dotenv";
|
||||||
import { main } from "./main";
|
import { main } from "./main";
|
||||||
import { runAct } from "./utils/act";
|
import { runAct } from "./utils/act";
|
||||||
|
import { setupTestRepo } from "./utils/setup";
|
||||||
|
|
||||||
|
// Load environment variables from .env file
|
||||||
|
config();
|
||||||
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
@@ -46,9 +49,7 @@ async function loadPrompt(filePath: string): Promise<string> {
|
|||||||
const module = await import(fileUrl);
|
const module = await import(fileUrl);
|
||||||
|
|
||||||
if (!module.default) {
|
if (!module.default) {
|
||||||
throw new Error(
|
throw new Error(`TypeScript file ${filePath} must have a default export`);
|
||||||
`TypeScript file ${filePath} must have a default export`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a string, use it directly
|
// If it's a string, use it directly
|
||||||
@@ -66,16 +67,11 @@ async function loadPrompt(filePath: string): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(`Unsupported file type: ${ext}. Supported types: .txt, .json, .ts`);
|
||||||
`Unsupported file type: ${ext}. Supported types: .txt, .json, .ts`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runPlay(
|
async function runPlay(filePath: string, options: { act?: boolean }): Promise<void> {
|
||||||
filePath: string,
|
|
||||||
options: { act?: boolean },
|
|
||||||
): Promise<void> {
|
|
||||||
try {
|
try {
|
||||||
// Load the prompt from the specified file
|
// Load the prompt from the specified file
|
||||||
const prompt = await loadPrompt(filePath);
|
const prompt = await loadPrompt(filePath);
|
||||||
@@ -85,56 +81,9 @@ async function runPlay(
|
|||||||
console.log("🐳 Running with Docker/act...");
|
console.log("🐳 Running with Docker/act...");
|
||||||
runAct(prompt);
|
runAct(prompt);
|
||||||
} else {
|
} else {
|
||||||
// Clone the test repository and run directly
|
// Setup test repository and run directly
|
||||||
const tempDir = join(process.cwd(), ".temp");
|
const tempDir = join(process.cwd(), ".temp");
|
||||||
const repoUrl = "git@github.com:pullfrogai/scratch.git";
|
setupTestRepo({ tempDir, forceClean: true });
|
||||||
|
|
||||||
// Remove existing temp directory if it exists
|
|
||||||
if (existsSync(tempDir)) {
|
|
||||||
console.log("🗑️ Removing existing .temp directory...");
|
|
||||||
rmSync(tempDir, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone the repository
|
|
||||||
console.log("📦 Cloning pullfrogai/scratch into .temp...");
|
|
||||||
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
|
||||||
|
|
||||||
// List of environment variables to copy to .temp
|
|
||||||
const envVarsToCopy = [
|
|
||||||
"ANTHROPIC_API_KEY",
|
|
||||||
"GITHUB_TOKEN",
|
|
||||||
// Add more environment variables here as needed
|
|
||||||
];
|
|
||||||
|
|
||||||
// Build .env content from the list
|
|
||||||
const envLines = envVarsToCopy
|
|
||||||
.map((varName) => `${varName}=${process.env[varName] || ""}`)
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
const envPath = join(tempDir, ".env");
|
|
||||||
writeFileSync(envPath, envLines + "\n");
|
|
||||||
console.log("📝 Created .env file in .temp directory with:");
|
|
||||||
|
|
||||||
let hasRequiredVars = true;
|
|
||||||
envVarsToCopy.forEach((varName) => {
|
|
||||||
const hasValue = !!process.env[varName];
|
|
||||||
console.log(` - ${varName}: ${hasValue ? "✓" : "✗ (missing)"}`);
|
|
||||||
|
|
||||||
// Check for required variables
|
|
||||||
if (varName === "ANTHROPIC_API_KEY" && !hasValue) {
|
|
||||||
hasRequiredVars = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!hasRequiredVars) {
|
|
||||||
console.warn("\n⚠️ Warning: ANTHROPIC_API_KEY is not set or empty.");
|
|
||||||
console.warn(
|
|
||||||
" Please ensure you have a valid API key in your .env file.",
|
|
||||||
);
|
|
||||||
console.warn(
|
|
||||||
" Get your API key from: https://console.anthropic.com/api-keys\n",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change to the temp directory
|
// Change to the temp directory
|
||||||
process.chdir(tempDir);
|
process.chdir(tempDir);
|
||||||
@@ -145,8 +94,35 @@ async function runPlay(
|
|||||||
console.log(prompt);
|
console.log(prompt);
|
||||||
console.log("─".repeat(50));
|
console.log("─".repeat(50));
|
||||||
|
|
||||||
// Run main with the params object
|
// Set environment variables from our .env for the action to use
|
||||||
const result = await main({ prompt });
|
const { EXPECTED_INPUTS } = await import("./main");
|
||||||
|
EXPECTED_INPUTS.forEach((inputName) => {
|
||||||
|
const value = process.env[inputName];
|
||||||
|
if (value) {
|
||||||
|
process.env[`INPUT_${inputName.toLowerCase()}`] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run main with the new params structure
|
||||||
|
const inputs: any = {
|
||||||
|
prompt,
|
||||||
|
anthropic_api_key: process.env.ANTHROPIC_API_KEY || "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add optional properties only if they exist
|
||||||
|
if (process.env.GITHUB_TOKEN) {
|
||||||
|
inputs.github_token = process.env.GITHUB_TOKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.GITHUB_INSTALLATION_TOKEN) {
|
||||||
|
inputs.github_installation_token = process.env.GITHUB_INSTALLATION_TOKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await main({
|
||||||
|
inputs,
|
||||||
|
env: process.env as Record<string, string>,
|
||||||
|
cwd: process.cwd(),
|
||||||
|
});
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log("✅ Test completed successfully");
|
console.log("✅ Test completed successfully");
|
||||||
@@ -171,15 +147,8 @@ program
|
|||||||
.name("play")
|
.name("play")
|
||||||
.description("Test the Pullfrog action with various prompts")
|
.description("Test the Pullfrog action with various prompts")
|
||||||
.version("1.0.0")
|
.version("1.0.0")
|
||||||
.argument(
|
.argument("[file]", "Prompt file to use (.txt, .json, or .ts)", "fixtures/basic.txt")
|
||||||
"[file]",
|
.option("--act", "Use Docker/act to run the action instead of running directly")
|
||||||
"Prompt file to use (.txt, .json, or .ts)",
|
|
||||||
"fixtures/basic.txt",
|
|
||||||
)
|
|
||||||
.option(
|
|
||||||
"--act",
|
|
||||||
"Use Docker/act to run the action instead of running directly",
|
|
||||||
)
|
|
||||||
.action(async (file: string, options: { act?: boolean }) => {
|
.action(async (file: string, options: { act?: boolean }) => {
|
||||||
await runPlay(file, options);
|
await runPlay(file, options);
|
||||||
});
|
});
|
||||||
|
|||||||
Generated
+4
-4
@@ -9,8 +9,11 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@actions/core':
|
'@actions/core':
|
||||||
specifier: ^1.10.1
|
specifier: ^1.11.1
|
||||||
version: 1.11.1
|
version: 1.11.1
|
||||||
|
dotenv:
|
||||||
|
specifier: ^17.2.2
|
||||||
|
version: 17.2.2
|
||||||
execa:
|
execa:
|
||||||
specifier: ^9.6.0
|
specifier: ^9.6.0
|
||||||
version: 9.6.0
|
version: 9.6.0
|
||||||
@@ -24,9 +27,6 @@ importers:
|
|||||||
commander:
|
commander:
|
||||||
specifier: ^14.0.0
|
specifier: ^14.0.0
|
||||||
version: 14.0.0
|
version: 14.0.0
|
||||||
dotenv:
|
|
||||||
specifier: ^17.2.2
|
|
||||||
version: 17.2.2
|
|
||||||
esbuild:
|
esbuild:
|
||||||
specifier: ^0.25.9
|
specifier: ^0.25.9
|
||||||
version: 0.25.9
|
version: 0.25.9
|
||||||
|
|||||||
+22
-41
@@ -1,59 +1,36 @@
|
|||||||
import { execSync } from "node:child_process";
|
import { execSync } from "node:child_process";
|
||||||
import { existsSync, readFileSync } from "node:fs";
|
import { existsSync } from "node:fs";
|
||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { config, parse } from "dotenv";
|
import { config } from "dotenv";
|
||||||
|
import { buildAction, setupTestRepo } from "./setup";
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
// Environment variables that should be passed as secrets to the workflow
|
||||||
|
const ENV_VARS = ["ANTHROPIC_API_KEY", "GITHUB_INSTALLATION_TOKEN"];
|
||||||
|
|
||||||
export function runAct(prompt: string): void {
|
export function runAct(prompt: string): void {
|
||||||
// First, ensure the scratch repo is cloned
|
|
||||||
const tempDir = join(__dirname, "..", ".temp");
|
const tempDir = join(__dirname, "..", ".temp");
|
||||||
|
const actionPath = join(__dirname, "..");
|
||||||
// Check if .temp exists and either reset it or clone it
|
|
||||||
if (existsSync(tempDir)) {
|
|
||||||
console.log("📦 Resetting existing .temp repository...");
|
|
||||||
execSync("git reset --hard HEAD && git clean -fd", {
|
|
||||||
cwd: tempDir,
|
|
||||||
stdio: "inherit",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log("📦 Cloning pullfrogai/scratch into .temp...");
|
|
||||||
const repoUrl = "git@github.com:pullfrogai/scratch.git";
|
|
||||||
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflowPath = join(tempDir, ".github", "workflows", "pullfrog.yml");
|
|
||||||
const envPath = join(__dirname, "..", "..", ".env");
|
const envPath = join(__dirname, "..", "..", ".env");
|
||||||
|
|
||||||
// Load environment variables into process
|
// Setup test repository
|
||||||
|
setupTestRepo({ tempDir });
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
config({ path: envPath });
|
config({ path: envPath });
|
||||||
|
|
||||||
// Parse environment variables from .env file to get keys
|
// Build action bundles
|
||||||
let envVars: string[] = [];
|
buildAction(actionPath);
|
||||||
try {
|
|
||||||
const content = readFileSync(envPath, "utf8");
|
|
||||||
const parsed = parse(content);
|
|
||||||
envVars = Object.keys(parsed);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(
|
|
||||||
`Warning: Could not read .env file: ${(error as Error).message}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build fresh bundles with esbuild
|
const workflowPath = join(tempDir, ".github", "workflows", "pullfrog.yml");
|
||||||
const actionPath = join(__dirname, "..");
|
|
||||||
console.log("🔨 Building fresh bundles with esbuild...");
|
|
||||||
execSync("node esbuild.config.js", {
|
|
||||||
cwd: actionPath,
|
|
||||||
stdio: "inherit",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create minimal dist for act (avoids pnpm symlink issues)
|
// Create minimal dist for act (avoids pnpm symlink issues)
|
||||||
const distPath = join(actionPath, ".act-dist");
|
const distPath = join(actionPath, ".act-dist");
|
||||||
console.log("📦 Creating minimal distribution for act...");
|
console.log("📦 Creating minimal distribution for act...");
|
||||||
execSync(`rm -rf "${distPath}" && mkdir -p "${distPath}"`, { shell: true });
|
execSync(`rm -rf "${distPath}" && mkdir -p "${distPath}"`, { shell: "/bin/bash" });
|
||||||
|
|
||||||
// Copy only necessary files (bundled, no node_modules needed)
|
// Copy only necessary files (bundled, no node_modules needed)
|
||||||
["action.yml", "entry.cjs", "index.cjs", "package.json"].forEach((file) => {
|
["action.yml", "entry.cjs", "index.cjs", "package.json"].forEach((file) => {
|
||||||
@@ -79,11 +56,15 @@ export function runAct(prompt: string): void {
|
|||||||
`pullfrog/action@v0=${distPath}`, // Use minimal dist without symlinks
|
`pullfrog/action@v0=${distPath}`, // Use minimal dist without symlinks
|
||||||
];
|
];
|
||||||
|
|
||||||
// Add all environment variables as secrets (without values)
|
// Add environment variables as secrets that will be available to the workflow
|
||||||
envVars.forEach((key) => {
|
ENV_VARS.forEach((key) => {
|
||||||
actCommandParts.push("-s", key);
|
if (process.env[key]) {
|
||||||
|
actCommandParts.push("-s", key);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// We only need the specific ENV_VARS, no need to add other variables
|
||||||
|
|
||||||
const actCommand = actCommandParts.join(" ");
|
const actCommand = actCommandParts.join(" ");
|
||||||
|
|
||||||
console.log("🚀 Running act with prompt:");
|
console.log("🚀 Running act with prompt:");
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import * as core from "@actions/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup GitHub installation token for the action
|
||||||
|
*/
|
||||||
|
export async function setupGitHubInstallationToken(): Promise<string> {
|
||||||
|
// Check if we have an installation token from inputs or environment
|
||||||
|
const inputToken = core.getInput("github_installation_token");
|
||||||
|
const envToken = process.env.GITHUB_INSTALLATION_TOKEN;
|
||||||
|
|
||||||
|
const existingToken = inputToken || envToken;
|
||||||
|
if (existingToken) {
|
||||||
|
core.info("Using provided GitHub installation token");
|
||||||
|
return existingToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info("No cached installation token found, generating OIDC token...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Generate OIDC token for our API
|
||||||
|
const oidcToken = await core.getIDToken("pullfrog-api");
|
||||||
|
core.info("OIDC token generated successfully");
|
||||||
|
|
||||||
|
// Exchange OIDC token for installation token
|
||||||
|
const apiUrl = process.env.API_URL || "https://pullfrog.ai";
|
||||||
|
|
||||||
|
const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${oidcToken}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tokenResponse.ok) {
|
||||||
|
const errorText = await tokenResponse.text();
|
||||||
|
throw new Error(
|
||||||
|
`Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText} - ${errorText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenData = await tokenResponse.json();
|
||||||
|
core.info(
|
||||||
|
`Installation token obtained for ${tokenData.owner}/${tokenData.repository || "all repositories"}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set the token as an environment variable for this run
|
||||||
|
process.env.GITHUB_INSTALLATION_TOKEN = tokenData.token;
|
||||||
|
|
||||||
|
return tokenData.token;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to setup GitHub installation token: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
export * from "./files";
|
export * from "./files";
|
||||||
|
export * from "./github";
|
||||||
|
export * from "./setup";
|
||||||
export * from "./subprocess";
|
export * from "./subprocess";
|
||||||
export * from "./table";
|
export * from "./table";
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { execSync } from "node:child_process";
|
||||||
|
import { existsSync, rmSync } from "node:fs";
|
||||||
|
|
||||||
|
export interface SetupOptions {
|
||||||
|
tempDir: string;
|
||||||
|
repoUrl?: string;
|
||||||
|
forceClean?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup the test repository for running actions
|
||||||
|
*/
|
||||||
|
export function setupTestRepo(options: SetupOptions): void {
|
||||||
|
const {
|
||||||
|
tempDir,
|
||||||
|
repoUrl = "git@github.com:pullfrogai/scratch.git",
|
||||||
|
forceClean = false,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// Handle existing temp directory
|
||||||
|
if (existsSync(tempDir)) {
|
||||||
|
if (forceClean) {
|
||||||
|
console.log("🗑️ Removing existing .temp directory...");
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
|
||||||
|
// Clone the repository
|
||||||
|
console.log("📦 Cloning pullfrogai/scratch into .temp...");
|
||||||
|
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
||||||
|
} else {
|
||||||
|
console.log("📦 Resetting existing .temp repository...");
|
||||||
|
execSync("git reset --hard HEAD && git clean -fd", {
|
||||||
|
cwd: tempDir,
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("📦 Cloning pullfrogai/scratch into .temp...");
|
||||||
|
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the action bundles
|
||||||
|
*/
|
||||||
|
export function buildAction(actionPath: string): void {
|
||||||
|
console.log("🔨 Building fresh bundles with esbuild...");
|
||||||
|
execSync("node esbuild.config.js", {
|
||||||
|
cwd: actionPath,
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user