@northflare/runner 0.0.12 → 0.0.13

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.
Files changed (77) hide show
  1. package/package.json +2 -3
  2. package/coverage/base.css +0 -224
  3. package/coverage/block-navigation.js +0 -87
  4. package/coverage/coverage-final.json +0 -12
  5. package/coverage/favicon.png +0 -0
  6. package/coverage/index.html +0 -176
  7. package/coverage/lib/index.html +0 -116
  8. package/coverage/lib/preload-script.js.html +0 -964
  9. package/coverage/prettify.css +0 -1
  10. package/coverage/prettify.js +0 -2
  11. package/coverage/sort-arrow-sprite.png +0 -0
  12. package/coverage/sorter.js +0 -196
  13. package/coverage/src/collections/index.html +0 -116
  14. package/coverage/src/collections/runner-messages.ts.html +0 -312
  15. package/coverage/src/components/claude-manager.ts.html +0 -1290
  16. package/coverage/src/components/index.html +0 -146
  17. package/coverage/src/components/message-handler.ts.html +0 -730
  18. package/coverage/src/components/repository-manager.ts.html +0 -841
  19. package/coverage/src/index.html +0 -131
  20. package/coverage/src/index.ts.html +0 -448
  21. package/coverage/src/runner.ts.html +0 -1239
  22. package/coverage/src/utils/config.ts.html +0 -780
  23. package/coverage/src/utils/console.ts.html +0 -121
  24. package/coverage/src/utils/index.html +0 -161
  25. package/coverage/src/utils/logger.ts.html +0 -475
  26. package/coverage/src/utils/status-line.ts.html +0 -445
  27. package/exceptions.log +0 -24
  28. package/lib/codex-sdk/src/codex.ts +0 -38
  29. package/lib/codex-sdk/src/codexOptions.ts +0 -10
  30. package/lib/codex-sdk/src/events.ts +0 -80
  31. package/lib/codex-sdk/src/exec.ts +0 -336
  32. package/lib/codex-sdk/src/index.ts +0 -39
  33. package/lib/codex-sdk/src/items.ts +0 -127
  34. package/lib/codex-sdk/src/outputSchemaFile.ts +0 -40
  35. package/lib/codex-sdk/src/thread.ts +0 -155
  36. package/lib/codex-sdk/src/threadOptions.ts +0 -18
  37. package/lib/codex-sdk/src/turnOptions.ts +0 -6
  38. package/lib/codex-sdk/tests/abort.test.ts +0 -165
  39. package/lib/codex-sdk/tests/codexExecSpy.ts +0 -37
  40. package/lib/codex-sdk/tests/responsesProxy.ts +0 -225
  41. package/lib/codex-sdk/tests/run.test.ts +0 -687
  42. package/lib/codex-sdk/tests/runStreamed.test.ts +0 -211
  43. package/lib/codex-sdk/tsconfig.json +0 -24
  44. package/rejections.log +0 -68
  45. package/runner.log +0 -488
  46. package/src/components/claude-sdk-manager.ts +0 -1425
  47. package/src/components/codex-sdk-manager.ts +0 -1358
  48. package/src/components/enhanced-repository-manager.ts +0 -823
  49. package/src/components/message-handler-sse.ts +0 -1097
  50. package/src/components/repository-manager.ts +0 -337
  51. package/src/index.ts +0 -168
  52. package/src/runner-sse.ts +0 -917
  53. package/src/services/RunnerAPIClient.ts +0 -175
  54. package/src/services/SSEClient.ts +0 -258
  55. package/src/types/claude.ts +0 -66
  56. package/src/types/computer-name.d.ts +0 -4
  57. package/src/types/index.ts +0 -64
  58. package/src/types/messages.ts +0 -39
  59. package/src/types/runner-interface.ts +0 -36
  60. package/src/utils/StateManager.ts +0 -187
  61. package/src/utils/config.ts +0 -327
  62. package/src/utils/console.ts +0 -15
  63. package/src/utils/debug.ts +0 -18
  64. package/src/utils/expand-env.ts +0 -22
  65. package/src/utils/logger.ts +0 -134
  66. package/src/utils/model.ts +0 -29
  67. package/src/utils/status-line.ts +0 -122
  68. package/src/utils/tool-response-sanitizer.ts +0 -160
  69. package/test-debug.sh +0 -26
  70. package/tests/retry-strategies.test.ts +0 -410
  71. package/tests/sdk-integration.test.ts +0 -329
  72. package/tests/sdk-streaming.test.ts +0 -1180
  73. package/tests/setup.ts +0 -5
  74. package/tests/test-claude-manager.ts +0 -120
  75. package/tests/tool-response-sanitizer.test.ts +0 -63
  76. package/tsconfig.json +0 -36
  77. package/vitest.config.ts +0 -27
