mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 06:54:13 +08:00
* 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>
298 lines
10 KiB
TypeScript
298 lines
10 KiB
TypeScript
#!/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({});
|
|
});
|
|
});
|
|
});
|