All files / src/action inputs.ts

94.59% Statements 35/37
86.53% Branches 45/52
100% Functions 5/5
94.28% Lines 33/35

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149                                                                                              198x 198x 198x 198x     198x             36x 34x 32x                     18x             18x 18x 18x   18x 18x       18x 18x       18x 18x   18x 18x   18x                                         1x     1x 1x                 4x 4x 4x   4x 2x 2x     2x 1x 1x      
/**
 * Action Input Parsing and Validation
 *
 * Handles parsing inputs from GitHub Actions environment and validates them.
 */
 
import { SeverityThresholdSchema } from '../types/index.js';
import type { SeverityThreshold } from '../types/index.js';
import { DEFAULT_CONCURRENCY } from '../utils/index.js';
 
// -----------------------------------------------------------------------------
// Types
// -----------------------------------------------------------------------------
 
export interface ActionInputs {
  /** API key for Anthropic API (empty if using OAuth) */
  anthropicApiKey: string;
  /** OAuth token for Claude Code (empty if using API key) */
  oauthToken: string;
  githubToken: string;
  /** Optional org-wide base config that is loaded before the repo config */
  baseConfigPath?: string;
  /** Optional repo root containing org-shared local skills for the base config */
  baseSkillRoot?: string;
  /** Optional repo-local config that extends the base config in the same run */
  configPath: string;
  failOn?: SeverityThreshold;
  reportOn?: SeverityThreshold;
  maxFindings: number;
  /** Whether to use REQUEST_CHANGES review event when findings exceed failOn */
  requestChanges?: boolean;
  /** Whether to fail the check run when findings exceed failOn */
  failCheck?: boolean;
  /** Max concurrent trigger executions */
  parallel: number;
}
 
// -----------------------------------------------------------------------------
// Input Parsing
// -----------------------------------------------------------------------------
 
/**
 * Get an input value from GitHub Actions environment.
 * Checks both hyphenated (native) and underscored (composite action) formats.
 */
function getInput(name: string, required = false): string {
  // Check both hyphenated (native GitHub Actions) and underscored (composite action) formats
  const hyphenEnv = `INPUT_${name.toUpperCase()}`;
  const underscoreEnv = `INPUT_${name.toUpperCase().replace(/-/g, '_')}`;
  const value = process.env[hyphenEnv] ?? process.env[underscoreEnv] ?? '';
  Iif (required && !value) {
    throw new Error(`Input required and not supplied: ${name}`);
  }
  return value;
}
 
/**
 * Parse a string input as a boolean, returning undefined for unrecognized values.
 */
function parseBooleanInput(value: string): boolean | undefined {
  if (value === 'true') return true;
  if (value === 'false') return false;
  return undefined;
}
 
/**
 * Parse action inputs from the GitHub Actions environment.
 * Runtime-specific auth can be absent here; runtime setup validates it when needed.
 */
export function parseActionInputs(): ActionInputs {
  // Check for auth token: supports both API keys and OAuth tokens
  // Priority: input > WARDEN_ANTHROPIC_API_KEY > ANTHROPIC_API_KEY > CLAUDE_CODE_OAUTH_TOKEN
  const authToken =
    getInput('anthropic-api-key') ||
    process.env['WARDEN_ANTHROPIC_API_KEY'] ||
    process.env['ANTHROPIC_API_KEY'] ||
    process.env['CLAUDE_CODE_OAUTH_TOKEN'] ||
    '';
 
  // Detect token type: OAuth tokens start with 'sk-ant-oat', API keys are other 'sk-ant-' prefixes
  const isOAuthToken = authToken.startsWith('sk-ant-oat');
  const anthropicApiKey = isOAuthToken ? '' : authToken;
  const oauthToken = isOAuthToken ? authToken : '';
 
  const failOnInput = getInput('fail-on');
  const failOn = SeverityThresholdSchema.safeParse(failOnInput).success
    ? (failOnInput as SeverityThreshold)
    : undefined;
 
  const reportOnInput = getInput('report-on');
  const reportOn = SeverityThresholdSchema.safeParse(reportOnInput).success
    ? (reportOnInput as SeverityThreshold)
    : undefined;
 
  const maxFindingsParsed = parseInt(getInput('max-findings') || '50', 10);
  const parallelParsed = parseInt(getInput('parallel') || String(DEFAULT_CONCURRENCY), 10);
 
  const requestChanges = parseBooleanInput(getInput('request-changes'));
  const failCheck = parseBooleanInput(getInput('fail-check'));
 
  return {
    anthropicApiKey,
    oauthToken,
    githubToken: getInput('github-token') || process.env['GITHUB_TOKEN'] || '',
    baseConfigPath: getInput('base-config-path') || undefined,
    baseSkillRoot: getInput('base-skill-root') || undefined,
    configPath: getInput('config-path') || 'warden.toml',
    failOn,
    reportOn,
    maxFindings: Number.isNaN(maxFindingsParsed) ? 50 : maxFindingsParsed,
    requestChanges,
    failCheck,
    parallel: Number.isNaN(parallelParsed) ? DEFAULT_CONCURRENCY : parallelParsed,
  };
}
 
/**
 * Validate that required inputs are present.
 * Throws with a descriptive error if validation fails.
 */
export function validateInputs(inputs: ActionInputs): void {
  Iif (!inputs.githubToken) {
    throw new Error('GitHub token is required');
  }
  Eif (inputs.baseSkillRoot && !inputs.baseConfigPath) {
    throw new Error('base-skill-root requires base-config-path');
  }
}
 
/**
 * Set up environment variables for authentication.
 * Sets appropriate env vars based on token type (API key vs OAuth).
 */
export function setupAuthEnv(inputs: ActionInputs): void {
  delete process.env['CLAUDE_CODE_OAUTH_TOKEN'];
  delete process.env['WARDEN_ANTHROPIC_API_KEY'];
  delete process.env['ANTHROPIC_API_KEY'];
 
  if (inputs.oauthToken) {
    process.env['CLAUDE_CODE_OAUTH_TOKEN'] = inputs.oauthToken;
    return;
  }
 
  if (inputs.anthropicApiKey) {
    process.env['WARDEN_ANTHROPIC_API_KEY'] = inputs.anthropicApiKey;
    process.env['ANTHROPIC_API_KEY'] = inputs.anthropicApiKey;
  }
}