@@ -1,187 +0,0 @@
1
- /**
2
- * State Manager for persisting runner state
3
- *
4
- * Manages persistent state including lastProcessedAt timestamp
5
- * to enable proper catch-up on restart.
6
- */
7
-
8
- import fs from 'fs/promises';
9
- import path from 'path';
10
- import { createLogger } from './logger';
11
-
12
- const logger = createLogger('StateManager');
13
-
14
- export interface RunnerState {
15
- runnerId: string;
16
- runnerUid: string | null;
17
- lastProcessedAt: string | null; // ISO string
18
- isActiveRunner: boolean;
19
- updatedAt: string; // ISO string
20
- }
21
-
22
- export class StateManager {
23
- private statePath: string;
24
- private state: RunnerState | null = null;
25
- private saveTimer: NodeJS.Timeout | null = null;
26
-
27
- constructor(dataDir: string, runnerId: string) {
28
- // Namespace state file by runnerId (stable external ID) to separate different users
29
- const stateFile = `runner-state-${runnerId}.json`;
30
- this.statePath = path.join(dataDir, stateFile);
31
- }
32
-
33
- /**
34
- * Load state from disk
35
- */
36
- async loadState(): Promise<RunnerState | null> {
37
- try {
38
- const content = await fs.readFile(this.statePath, 'utf-8');
39
- this.state = JSON.parse(content);
40
- logger.info('Loaded runner state', {
41
- runnerId: this.state?.runnerId,
42
- runnerUid: this.state?.runnerUid,
43
- lastProcessedAt: this.state?.lastProcessedAt,
44
- isActiveRunner: this.state?.isActiveRunner,
45
- });
46
- return this.state;
47
- } catch (error) {
48
- if ((error as any).code === 'ENOENT') {
49
- logger.debug('No state file found, starting fresh');
50
- return null;
51
- }
52
- logger.error('Failed to load state file:', error);
53
- return null;
54
- }
55
- }
56
-
57
- /**
58
- * Save state to disk
59
- */
60
- async saveState(state: RunnerState): Promise<void> {
61
- this.state = state;
62
-
63
- // Cancel any pending save
64
- if (this.saveTimer) {
65
- clearTimeout(this.saveTimer);
66
- }
67
-
68
- // Debounce saves to avoid excessive disk writes
69
- this.saveTimer = setTimeout(async () => {
70
- try {
71
- // Ensure directory exists
72
- const dir = path.dirname(this.statePath);
73
- await fs.mkdir(dir, { recursive: true });
74
-
75
- // Update timestamp
76
- state.updatedAt = new Date().toISOString();
77
-
78
- // Write atomically by writing to temp file first
79
- const tempPath = `${this.statePath}.tmp`;
80
- await fs.writeFile(tempPath, JSON.stringify(state, null, 2), 'utf-8');
81
- await fs.rename(tempPath, this.statePath);
82
-
83
- logger.debug('Saved runner state', {
84
- runnerId: state.runnerId,
85
- lastProcessedAt: state.lastProcessedAt,
86
- isActiveRunner: state.isActiveRunner,
87
- });
88
- } catch (error) {
89
- logger.error('Failed to save state file:', error);
90
- throw error;
91
- }
92
- }, 1000); // 1 second debounce
93
- }
94
-
95
- /**
96
- * Update lastProcessedAt timestamp
97
- */
98
- async updateLastProcessedAt(timestamp: Date | string): Promise<void> {
99
- if (!this.state) {
100
- throw new Error('State not loaded');
101
- }
102
-
103
- const isoString = timestamp instanceof Date ? timestamp.toISOString() : timestamp;
104
-
105
- const updatedState: RunnerState = {
106
- ...this.state,
107
- lastProcessedAt: isoString,
108
- };
109
-
110
- await this.saveState(updatedState);
111
- }
112
-
113
- /**
114
- * Update runner registration details
115
- */
116
- async updateRunnerRegistration(
117
- runnerId: string,
118
- runnerUid: string | null,
119
- lastProcessedAt: Date | null
120
- ): Promise<void> {
121
- const state: RunnerState = this.state || {
122
- runnerId: '',
123
- runnerUid: null,
124
- lastProcessedAt: null,
125
- isActiveRunner: false,
126
- updatedAt: new Date().toISOString(),
127
- };
128
-
129
- const updatedState: RunnerState = {
130
- ...state,
131
- runnerId,
132
- runnerUid,
133
- lastProcessedAt: lastProcessedAt ? lastProcessedAt.toISOString() : null,
134
- };
135
-
136
- await this.saveState(updatedState);
137
- }
138
-
139
- /**
140
- * Update active runner status
141
- */
142
- async updateActiveStatus(isActive: boolean): Promise<void> {
143
- if (!this.state) {
144
- throw new Error('State not loaded');
145
- }
146
-
147
- const updatedState: RunnerState = {
148
- ...this.state,
149
- isActiveRunner: isActive,
150
- };
151
-
152
- await this.saveState(updatedState);
153
- }
154
-
155
- /**
156
- * Get current state
157
- */
158
- getState(): RunnerState | null {
159
- return this.state;
160
- }
161
-
162
- /**
163
- * Get lastProcessedAt as Date
164
- */
165
- getLastProcessedAt(): Date | null {
166
- if (!this.state || !this.state.lastProcessedAt) {
167
- return null;
168
- }
169
- return new Date(this.state.lastProcessedAt);
170
- }
171
-
172
- /**
173
- * Clear state (for testing or reset)
174
- */
175
- async clearState(): Promise<void> {
176
- this.state = null;
177
- try {
178
- await fs.unlink(this.statePath);
179
- logger.info('Cleared runner state');
180
- } catch (error) {
181
- if ((error as any).code !== 'ENOENT') {
182
- logger.error('Failed to clear state file:', error);
183
- throw error;
184
- }
185
- }
186
- }
187
- }
@@ -1,327 +0,0 @@
1
- /**
2
- * Configuration management utilities
3
- */
4
-
5
- import { RunnerConfig, EnvironmentConfig, RetryStrategy } from "../types";
6
- import fs from "fs/promises";
7
- import path from "path";
8
- import { createLogger } from "./logger";
9
- import { Command } from "commander";
10
-
11
- const logger = createLogger("ConfigManager");
12
-
13
- export class ConfigManager {
14
- private static DEFAULT_CONFIG: Partial<RunnerConfig> = {
15
- dataDir: ConfigManager.resolveDefaultDataDir(),
16
- heartbeatInterval: 120000, // 2 minutes
17
- retryStrategy: "exponential" as RetryStrategy,
18
- retryIntervalSecs: 60,
19
- retryDurationSecs: 900,
20
- };
21
-
22
- private static resolveDefaultDataDir(): string {
23
- try {
24
- const envPaths = require("env-paths").default || require("env-paths");
25
- const paths = envPaths("northflare-runner", { suffix: "" });
26
- return paths.data;
27
- } catch (error) {
28
- logger.debug("Falling back to cwd-relative data directory", error);
29
- return path.resolve("./data");
30
- }
31
- }
32
-
33
- /**
34
- * Load configuration without parsing command line arguments
35
- * Used when arguments have already been parsed by the CLI
36
- */
37
- static async loadConfig(configPath?: string): Promise<RunnerConfig> {
38
- // Validate required environment variables
39
- this.validateEnvironment();
40
-
41
- // Start with defaults
42
- let config: Partial<RunnerConfig> = { ...this.DEFAULT_CONFIG };
43
-
44
- // Determine config path - use provided path or default location
45
- let effectiveConfigPath = configPath;
46
- if (!effectiveConfigPath) {
47
- // Try to use default config location
48
- try {
49
- const envPaths = require("env-paths").default || require("env-paths");
50
- const paths = envPaths("northflare-runner", { suffix: "" });
51
- const defaultConfigPath = path.join(paths.config, "config.json");
52
-
53
- // Check if default config exists
54
- const fs = require("fs");
55
- if (fs.existsSync(defaultConfigPath)) {
56
- effectiveConfigPath = defaultConfigPath;
57
- logger.info(`Using default config file: ${defaultConfigPath}`);
58
- }
59
- } catch (error) {
60
- // env-paths not available or error accessing default location
61
- logger.debug("Could not check default config location:", error);
62
- }
63
- }
64
-
65
- // Load from config file if we have a path
66
- if (effectiveConfigPath) {
67
- const fileConfig = await this.loadConfigFile(effectiveConfigPath);
68
- config = { ...config, ...fileConfig };
69
- }
70
-
71
- // Override with environment variables
72
- const envConfig = this.loadFromEnvironment();
73
- config = { ...config, ...envConfig };
74
-
75
- // Validate final configuration
76
- this.validateConfig(config);
77
-
78
- return config as RunnerConfig;
79
- }
80
-
81
- /**
82
- * Parse command line arguments and load configuration
83
- */
84
- static async parseArgsAndLoadConfig(argv: string[]): Promise<RunnerConfig> {
85
- const program = new Command();
86
-
87
- program
88
- .name("northflare-runner")
89
- .description(
90
- "Northflare Runner - Executes Claude agents for task processing"
91
- )
92
- .version("1.0.0")
93
- .option("-c, --config <path>", "Path to configuration file")
94
- .option(
95
- "--retry-strategy <strategy>",
96
- "Registration retry strategy (none, interval, exponential)",
97
- "exponential"
98
- )
99
- .option(
100
- "--retry-interval-secs <seconds>",
101
- "Retry interval in seconds for interval strategy",
102
- "60"
103
- )
104
- .option(
105
- "--retry-duration-secs <seconds>",
106
- "Max retry duration in seconds for exponential strategy",
107
- "900"
108
- )
109
- .option("--data-dir <path>", "Data directory path")
110
- .option(
111
- "--heartbeat-interval <ms>",
112
- "Heartbeat interval in milliseconds",
113
- "120000"
114
- );
115
-
116
- program.parse(argv);
117
- const options = program.opts();
118
-
119
- // Validate required environment variables
120
- this.validateEnvironment();
121
-
122
- // Start with defaults
123
- let config: Partial<RunnerConfig> = { ...this.DEFAULT_CONFIG };
124
-
125
- // Load from config file if provided
126
- if (options["config"]) {
127
- const fileConfig = await this.loadConfigFile(options["config"]);
128
- config = { ...config, ...fileConfig };
129
- }
130
-
131
- // Override with environment variables
132
- const envConfig = this.loadFromEnvironment();
133
- config = { ...config, ...envConfig };
134
-
135
- // Override with CLI arguments (highest priority)
136
- if (options["retryStrategy"]) {
137
- config.retryStrategy = options["retryStrategy"] as RetryStrategy;
138
- }
139
- if (options["retryIntervalSecs"]) {
140
- config.retryIntervalSecs = parseInt(options["retryIntervalSecs"]);
141
- }
142
- if (options["retryDurationSecs"]) {
143
- config.retryDurationSecs = parseInt(options["retryDurationSecs"]);
144
- }
145
- if (options["dataDir"]) {
146
- config.dataDir = options["dataDir"];
147
- }
148
- if (options["heartbeatInterval"]) {
149
- config.heartbeatInterval = parseInt(options["heartbeatInterval"]);
150
- }
151
-
152
- // Validate final configuration
153
- this.validateConfig(config);
154
-
155
- return config as RunnerConfig;
156
- }
157
-
158
- /**
159
- * Validate required environment variables
160
- */
161
- private static validateEnvironment(): void {
162
- // Set default for NORTHFLARE_WORKSPACE_DIR if not provided
163
- if (!process.env["NORTHFLARE_WORKSPACE_DIR"]) {
164
- try {
165
- const envPaths = require("env-paths").default || require("env-paths");
166
- const paths = envPaths("northflare-runner", { suffix: "" });
167
- process.env["NORTHFLARE_WORKSPACE_DIR"] = paths.data;
168
- } catch (error) {
169
- // Fallback to original default if env-paths is not available
170
- process.env["NORTHFLARE_WORKSPACE_DIR"] = "/workspace";
171
- }
172
- }
173
-
174
- // Set default for NORTHFLARE_ORCHESTRATOR_URL if not provided
175
- if (!process.env["NORTHFLARE_ORCHESTRATOR_URL"]) {
176
- process.env["NORTHFLARE_ORCHESTRATOR_URL"] = "https://api.northflare.app";
177
- }
178
-
179
- const required = [
180
- "NORTHFLARE_RUNNER_TOKEN",
181
- "NORTHFLARE_WORKSPACE_DIR",
182
- "NORTHFLARE_ORCHESTRATOR_URL",
183
- ];
184
-
185
- const missing = required.filter((key) => !process.env[key]);
186
-
187
- if (missing.length > 0) {
188
- throw new Error(
189
- `Missing required environment variables: ${missing.join(", ")}\n` +
190
- "Please set these environment variables before starting the runner."
191
- );
192
- }
193
- }
194
-
195
- /**
196
- * Load configuration from environment variables
197
- */
198
- private static loadFromEnvironment(): Partial<RunnerConfig> {
199
- const config: Partial<RunnerConfig> = {
200
- orchestratorUrl: process.env["NORTHFLARE_ORCHESTRATOR_URL"]!,
201
- };
202
-
203
- // Optional environment overrides
204
- if (process.env["NORTHFLARE_DATA_DIR"]) {
205
- config.dataDir = process.env["NORTHFLARE_DATA_DIR"];
206
- }
207
-
208
- return config;
209
- }
210
-
211
- /**
212
- * Load configuration from file
213
- */
214
- private static async loadConfigFile(
215
- configPath: string
216
- ): Promise<Partial<RunnerConfig>> {
217
- try {
218
- const absolutePath = path.resolve(configPath);
219
- const content = await fs.readFile(absolutePath, "utf-8");
220
-
221
- // Support both JSON and YAML formats
222
- if (configPath.endsWith(".json")) {
223
- const config = JSON.parse(content);
224
-
225
- // Log if runnerRepos are found in config
226
- if (config.runnerRepos && Array.isArray(config.runnerRepos)) {
227
- logger.info(
228
- `Found ${config.runnerRepos.length} runner repos in config file`
229
- );
230
- }
231
-
232
- return config;
233
- } else if (configPath.endsWith(".yaml") || configPath.endsWith(".yml")) {
234
- // For YAML support, we'd need to add a yaml parser dependency
235
- throw new Error("YAML configuration files are not yet supported");
236
- } else {
237
- throw new Error("Configuration file must be .json format");
238
- }
239
- } catch (error) {
240
- if ((error as any).code === "ENOENT") {
241
- logger.warn(`Configuration file not found: ${configPath}`);
242
- return {};
243
- }
244
- throw error;
245
- }
246
- }
247
-
248
- /**
249
- * Validate final configuration
250
- */
251
- private static validateConfig(config: Partial<RunnerConfig>): void {
252
- // Note: Runner ID will be generated by server during registration
253
-
254
- if (!config.orchestratorUrl) {
255
- throw new Error("orchestratorUrl is required");
256
- }
257
-
258
- if (!config.dataDir) {
259
- throw new Error("dataDir is required");
260
- }
261
-
262
- if (!config.heartbeatInterval || config.heartbeatInterval < 1000) {
263
- throw new Error("heartbeatInterval must be at least 1000ms");
264
- }
265
-
266
- // Validate retry strategy
267
- const validStrategies: RetryStrategy[] = [
268
- "none",
269
- "interval",
270
- "exponential",
271
- ];
272
- if (
273
- !config.retryStrategy ||
274
- !validStrategies.includes(config.retryStrategy)
275
- ) {
276
- throw new Error(
277
- `retryStrategy must be one of: ${validStrategies.join(", ")}`
278
- );
279
- }
280
-
281
- if (
282
- config.retryStrategy === "interval" &&
283
- (!config.retryIntervalSecs || config.retryIntervalSecs < 1)
284
- ) {
285
- throw new Error(
286
- "retryIntervalSecs must be at least 1 when using interval strategy"
287
- );
288
- }
289
-
290
- if (
291
- config.retryStrategy === "exponential" &&
292
- (!config.retryDurationSecs || config.retryDurationSecs < 1)
293
- ) {
294
- throw new Error(
295
- "retryDurationSecs must be at least 1 when using exponential strategy"
296
- );
297
- }
298
- }
299
-
300
- /**
301
- * Get all environment configuration
302
- */
303
- static getEnvironmentConfig(): EnvironmentConfig {
304
- return {
305
- NORTHFLARE_RUNNER_TOKEN: process.env["NORTHFLARE_RUNNER_TOKEN"]!,
306
- NORTHFLARE_WORKSPACE_DIR: process.env["NORTHFLARE_WORKSPACE_DIR"]!,
307
- NORTHFLARE_ORCHESTRATOR_URL: process.env["NORTHFLARE_ORCHESTRATOR_URL"]!,
308
- NORTHFLARE_RUNNER_DEBUG: process.env["NORTHFLARE_RUNNER_DEBUG"],
309
- DEBUG: process.env["DEBUG"],
310
- };
311
- }
312
-
313
- /**
314
- * Save configuration file with updated data
315
- */
316
- static async saveConfigFile(configPath: string, config: any): Promise<void> {
317
- try {
318
- const absolutePath = path.resolve(configPath);
319
- const content = JSON.stringify(config, null, 2);
320
- await fs.writeFile(absolutePath, content, "utf-8");
321
- logger.info(`Updated configuration file: ${absolutePath}`);
322
- } catch (error) {
323
- logger.error("Failed to save configuration file", error);
324
- throw error;
325
- }
326
- }
327
- }
@@ -1,15 +0,0 @@
1
- /**
2
- * Console wrapper that suppresses output when not in debug mode
3
- */
4
-
5
- import { isRunnerDebugEnabled } from "./debug";
6
-
7
- const isDebug = isRunnerDebugEnabled();
8
-
9
- export const console = {
10
- log: isDebug ? global.console.log.bind(global.console) : () => {},
11
- warn: isDebug ? global.console.warn.bind(global.console) : () => {},
12
- error: global.console.error.bind(global.console), // Always show errors
13
- info: isDebug ? global.console.info.bind(global.console) : () => {},
14
- debug: isDebug ? global.console.debug.bind(global.console) : () => {},
15
- };
@@ -1,18 +0,0 @@
1
- export function isRunnerDebugEnabled(): boolean {
2
- const explicitFlag = process.env["NORTHFLARE_RUNNER_DEBUG"];
3
- if (typeof explicitFlag === "string") {
4
- return isTruthy(explicitFlag);
5
- }
6
-
7
- const legacyFlag = process.env["DEBUG"];
8
- if (typeof legacyFlag === "string") {
9
- return isTruthy(legacyFlag);
10
- }
11
-
12
- return false;
13
- }
14
-
15
- function isTruthy(value: string): boolean {
16
- const normalized = value.trim().toLowerCase();
17
- return normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on";
18
- }
@@ -1,22 +0,0 @@
1
- export function expandEnv(
2
- obj: any,
3
- env: Record<string, string | undefined> = process.env
4
- ): any {
5
- if (typeof obj === "string") {
6
- return obj.replace(/\$\{([^}]+)\}/g, (match, key) => env[key] || match);
7
- }
8
-
9
- if (Array.isArray(obj)) {
10
- return obj.map((item) => expandEnv(item, env));
11
- }
12
-
13
- if (obj && typeof obj === "object") {
14
- const result: Record<string, any> = {};
15
- for (const [key, value] of Object.entries(obj)) {
16
- result[key] = expandEnv(value, env);
17
- }
18
- return result;
19
- }
20
-
21
- return obj;
22
- }
@@ -1,134 +0,0 @@
1
- /**
2
- * Logger configuration using Winston
3
- */
4
-
5
- import winston from "winston";
6
- import path from "path";
7
- import { isRunnerDebugEnabled } from "./debug";
8
-
9
- // Custom log levels
10
- const levels = {
11
- error: 0,
12
- warn: 1,
13
- info: 2,
14
- http: 3,
15
- verbose: 4,
16
- debug: 5,
17
- silly: 6,
18
- };
19
-
20
- // Log colors
21
- const colors = {
22
- error: "red",
23
- warn: "yellow",
24
- info: "green",
25
- http: "magenta",
26
- verbose: "cyan",
27
- debug: "blue",
28
- silly: "gray",
29
- };
30
-
31
- winston.addColors(colors);
32
-
33
- const debugEnabled = isRunnerDebugEnabled();
34
-
35
- // Format for console output
36
- const consoleFormat = winston.format.combine(
37
- winston.format.colorize({ all: true }),
38
- winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
39
- winston.format.printf(({ timestamp, level, message, stack, ...metadata }) => {
40
- let msg = `${timestamp} [${level}]: ${message}`;
41
- // Only show stack traces and metadata in debug mode
42
- if (debugEnabled) {
43
- if (stack) {
44
- msg += `\n${stack}`;
45
- }
46
- const filteredMetadata = { ...metadata };
47
- delete filteredMetadata["component"]; // Keep component info
48
- if (Object.keys(filteredMetadata).length > 0) {
49
- msg += ` ${JSON.stringify(filteredMetadata)}`;
50
- }
51
- }
52
- return msg;
53
- })
54
- );
55
-
56
- // Format for file output
57
- const fileFormat = winston.format.combine(
58
- winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
59
- winston.format.errors({ stack: true }),
60
- winston.format.json()
61
- );
62
-
63
- // Create logger instance
64
- export const logger = winston.createLogger({
65
- level: debugEnabled ? "debug" : "info",
66
- levels,
67
- transports: [
68
- // Always include console transport for errors
69
- new winston.transports.Console({
70
- format: consoleFormat,
71
- level: debugEnabled ? "debug" : "error",
72
- }),
73
- ],
74
- });
75
-
76
- // Add file transports if log directory is configured
77
- export function configureFileLogging(logDir: string): void {
78
- // Error log file
79
- logger.add(
80
- new winston.transports.File({
81
- filename: path.join(logDir, "error.log"),
82
- level: "error",
83
- format: fileFormat,
84
- maxsize: 10 * 1024 * 1024, // 10MB
85
- maxFiles: 5,
86
- })
87
- );
88
-
89
- // Combined log file
90
- logger.add(
91
- new winston.transports.File({
92
- filename: path.join(logDir, "combined.log"),
93
- format: fileFormat,
94
- maxsize: 50 * 1024 * 1024, // 50MB
95
- maxFiles: 10,
96
- })
97
- );
98
-
99
- // Debug log file (only in debug mode)
100
- if (debugEnabled) {
101
- logger.add(
102
- new winston.transports.File({
103
- filename: path.join(logDir, "debug.log"),
104
- level: "debug",
105
- format: fileFormat,
106
- maxsize: 100 * 1024 * 1024, // 100MB
107
- maxFiles: 3,
108
- })
109
- );
110
- }
111
- }
112
-
113
- // Create child logger for specific components
114
- export function createLogger(component: string): winston.Logger {
115
- return logger.child({ component });
116
- }
117
-
118
- // Log unhandled errors
119
- logger.exceptions.handle(
120
- new winston.transports.File({
121
- filename: "exceptions.log",
122
- format: fileFormat,
123
- })
124
- );
125
-
126
- logger.rejections.handle(
127
- new winston.transports.File({
128
- filename: "rejections.log",
129
- format: fileFormat,
130
- })
131
- );
132
-
133
- // Export default logger
134
- export default logger;