@dependabit/action 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +225 -0
- package/action.yml +85 -0
- package/dist/actions/check.d.ts +33 -0
- package/dist/actions/check.d.ts.map +1 -0
- package/dist/actions/check.js +162 -0
- package/dist/actions/check.js.map +1 -0
- package/dist/actions/generate.d.ts +9 -0
- package/dist/actions/generate.d.ts.map +1 -0
- package/dist/actions/generate.js +152 -0
- package/dist/actions/generate.js.map +1 -0
- package/dist/actions/update.d.ts +9 -0
- package/dist/actions/update.d.ts.map +1 -0
- package/dist/actions/update.js +246 -0
- package/dist/actions/update.js.map +1 -0
- package/dist/actions/validate.d.ts +33 -0
- package/dist/actions/validate.d.ts.map +1 -0
- package/dist/actions/validate.js +226 -0
- package/dist/actions/validate.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +114 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +154 -0
- package/dist/logger.js.map +1 -0
- package/dist/utils/agent-config.d.ts +31 -0
- package/dist/utils/agent-config.d.ts.map +1 -0
- package/dist/utils/agent-config.js +42 -0
- package/dist/utils/agent-config.js.map +1 -0
- package/dist/utils/agent-router.d.ts +33 -0
- package/dist/utils/agent-router.d.ts.map +1 -0
- package/dist/utils/agent-router.js +57 -0
- package/dist/utils/agent-router.js.map +1 -0
- package/dist/utils/errors.d.ts +51 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +219 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/inputs.d.ts +35 -0
- package/dist/utils/inputs.d.ts.map +1 -0
- package/dist/utils/inputs.js +47 -0
- package/dist/utils/inputs.js.map +1 -0
- package/dist/utils/metrics.d.ts +66 -0
- package/dist/utils/metrics.d.ts.map +1 -0
- package/dist/utils/metrics.js +116 -0
- package/dist/utils/metrics.js.map +1 -0
- package/dist/utils/outputs.d.ts +43 -0
- package/dist/utils/outputs.d.ts.map +1 -0
- package/dist/utils/outputs.js +146 -0
- package/dist/utils/outputs.js.map +1 -0
- package/dist/utils/performance.d.ts +100 -0
- package/dist/utils/performance.d.ts.map +1 -0
- package/dist/utils/performance.js +185 -0
- package/dist/utils/performance.js.map +1 -0
- package/dist/utils/reporter.d.ts +43 -0
- package/dist/utils/reporter.d.ts.map +1 -0
- package/dist/utils/reporter.js +122 -0
- package/dist/utils/reporter.js.map +1 -0
- package/dist/utils/secrets.d.ts +45 -0
- package/dist/utils/secrets.d.ts.map +1 -0
- package/dist/utils/secrets.js +94 -0
- package/dist/utils/secrets.js.map +1 -0
- package/package.json +45 -0
- package/src/actions/check.ts +223 -0
- package/src/actions/generate.ts +181 -0
- package/src/actions/update.ts +284 -0
- package/src/actions/validate.ts +292 -0
- package/src/index.ts +43 -0
- package/src/logger.test.ts +200 -0
- package/src/logger.ts +210 -0
- package/src/utils/agent-config.ts +61 -0
- package/src/utils/agent-router.ts +67 -0
- package/src/utils/errors.ts +251 -0
- package/src/utils/inputs.ts +75 -0
- package/src/utils/metrics.ts +169 -0
- package/src/utils/outputs.ts +202 -0
- package/src/utils/performance.ts +248 -0
- package/src/utils/reporter.ts +169 -0
- package/src/utils/secrets.ts +124 -0
- package/test/actions/check.test.ts +216 -0
- package/test/actions/generate.test.ts +82 -0
- package/test/actions/update.test.ts +70 -0
- package/test/actions/validate.test.ts +257 -0
- package/test/utils/agent-config.test.ts +112 -0
- package/test/utils/agent-router.test.ts +129 -0
- package/test/utils/metrics.test.ts +221 -0
- package/test/utils/reporter.test.ts +196 -0
- package/test/utils/secrets.test.ts +217 -0
- package/tsconfig.json +15 -0
- package/tsconfig.tsbuildinfo +1 -0
package/src/logger.ts
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import * as core from '@actions/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Log level enumeration
|
|
5
|
+
*/
|
|
6
|
+
export enum LogLevel {
|
|
7
|
+
DEBUG = 'debug',
|
|
8
|
+
INFO = 'info',
|
|
9
|
+
WARNING = 'warning',
|
|
10
|
+
ERROR = 'error'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Structured log entry
|
|
15
|
+
*/
|
|
16
|
+
export interface LogEntry {
|
|
17
|
+
timestamp: string;
|
|
18
|
+
level: LogLevel;
|
|
19
|
+
message: string;
|
|
20
|
+
correlationId?: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Logger configuration
|
|
26
|
+
*/
|
|
27
|
+
export interface LoggerConfig {
|
|
28
|
+
correlationId?: string;
|
|
29
|
+
enableDebug?: boolean;
|
|
30
|
+
context?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Structured JSON logger for GitHub Actions
|
|
35
|
+
*/
|
|
36
|
+
export class Logger {
|
|
37
|
+
private correlationId: string;
|
|
38
|
+
private enableDebug: boolean;
|
|
39
|
+
private context: Record<string, unknown>;
|
|
40
|
+
|
|
41
|
+
constructor(config: LoggerConfig = {}) {
|
|
42
|
+
this.correlationId = config.correlationId || this.generateCorrelationId();
|
|
43
|
+
this.enableDebug = config.enableDebug ?? false;
|
|
44
|
+
this.context = config.context || {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Generate a unique correlation ID for operation tracing
|
|
49
|
+
*/
|
|
50
|
+
private generateCorrelationId(): string {
|
|
51
|
+
return `${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Format log entry as JSON
|
|
56
|
+
*/
|
|
57
|
+
private formatEntry(level: LogLevel, message: string, data?: Record<string, unknown>): string {
|
|
58
|
+
const entry: LogEntry = {
|
|
59
|
+
timestamp: new Date().toISOString(),
|
|
60
|
+
level,
|
|
61
|
+
message,
|
|
62
|
+
correlationId: this.correlationId,
|
|
63
|
+
...this.context,
|
|
64
|
+
...data
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return JSON.stringify(entry);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Log debug message
|
|
72
|
+
*/
|
|
73
|
+
debug(message: string, data?: Record<string, unknown>): void {
|
|
74
|
+
if (this.enableDebug) {
|
|
75
|
+
core.debug(this.formatEntry(LogLevel.DEBUG, message, data));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Log info message
|
|
81
|
+
*/
|
|
82
|
+
info(message: string, data?: Record<string, unknown>): void {
|
|
83
|
+
core.info(this.formatEntry(LogLevel.INFO, message, data));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Log warning message
|
|
88
|
+
*/
|
|
89
|
+
warning(message: string, data?: Record<string, unknown>): void {
|
|
90
|
+
core.warning(this.formatEntry(LogLevel.WARNING, message, data));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Log error message
|
|
95
|
+
*/
|
|
96
|
+
error(message: string, data?: Record<string, unknown>): void {
|
|
97
|
+
core.error(this.formatEntry(LogLevel.ERROR, message, data));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Start a log group
|
|
102
|
+
*/
|
|
103
|
+
startGroup(name: string): void {
|
|
104
|
+
core.startGroup(name);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* End a log group
|
|
109
|
+
*/
|
|
110
|
+
endGroup(): void {
|
|
111
|
+
core.endGroup();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get correlation ID
|
|
116
|
+
*/
|
|
117
|
+
getCorrelationId(): string {
|
|
118
|
+
return this.correlationId;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create a child logger with the same correlation ID
|
|
123
|
+
*/
|
|
124
|
+
child(context?: Record<string, unknown>): Logger {
|
|
125
|
+
return new Logger({
|
|
126
|
+
correlationId: this.correlationId,
|
|
127
|
+
enableDebug: this.enableDebug,
|
|
128
|
+
context: { ...this.context, ...context }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Log LLM interaction
|
|
134
|
+
*/
|
|
135
|
+
logLLMInteraction(data: {
|
|
136
|
+
provider: string;
|
|
137
|
+
model?: string;
|
|
138
|
+
prompt: string;
|
|
139
|
+
response: string;
|
|
140
|
+
tokens?: number;
|
|
141
|
+
latencyMs?: number;
|
|
142
|
+
}): void {
|
|
143
|
+
this.info('LLM interaction', {
|
|
144
|
+
type: 'llm_interaction',
|
|
145
|
+
...data
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Log API call
|
|
151
|
+
*/
|
|
152
|
+
logAPICall(data: {
|
|
153
|
+
endpoint: string;
|
|
154
|
+
method: string;
|
|
155
|
+
statusCode?: number;
|
|
156
|
+
latencyMs?: number;
|
|
157
|
+
rateLimit?: {
|
|
158
|
+
remaining: number;
|
|
159
|
+
limit: number;
|
|
160
|
+
reset: number;
|
|
161
|
+
};
|
|
162
|
+
}): void {
|
|
163
|
+
this.info('API call', {
|
|
164
|
+
type: 'api_call',
|
|
165
|
+
...data
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Log operation duration
|
|
171
|
+
*/
|
|
172
|
+
logDuration(operation: string, durationMs: number, data?: Record<string, unknown>): void {
|
|
173
|
+
this.info(`Operation completed: ${operation}`, {
|
|
174
|
+
type: 'operation_duration',
|
|
175
|
+
operation,
|
|
176
|
+
durationMs,
|
|
177
|
+
...data
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Create a new logger instance
|
|
184
|
+
*/
|
|
185
|
+
export function createLogger(config?: LoggerConfig): Logger {
|
|
186
|
+
return new Logger(config);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Measure and log operation duration
|
|
191
|
+
*/
|
|
192
|
+
export async function withTiming<T>(
|
|
193
|
+
logger: Logger,
|
|
194
|
+
operation: string,
|
|
195
|
+
fn: () => Promise<T>
|
|
196
|
+
): Promise<T> {
|
|
197
|
+
const start = Date.now();
|
|
198
|
+
try {
|
|
199
|
+
const result = await fn();
|
|
200
|
+
const duration = Date.now() - start;
|
|
201
|
+
logger.logDuration(operation, duration);
|
|
202
|
+
return result;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
const duration = Date.now() - start;
|
|
205
|
+
logger.logDuration(operation, duration, {
|
|
206
|
+
error: error instanceof Error ? error.message : String(error)
|
|
207
|
+
});
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { DependabitConfig, Severity } from '@dependabit/manifest';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agent assignment configuration
|
|
5
|
+
*/
|
|
6
|
+
export interface AgentAssignmentConfig {
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
severityMapping: {
|
|
9
|
+
breaking?: string;
|
|
10
|
+
major?: string;
|
|
11
|
+
minor?: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse AI agent assignment configuration from config
|
|
17
|
+
*
|
|
18
|
+
* Extracts severity-to-agent mapping from config.yml:
|
|
19
|
+
*
|
|
20
|
+
* ```yaml
|
|
21
|
+
* issues:
|
|
22
|
+
* aiAgentAssignment:
|
|
23
|
+
* enabled: true
|
|
24
|
+
* breaking: copilot
|
|
25
|
+
* major: claude
|
|
26
|
+
* minor: copilot
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @param config Dependabit configuration
|
|
30
|
+
* @returns Agent assignment configuration
|
|
31
|
+
*/
|
|
32
|
+
export function parseAgentConfig(config: DependabitConfig): AgentAssignmentConfig {
|
|
33
|
+
const aiAgentAssignment = config.issues?.aiAgentAssignment;
|
|
34
|
+
|
|
35
|
+
if (!aiAgentAssignment || !aiAgentAssignment.enabled) {
|
|
36
|
+
return {
|
|
37
|
+
enabled: false,
|
|
38
|
+
severityMapping: {}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Normalize agent names to lowercase
|
|
43
|
+
const severityMapping: Record<string, string> = {};
|
|
44
|
+
|
|
45
|
+
if (aiAgentAssignment.breaking) {
|
|
46
|
+
severityMapping['breaking'] = aiAgentAssignment.breaking.toLowerCase();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (aiAgentAssignment.major) {
|
|
50
|
+
severityMapping['major'] = aiAgentAssignment.major.toLowerCase();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (aiAgentAssignment.minor) {
|
|
54
|
+
severityMapping['minor'] = aiAgentAssignment.minor.toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
enabled: true,
|
|
59
|
+
severityMapping
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Severity } from '@dependabit/manifest';
|
|
2
|
+
import type { AgentAssignmentConfig } from './agent-config';
|
|
3
|
+
/**
|
|
4
|
+
* Route issue to appropriate AI agent based on severity
|
|
5
|
+
*
|
|
6
|
+
* Uses severity-to-agent mapping from configuration to determine
|
|
7
|
+
* which AI agent should be assigned to handle the issue.
|
|
8
|
+
*
|
|
9
|
+
* @param severity Change severity (breaking, major, minor)
|
|
10
|
+
* @param config Agent assignment configuration
|
|
11
|
+
* @returns Agent name (e.g., 'copilot', 'claude') or null if not configured
|
|
12
|
+
*/
|
|
13
|
+
export function routeToAgent(severity: Severity, config: AgentAssignmentConfig): string | null {
|
|
14
|
+
if (!config.enabled) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const agent = config.severityMapping[severity];
|
|
19
|
+
return agent || null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generate assignee label for issue
|
|
24
|
+
*
|
|
25
|
+
* Creates a label in format "assigned:agent" that can be used
|
|
26
|
+
* to trigger agent-specific workflows or assign the issue.
|
|
27
|
+
*
|
|
28
|
+
* @param agent Agent name or null
|
|
29
|
+
* @returns Label string or null
|
|
30
|
+
*/
|
|
31
|
+
export function getAssigneeLabel(agent: string | null): string | null {
|
|
32
|
+
if (!agent) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return `assigned:${agent}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get all configured agents
|
|
41
|
+
*
|
|
42
|
+
* Returns list of unique agent names configured in the severity mapping
|
|
43
|
+
*
|
|
44
|
+
* @param config Agent assignment configuration
|
|
45
|
+
* @returns Array of agent names
|
|
46
|
+
*/
|
|
47
|
+
export function getConfiguredAgents(config: AgentAssignmentConfig): string[] {
|
|
48
|
+
if (!config.enabled) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const agents = new Set<string>();
|
|
53
|
+
|
|
54
|
+
if (config.severityMapping.breaking) {
|
|
55
|
+
agents.add(config.severityMapping.breaking);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (config.severityMapping.major) {
|
|
59
|
+
agents.add(config.severityMapping.major);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (config.severityMapping.minor) {
|
|
63
|
+
agents.add(config.severityMapping.minor);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return Array.from(agents);
|
|
67
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error categorization and remediation messages
|
|
3
|
+
* Provides actionable error messages with remediation steps
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export enum ErrorCategory {
|
|
7
|
+
AUTHENTICATION = 'authentication',
|
|
8
|
+
RATE_LIMIT = 'rate_limit',
|
|
9
|
+
NETWORK = 'network',
|
|
10
|
+
VALIDATION = 'validation',
|
|
11
|
+
NOT_FOUND = 'not_found',
|
|
12
|
+
PERMISSION = 'permission',
|
|
13
|
+
CONFIGURATION = 'configuration',
|
|
14
|
+
INTERNAL = 'internal'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CategorizedError {
|
|
18
|
+
category: ErrorCategory;
|
|
19
|
+
message: string;
|
|
20
|
+
originalError: Error;
|
|
21
|
+
remediation: string[];
|
|
22
|
+
context?: Record<string, unknown> | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Error categorizer with remediation suggestions
|
|
27
|
+
*/
|
|
28
|
+
export class ErrorCategorizer {
|
|
29
|
+
/**
|
|
30
|
+
* Categorize an error and provide remediation steps
|
|
31
|
+
*/
|
|
32
|
+
categorize(error: Error, context?: Record<string, unknown>): CategorizedError {
|
|
33
|
+
const message = error.message.toLowerCase();
|
|
34
|
+
|
|
35
|
+
// Authentication errors
|
|
36
|
+
if (
|
|
37
|
+
message.includes('authentication') ||
|
|
38
|
+
message.includes('unauthorized') ||
|
|
39
|
+
message.includes('invalid token') ||
|
|
40
|
+
message.includes('bad credentials')
|
|
41
|
+
) {
|
|
42
|
+
return {
|
|
43
|
+
category: ErrorCategory.AUTHENTICATION,
|
|
44
|
+
message: error.message,
|
|
45
|
+
originalError: error,
|
|
46
|
+
remediation: [
|
|
47
|
+
'Verify that GITHUB_TOKEN is set correctly',
|
|
48
|
+
'Check token has required permissions (repo, issues, contents)',
|
|
49
|
+
'Ensure token has not expired',
|
|
50
|
+
'For fine-grained tokens, verify repository access is granted',
|
|
51
|
+
'Try regenerating the token if issues persist'
|
|
52
|
+
],
|
|
53
|
+
context
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Rate limit errors
|
|
58
|
+
if (
|
|
59
|
+
message.includes('rate limit') ||
|
|
60
|
+
message.includes('api rate limit exceeded') ||
|
|
61
|
+
message.includes('secondary rate limit')
|
|
62
|
+
) {
|
|
63
|
+
return {
|
|
64
|
+
category: ErrorCategory.RATE_LIMIT,
|
|
65
|
+
message: error.message,
|
|
66
|
+
originalError: error,
|
|
67
|
+
remediation: [
|
|
68
|
+
'Wait for rate limit to reset (check X-RateLimit-Reset header)',
|
|
69
|
+
'Consider using authenticated requests (higher rate limits)',
|
|
70
|
+
'Implement request batching or caching',
|
|
71
|
+
'Review API usage patterns to reduce calls',
|
|
72
|
+
'For GitHub Actions, consider using GITHUB_TOKEN instead of PAT'
|
|
73
|
+
],
|
|
74
|
+
context
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Network errors
|
|
79
|
+
if (
|
|
80
|
+
message.includes('econnrefused') ||
|
|
81
|
+
message.includes('enotfound') ||
|
|
82
|
+
message.includes('timeout') ||
|
|
83
|
+
message.includes('network')
|
|
84
|
+
) {
|
|
85
|
+
return {
|
|
86
|
+
category: ErrorCategory.NETWORK,
|
|
87
|
+
message: error.message,
|
|
88
|
+
originalError: error,
|
|
89
|
+
remediation: [
|
|
90
|
+
'Check internet connectivity',
|
|
91
|
+
'Verify API endpoint is accessible',
|
|
92
|
+
'Check for firewall or proxy blocking requests',
|
|
93
|
+
'Retry the operation after a short delay',
|
|
94
|
+
'Verify DNS resolution is working'
|
|
95
|
+
],
|
|
96
|
+
context
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Not found errors
|
|
101
|
+
if (message.includes('not found') || message.includes('404')) {
|
|
102
|
+
return {
|
|
103
|
+
category: ErrorCategory.NOT_FOUND,
|
|
104
|
+
message: error.message,
|
|
105
|
+
originalError: error,
|
|
106
|
+
remediation: [
|
|
107
|
+
'Verify the repository owner and name are correct',
|
|
108
|
+
'Check if the resource exists (branch, file, issue)',
|
|
109
|
+
'Ensure you have access to the repository (private repos)',
|
|
110
|
+
'Verify the URL or path is correct',
|
|
111
|
+
'Check if the resource was recently deleted'
|
|
112
|
+
],
|
|
113
|
+
context
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Permission errors
|
|
118
|
+
if (
|
|
119
|
+
message.includes('permission') ||
|
|
120
|
+
message.includes('forbidden') ||
|
|
121
|
+
message.includes('403')
|
|
122
|
+
) {
|
|
123
|
+
return {
|
|
124
|
+
category: ErrorCategory.PERMISSION,
|
|
125
|
+
message: error.message,
|
|
126
|
+
originalError: error,
|
|
127
|
+
remediation: [
|
|
128
|
+
'Verify token has required scopes (repo, admin:org, etc.)',
|
|
129
|
+
'Check repository access permissions',
|
|
130
|
+
'For organization repos, verify org membership',
|
|
131
|
+
'Ensure not attempting to modify protected branches',
|
|
132
|
+
'Review repository settings and branch protection rules'
|
|
133
|
+
],
|
|
134
|
+
context
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Validation errors
|
|
139
|
+
if (
|
|
140
|
+
message.includes('validation') ||
|
|
141
|
+
message.includes('invalid') ||
|
|
142
|
+
message.includes('malformed')
|
|
143
|
+
) {
|
|
144
|
+
return {
|
|
145
|
+
category: ErrorCategory.VALIDATION,
|
|
146
|
+
message: error.message,
|
|
147
|
+
originalError: error,
|
|
148
|
+
remediation: [
|
|
149
|
+
'Check input data format and structure',
|
|
150
|
+
'Verify required fields are provided',
|
|
151
|
+
'Validate data types match API expectations',
|
|
152
|
+
'Review API documentation for correct format',
|
|
153
|
+
'Check for special characters that need escaping'
|
|
154
|
+
],
|
|
155
|
+
context
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Configuration errors
|
|
160
|
+
if (message.includes('config') || message.includes('missing')) {
|
|
161
|
+
return {
|
|
162
|
+
category: ErrorCategory.CONFIGURATION,
|
|
163
|
+
message: error.message,
|
|
164
|
+
originalError: error,
|
|
165
|
+
remediation: [
|
|
166
|
+
'Verify all required configuration values are set',
|
|
167
|
+
'Check .dependabit/config.yml exists and is valid',
|
|
168
|
+
'Validate configuration schema',
|
|
169
|
+
'Review example configuration in documentation',
|
|
170
|
+
'Ensure environment variables are properly set'
|
|
171
|
+
],
|
|
172
|
+
context
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Default to internal error
|
|
177
|
+
return {
|
|
178
|
+
category: ErrorCategory.INTERNAL,
|
|
179
|
+
message: error.message,
|
|
180
|
+
originalError: error,
|
|
181
|
+
remediation: [
|
|
182
|
+
'This may be an internal error or unexpected condition',
|
|
183
|
+
'Check error details for more information',
|
|
184
|
+
'Try running the operation again',
|
|
185
|
+
'If issue persists, report it with error details',
|
|
186
|
+
'Check GitHub status page for service issues'
|
|
187
|
+
],
|
|
188
|
+
context
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Format error with remediation for display
|
|
194
|
+
*/
|
|
195
|
+
format(categorizedError: CategorizedError): string {
|
|
196
|
+
const lines = [
|
|
197
|
+
`Error [${categorizedError.category}]: ${categorizedError.message}`,
|
|
198
|
+
'',
|
|
199
|
+
'Remediation steps:',
|
|
200
|
+
...categorizedError.remediation.map((step, i) => ` ${i + 1}. ${step}`)
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
if (categorizedError.context && Object.keys(categorizedError.context).length > 0) {
|
|
204
|
+
lines.push('');
|
|
205
|
+
lines.push('Context:');
|
|
206
|
+
for (const [key, value] of Object.entries(categorizedError.context)) {
|
|
207
|
+
lines.push(` ${key}: ${JSON.stringify(value)}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return lines.join('\n');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Check if error is retryable
|
|
216
|
+
*/
|
|
217
|
+
isRetryable(categorizedError: CategorizedError): boolean {
|
|
218
|
+
return [ErrorCategory.NETWORK, ErrorCategory.RATE_LIMIT].includes(categorizedError.category);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get recommended retry delay in milliseconds
|
|
223
|
+
*/
|
|
224
|
+
getRetryDelay(categorizedError: CategorizedError): number {
|
|
225
|
+
switch (categorizedError.category) {
|
|
226
|
+
case ErrorCategory.RATE_LIMIT:
|
|
227
|
+
return 60000; // 1 minute
|
|
228
|
+
case ErrorCategory.NETWORK:
|
|
229
|
+
return 5000; // 5 seconds
|
|
230
|
+
default:
|
|
231
|
+
return 0; // Not retryable
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Create a categorized error from an exception
|
|
238
|
+
*/
|
|
239
|
+
export function categorizeError(error: Error, context?: Record<string, unknown>): CategorizedError {
|
|
240
|
+
const categorizer = new ErrorCategorizer();
|
|
241
|
+
return categorizer.categorize(error, context);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Format error with remediation steps
|
|
246
|
+
*/
|
|
247
|
+
export function formatError(error: Error, context?: Record<string, unknown>): string {
|
|
248
|
+
const categorizer = new ErrorCategorizer();
|
|
249
|
+
const categorized = categorizer.categorize(error, context);
|
|
250
|
+
return categorizer.format(categorized);
|
|
251
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action Input Parsing
|
|
3
|
+
* Handles parsing and validation of GitHub Action inputs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as core from '@actions/core';
|
|
7
|
+
|
|
8
|
+
export interface GenerateActionInputs {
|
|
9
|
+
repoPath: string;
|
|
10
|
+
llmProvider: 'github-copilot' | 'claude' | 'openai';
|
|
11
|
+
llmModel?: string;
|
|
12
|
+
llmApiKey?: string;
|
|
13
|
+
manifestPath: string;
|
|
14
|
+
enableDebug: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UpdateActionInputs {
|
|
18
|
+
repoPath: string;
|
|
19
|
+
manifestPath: string;
|
|
20
|
+
commits: string[]; // Commit SHAs to analyze
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CheckActionInputs {
|
|
24
|
+
manifestPath: string;
|
|
25
|
+
createIssues: boolean;
|
|
26
|
+
issueLabels: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Parse inputs for the generate action
|
|
31
|
+
*/
|
|
32
|
+
export function parseGenerateInputs(): GenerateActionInputs {
|
|
33
|
+
const llmModelInput = core.getInput('llm_model');
|
|
34
|
+
const llmApiKeyInput =
|
|
35
|
+
core.getInput('llm_api_key') || process.env['GITHUB_TOKEN'] || process.env['OPENAI_API_KEY'];
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
repoPath: core.getInput('repo_path') || process.cwd(),
|
|
39
|
+
llmProvider: (core.getInput('llm_provider') || 'github-copilot') as 'github-copilot',
|
|
40
|
+
...(llmModelInput && { llmModel: llmModelInput }),
|
|
41
|
+
...(llmApiKeyInput && { llmApiKey: llmApiKeyInput }),
|
|
42
|
+
manifestPath: core.getInput('manifest_path') || '.dependabit/manifest.json',
|
|
43
|
+
enableDebug: core.getInput('debug') === 'true'
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse inputs for the update action
|
|
49
|
+
*/
|
|
50
|
+
export function parseUpdateInputs(): UpdateActionInputs {
|
|
51
|
+
const commitsInput = core.getInput('commits');
|
|
52
|
+
const commits = commitsInput ? commitsInput.split(',').map((c) => c.trim()) : [];
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
repoPath: core.getInput('repo_path') || process.cwd(),
|
|
56
|
+
manifestPath: core.getInput('manifest_path') || '.dependabit/manifest.json',
|
|
57
|
+
commits
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parse inputs for the check action
|
|
63
|
+
*/
|
|
64
|
+
export function parseCheckInputs(): CheckActionInputs {
|
|
65
|
+
const labelsInput = core.getInput('issue_labels');
|
|
66
|
+
const labels = labelsInput
|
|
67
|
+
? labelsInput.split(',').map((l) => l.trim())
|
|
68
|
+
: ['dependabit', 'dependency-update'];
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
manifestPath: core.getInput('manifest_path') || '.dependabit/manifest.json',
|
|
72
|
+
createIssues: core.getBooleanInput('create_issues') !== false,
|
|
73
|
+
issueLabels: labels
|
|
74
|
+
};
|
|
75
|
+
}
|