mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 06:54:13 +08:00
feat: merge prepare and run steps into unified entry point
Consolidates the two-step architecture (prepare → run) into a single step to eliminate file-based and output-based communication. Changes: - Add base-action/src/lib.ts with exports for main action import - Modify run-claude-sdk.ts to accept prompt string directly and return result - Add generatePromptContent() that returns prompt without file I/O - Update mode prepare() to return promptContent in result - Create src/entrypoints/run.ts as unified entry point - Update action.yml to use single Run Claude Code step - Update output references from steps.prepare to steps.claude-code Benefits: - No file I/O for prompt - data stays in memory - No step output parsing - direct function returns - Simpler debugging - single entry point - Faster execution - no subprocess overhead - Type safety - TypeScript across the boundary 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
13
base-action/src/lib.ts
Normal file
13
base-action/src/lib.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Library exports for the base-action.
|
||||
* These functions can be imported directly by the main action
|
||||
* to avoid file/output-based communication between steps.
|
||||
*/
|
||||
|
||||
export { runClaudeWithSdk, runClaudeWithSdkFromFile } from "./run-claude-sdk";
|
||||
export type { RunClaudeResult, PromptInput } from "./run-claude-sdk";
|
||||
export { setupClaudeCodeSettings } from "./setup-claude-code-settings";
|
||||
export { installPlugins } from "./install-plugins";
|
||||
export { parseSdkOptions } from "./parse-sdk-options";
|
||||
export type { ClaudeOptions } from "./run-claude";
|
||||
export type { ParsedSdkOptions } from "./parse-sdk-options";
|
||||
@@ -9,6 +9,18 @@ import type { ParsedSdkOptions } from "./parse-sdk-options";
|
||||
|
||||
const EXECUTION_FILE = `${process.env.RUNNER_TEMP}/claude-execution-output.json`;
|
||||
|
||||
/**
|
||||
* Result of running Claude via SDK
|
||||
*/
|
||||
export type RunClaudeResult = {
|
||||
success: boolean;
|
||||
executionFile: string;
|
||||
conclusion: "success" | "failure";
|
||||
structuredOutput?: string;
|
||||
sessionId?: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitizes SDK output to match CLI sanitization behavior
|
||||
*/
|
||||
@@ -57,13 +69,31 @@ function sanitizeSdkOutput(
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Claude using the Agent SDK
|
||||
* Input for runClaudeWithSdk - either a prompt string or file path
|
||||
*/
|
||||
export type PromptInput =
|
||||
| { type: "string"; prompt: string }
|
||||
| { type: "file"; promptPath: string };
|
||||
|
||||
/**
|
||||
* Run Claude using the Agent SDK.
|
||||
*
|
||||
* @param promptInput - Either a direct prompt string or path to prompt file
|
||||
* @param parsedOptions - Parsed SDK options
|
||||
* @param options - Additional options
|
||||
* @param options.setOutputs - Whether to set GitHub Action outputs (default: true for backwards compat)
|
||||
* @returns Result of the execution
|
||||
*/
|
||||
export async function runClaudeWithSdk(
|
||||
promptPath: string,
|
||||
promptInput: PromptInput,
|
||||
{ sdkOptions, showFullOutput, hasJsonSchema }: ParsedSdkOptions,
|
||||
): Promise<void> {
|
||||
const prompt = await readFile(promptPath, "utf-8");
|
||||
{ setOutputs = true }: { setOutputs?: boolean } = {},
|
||||
): Promise<RunClaudeResult> {
|
||||
// Get prompt from string or file
|
||||
const prompt =
|
||||
promptInput.type === "string"
|
||||
? promptInput.prompt
|
||||
: await readFile(promptInput.promptPath, "utf-8");
|
||||
|
||||
if (!showFullOutput) {
|
||||
console.log(
|
||||
@@ -74,7 +104,13 @@ export async function runClaudeWithSdk(
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`Running Claude with prompt from file: ${promptPath}`);
|
||||
if (promptInput.type === "file") {
|
||||
console.log(
|
||||
`Running Claude with prompt from file: ${promptInput.promptPath}`,
|
||||
);
|
||||
} else {
|
||||
console.log(`Running Claude with prompt string (${prompt.length} chars)`);
|
||||
}
|
||||
// Log SDK options without env (which could contain sensitive data)
|
||||
const { env, ...optionsToLog } = sdkOptions;
|
||||
console.log("SDK options:", JSON.stringify(optionsToLog, null, 2));
|
||||
@@ -97,27 +133,47 @@ export async function runClaudeWithSdk(
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("SDK execution error:", error);
|
||||
core.setOutput("conclusion", "failure");
|
||||
process.exit(1);
|
||||
if (setOutputs) {
|
||||
core.setOutput("conclusion", "failure");
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
executionFile: EXECUTION_FILE,
|
||||
conclusion: "failure",
|
||||
error: String(error),
|
||||
};
|
||||
}
|
||||
|
||||
// Write execution file
|
||||
try {
|
||||
await writeFile(EXECUTION_FILE, JSON.stringify(messages, null, 2));
|
||||
console.log(`Log saved to ${EXECUTION_FILE}`);
|
||||
core.setOutput("execution_file", EXECUTION_FILE);
|
||||
if (setOutputs) {
|
||||
core.setOutput("execution_file", EXECUTION_FILE);
|
||||
}
|
||||
} catch (error) {
|
||||
core.warning(`Failed to write execution file: ${error}`);
|
||||
}
|
||||
|
||||
if (!resultMessage) {
|
||||
core.setOutput("conclusion", "failure");
|
||||
if (setOutputs) {
|
||||
core.setOutput("conclusion", "failure");
|
||||
}
|
||||
core.error("No result message received from Claude");
|
||||
process.exit(1);
|
||||
return {
|
||||
success: false,
|
||||
executionFile: EXECUTION_FILE,
|
||||
conclusion: "failure",
|
||||
error: "No result message received from Claude",
|
||||
};
|
||||
}
|
||||
|
||||
const isSuccess = resultMessage.subtype === "success";
|
||||
core.setOutput("conclusion", isSuccess ? "success" : "failure");
|
||||
if (setOutputs) {
|
||||
core.setOutput("conclusion", isSuccess ? "success" : "failure");
|
||||
}
|
||||
|
||||
let structuredOutput: string | undefined;
|
||||
|
||||
// Handle structured output
|
||||
if (hasJsonSchema) {
|
||||
@@ -126,26 +182,64 @@ export async function runClaudeWithSdk(
|
||||
"structured_output" in resultMessage &&
|
||||
resultMessage.structured_output
|
||||
) {
|
||||
const structuredOutputJson = JSON.stringify(
|
||||
resultMessage.structured_output,
|
||||
);
|
||||
core.setOutput("structured_output", structuredOutputJson);
|
||||
structuredOutput = JSON.stringify(resultMessage.structured_output);
|
||||
if (setOutputs) {
|
||||
core.setOutput("structured_output", structuredOutput);
|
||||
}
|
||||
core.info(
|
||||
`Set structured_output with ${Object.keys(resultMessage.structured_output as object).length} field(s)`,
|
||||
);
|
||||
} else {
|
||||
core.setFailed(
|
||||
`--json-schema was provided but Claude did not return structured_output. Result subtype: ${resultMessage.subtype}`,
|
||||
);
|
||||
core.setOutput("conclusion", "failure");
|
||||
process.exit(1);
|
||||
const errorMsg = `--json-schema was provided but Claude did not return structured_output. Result subtype: ${resultMessage.subtype}`;
|
||||
if (setOutputs) {
|
||||
core.setFailed(errorMsg);
|
||||
core.setOutput("conclusion", "failure");
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
executionFile: EXECUTION_FILE,
|
||||
conclusion: "failure",
|
||||
error: errorMsg,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSuccess) {
|
||||
if ("errors" in resultMessage && resultMessage.errors) {
|
||||
core.error(`Execution failed: ${resultMessage.errors.join(", ")}`);
|
||||
}
|
||||
const errors =
|
||||
"errors" in resultMessage && resultMessage.errors
|
||||
? resultMessage.errors.join(", ")
|
||||
: "Unknown error";
|
||||
core.error(`Execution failed: ${errors}`);
|
||||
return {
|
||||
success: false,
|
||||
executionFile: EXECUTION_FILE,
|
||||
conclusion: "failure",
|
||||
error: errors,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
executionFile: EXECUTION_FILE,
|
||||
conclusion: "success",
|
||||
structuredOutput,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for backwards compatibility - reads prompt from file path and exits on failure
|
||||
*/
|
||||
export async function runClaudeWithSdkFromFile(
|
||||
promptPath: string,
|
||||
parsedOptions: ParsedSdkOptions,
|
||||
): Promise<void> {
|
||||
const result = await runClaudeWithSdk(
|
||||
{ type: "file", promptPath },
|
||||
parsedOptions,
|
||||
{ setOutputs: true },
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { unlink, writeFile, stat, readFile } from "fs/promises";
|
||||
import { createWriteStream } from "fs";
|
||||
import { spawn } from "child_process";
|
||||
import { parse as parseShellArgs } from "shell-quote";
|
||||
import { runClaudeWithSdk } from "./run-claude-sdk";
|
||||
import { runClaudeWithSdkFromFile } from "./run-claude-sdk";
|
||||
import { parseSdkOptions } from "./parse-sdk-options";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
@@ -205,7 +205,7 @@ export async function runClaude(promptPath: string, options: ClaudeOptions) {
|
||||
|
||||
if (useAgentSdk) {
|
||||
const parsedOptions = parseSdkOptions(options);
|
||||
return runClaudeWithSdk(promptPath, parsedOptions);
|
||||
return runClaudeWithSdkFromFile(promptPath, parsedOptions);
|
||||
}
|
||||
|
||||
const config = prepareRunConfig(promptPath, options);
|
||||
|
||||
Reference in New Issue
Block a user