wave-agent-sdk 0.0.6 → 0.0.8
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/dist/agent.d.ts +32 -20
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +209 -24
- package/dist/constants/events.d.ts +28 -0
- package/dist/constants/events.d.ts.map +1 -0
- package/dist/constants/events.js +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/managers/aiManager.d.ts +34 -1
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +248 -132
- package/dist/managers/backgroundBashManager.d.ts.map +1 -1
- package/dist/managers/backgroundBashManager.js +7 -6
- package/dist/managers/hookManager.d.ts +13 -16
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +81 -44
- package/dist/managers/liveConfigManager.d.ts +58 -0
- package/dist/managers/liveConfigManager.d.ts.map +1 -0
- package/dist/managers/liveConfigManager.js +160 -0
- package/dist/managers/messageManager.d.ts +41 -24
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +168 -49
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +9 -3
- package/dist/managers/subagentManager.d.ts +51 -0
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +190 -19
- package/dist/services/aiService.d.ts +13 -5
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +350 -74
- package/dist/services/configurationWatcher.d.ts +120 -0
- package/dist/services/configurationWatcher.d.ts.map +1 -0
- package/dist/services/configurationWatcher.js +439 -0
- package/dist/services/fileWatcher.d.ts +69 -0
- package/dist/services/fileWatcher.d.ts.map +1 -0
- package/dist/services/fileWatcher.js +213 -0
- package/dist/services/hook.d.ts +91 -9
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +393 -43
- package/dist/services/jsonlHandler.d.ts +62 -0
- package/dist/services/jsonlHandler.d.ts.map +1 -0
- package/dist/services/jsonlHandler.js +257 -0
- package/dist/services/memory.d.ts +9 -0
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +81 -12
- package/dist/services/memoryStore.d.ts +81 -0
- package/dist/services/memoryStore.d.ts.map +1 -0
- package/dist/services/memoryStore.js +200 -0
- package/dist/services/session.d.ts +64 -49
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +310 -132
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +5 -4
- package/dist/tools/deleteFileTool.d.ts.map +1 -1
- package/dist/tools/deleteFileTool.js +2 -1
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +3 -2
- package/dist/tools/multiEditTool.d.ts.map +1 -1
- package/dist/tools/multiEditTool.js +4 -3
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +2 -1
- package/dist/tools/todoWriteTool.d.ts.map +1 -1
- package/dist/tools/todoWriteTool.js +3 -10
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +5 -6
- package/dist/types/commands.d.ts +4 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/core.d.ts +35 -0
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/environment.d.ts +42 -0
- package/dist/types/environment.d.ts.map +1 -0
- package/dist/types/environment.js +21 -0
- package/dist/types/hooks.d.ts +8 -2
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +8 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/memoryStore.d.ts +82 -0
- package/dist/types/memoryStore.d.ts.map +1 -0
- package/dist/types/memoryStore.js +7 -0
- package/dist/types/messaging.d.ts +21 -9
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/messaging.js +5 -1
- package/dist/types/session.d.ts +20 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +7 -0
- package/dist/utils/bashHistory.d.ts.map +1 -1
- package/dist/utils/bashHistory.js +27 -26
- package/dist/utils/cacheControlUtils.d.ts +121 -0
- package/dist/utils/cacheControlUtils.d.ts.map +1 -0
- package/dist/utils/cacheControlUtils.js +367 -0
- package/dist/utils/commandPathResolver.d.ts +52 -0
- package/dist/utils/commandPathResolver.d.ts.map +1 -0
- package/dist/utils/commandPathResolver.js +145 -0
- package/dist/utils/configPaths.d.ts +85 -0
- package/dist/utils/configPaths.d.ts.map +1 -0
- package/dist/utils/configPaths.js +121 -0
- package/dist/utils/configResolver.d.ts +37 -10
- package/dist/utils/configResolver.d.ts.map +1 -1
- package/dist/utils/configResolver.js +127 -23
- package/dist/utils/constants.d.ts +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +8 -13
- package/dist/utils/customCommands.d.ts.map +1 -1
- package/dist/utils/customCommands.js +66 -21
- package/dist/utils/fileUtils.d.ts +15 -0
- package/dist/utils/fileUtils.d.ts.map +1 -0
- package/dist/utils/fileUtils.js +61 -0
- package/dist/utils/globalLogger.d.ts +102 -0
- package/dist/utils/globalLogger.d.ts.map +1 -0
- package/dist/utils/globalLogger.js +136 -0
- package/dist/utils/hookMatcher.d.ts +1 -6
- package/dist/utils/hookMatcher.d.ts.map +1 -1
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +25 -3
- package/dist/utils/messageOperations.d.ts +27 -27
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +46 -36
- package/dist/utils/pathEncoder.d.ts +104 -0
- package/dist/utils/pathEncoder.d.ts.map +1 -0
- package/dist/utils/pathEncoder.js +272 -0
- package/dist/utils/subagentParser.d.ts.map +1 -1
- package/dist/utils/subagentParser.js +2 -1
- package/dist/utils/tokenCalculation.d.ts +26 -0
- package/dist/utils/tokenCalculation.d.ts.map +1 -0
- package/dist/utils/tokenCalculation.js +36 -0
- package/package.json +6 -3
- package/src/agent.ts +301 -37
- package/src/constants/events.ts +38 -0
- package/src/index.ts +2 -0
- package/src/managers/aiManager.ts +325 -173
- package/src/managers/backgroundBashManager.ts +7 -6
- package/src/managers/hookManager.ts +106 -84
- package/src/managers/liveConfigManager.ts +248 -0
- package/src/managers/messageManager.ts +237 -100
- package/src/managers/slashCommandManager.ts +9 -7
- package/src/managers/subagentManager.ts +284 -22
- package/src/services/aiService.ts +474 -83
- package/src/services/configurationWatcher.ts +622 -0
- package/src/services/fileWatcher.ts +301 -0
- package/src/services/hook.ts +538 -47
- package/src/services/jsonlHandler.ts +319 -0
- package/src/services/memory.ts +92 -12
- package/src/services/memoryStore.ts +279 -0
- package/src/services/session.ts +381 -157
- package/src/tools/bashTool.ts +5 -4
- package/src/tools/deleteFileTool.ts +2 -1
- package/src/tools/editTool.ts +3 -2
- package/src/tools/multiEditTool.ts +4 -3
- package/src/tools/readTool.ts +2 -1
- package/src/tools/todoWriteTool.ts +3 -11
- package/src/tools/writeTool.ts +7 -6
- package/src/types/commands.ts +6 -0
- package/src/types/core.ts +44 -0
- package/src/types/environment.ts +60 -0
- package/src/types/hooks.ts +21 -8
- package/src/types/index.ts +2 -0
- package/src/types/memoryStore.ts +94 -0
- package/src/types/messaging.ts +21 -10
- package/src/types/session.ts +25 -0
- package/src/utils/bashHistory.ts +27 -27
- package/src/utils/cacheControlUtils.ts +540 -0
- package/src/utils/commandPathResolver.ts +189 -0
- package/src/utils/configPaths.ts +163 -0
- package/src/utils/configResolver.ts +182 -22
- package/src/utils/constants.ts +1 -1
- package/src/utils/convertMessagesForAPI.ts +8 -14
- package/src/utils/customCommands.ts +90 -22
- package/src/utils/fileUtils.ts +65 -0
- package/src/utils/globalLogger.ts +145 -0
- package/src/utils/hookMatcher.ts +1 -12
- package/src/utils/mcpUtils.ts +34 -3
- package/src/utils/messageOperations.ts +77 -60
- package/src/utils/pathEncoder.ts +379 -0
- package/src/utils/subagentParser.ts +2 -1
- package/src/utils/tokenCalculation.ts +43 -0
- package/src/types/index.ts.backup +0 -357
package/src/services/hook.ts
CHANGED
|
@@ -7,19 +7,31 @@
|
|
|
7
7
|
|
|
8
8
|
import { spawn, type ChildProcess } from "child_process";
|
|
9
9
|
import { existsSync, readFileSync } from "fs";
|
|
10
|
-
import {
|
|
11
|
-
|
|
10
|
+
import {
|
|
11
|
+
getUserConfigPath,
|
|
12
|
+
getProjectConfigPath,
|
|
13
|
+
getUserConfigPaths,
|
|
14
|
+
getProjectConfigPaths,
|
|
15
|
+
hasAnyConfig,
|
|
16
|
+
getConfigurationInfo,
|
|
17
|
+
} from "../utils/configPaths.js";
|
|
12
18
|
import {
|
|
13
19
|
type HookExecutionContext,
|
|
14
20
|
type HookExecutionResult,
|
|
15
21
|
type HookExecutionOptions,
|
|
16
22
|
type ExtendedHookExecutionContext,
|
|
17
23
|
type HookJsonInput,
|
|
18
|
-
type
|
|
24
|
+
type WaveConfiguration,
|
|
19
25
|
type PartialHookConfiguration,
|
|
20
26
|
getSessionFilePath,
|
|
21
27
|
isValidHookEvent,
|
|
22
28
|
} from "../types/hooks.js";
|
|
29
|
+
import {
|
|
30
|
+
type EnvironmentValidationResult,
|
|
31
|
+
type MergedEnvironmentContext,
|
|
32
|
+
type EnvironmentMergeOptions,
|
|
33
|
+
isValidEnvironmentVars,
|
|
34
|
+
} from "../types/environment.js";
|
|
23
35
|
|
|
24
36
|
// =============================================================================
|
|
25
37
|
// Hook Execution Functions
|
|
@@ -61,6 +73,11 @@ function buildHookJsonInput(
|
|
|
61
73
|
jsonInput.user_prompt = context.userPrompt;
|
|
62
74
|
}
|
|
63
75
|
|
|
76
|
+
// Add subagent_type if present
|
|
77
|
+
if (context.subagentType !== undefined) {
|
|
78
|
+
jsonInput.subagent_type = context.subagentType;
|
|
79
|
+
}
|
|
80
|
+
|
|
64
81
|
return jsonInput;
|
|
65
82
|
}
|
|
66
83
|
|
|
@@ -71,6 +88,7 @@ export async function executeCommand(
|
|
|
71
88
|
command: string,
|
|
72
89
|
context: HookExecutionContext | ExtendedHookExecutionContext,
|
|
73
90
|
options?: HookExecutionOptions,
|
|
91
|
+
additionalEnvVars?: Record<string, string>,
|
|
74
92
|
): Promise<HookExecutionResult> {
|
|
75
93
|
const defaultTimeout = 10000; // 10 seconds
|
|
76
94
|
const maxTimeout = 300000; // 5 minutes
|
|
@@ -108,6 +126,7 @@ export async function executeCommand(
|
|
|
108
126
|
cwd: context.projectDir,
|
|
109
127
|
env: {
|
|
110
128
|
...process.env,
|
|
129
|
+
...additionalEnvVars, // Merge additional environment variables from Wave configuration
|
|
111
130
|
HOOK_EVENT: context.event,
|
|
112
131
|
HOOK_TOOL_NAME: context.toolName || "",
|
|
113
132
|
HOOK_PROJECT_DIR: context.projectDir,
|
|
@@ -193,11 +212,17 @@ export async function executeCommands(
|
|
|
193
212
|
commands: string[],
|
|
194
213
|
context: HookExecutionContext | ExtendedHookExecutionContext,
|
|
195
214
|
options?: HookExecutionOptions,
|
|
215
|
+
additionalEnvVars?: Record<string, string>,
|
|
196
216
|
): Promise<HookExecutionResult[]> {
|
|
197
217
|
const results: HookExecutionResult[] = [];
|
|
198
218
|
|
|
199
219
|
for (const command of commands) {
|
|
200
|
-
const result = await executeCommand(
|
|
220
|
+
const result = await executeCommand(
|
|
221
|
+
command,
|
|
222
|
+
context,
|
|
223
|
+
options,
|
|
224
|
+
additionalEnvVars,
|
|
225
|
+
);
|
|
201
226
|
results.push(result);
|
|
202
227
|
|
|
203
228
|
// Stop on first failure unless continueOnFailure is set
|
|
@@ -237,68 +262,502 @@ export function isCommandSafe(command: string): boolean {
|
|
|
237
262
|
}
|
|
238
263
|
|
|
239
264
|
// =============================================================================
|
|
240
|
-
//
|
|
265
|
+
// Environment Variable Functions
|
|
241
266
|
// =============================================================================
|
|
242
267
|
|
|
243
268
|
/**
|
|
244
|
-
*
|
|
269
|
+
* Validate environment variable configuration
|
|
270
|
+
*/
|
|
271
|
+
export function validateEnvironmentConfig(
|
|
272
|
+
env: unknown,
|
|
273
|
+
configPath?: string,
|
|
274
|
+
): EnvironmentValidationResult {
|
|
275
|
+
const result: EnvironmentValidationResult = {
|
|
276
|
+
isValid: true,
|
|
277
|
+
errors: [],
|
|
278
|
+
warnings: [],
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// Check if env is defined
|
|
282
|
+
if (env === undefined || env === null) {
|
|
283
|
+
return result; // undefined/null env is valid (means no env vars)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Validate that env is a Record<string, string>
|
|
287
|
+
if (!isValidEnvironmentVars(env)) {
|
|
288
|
+
result.isValid = false;
|
|
289
|
+
result.errors.push(
|
|
290
|
+
`Invalid env field format${configPath ? ` in ${configPath}` : ""}. Environment variables must be a Record<string, string>.`,
|
|
291
|
+
);
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Additional validation for environment variable names
|
|
296
|
+
const envVars = env as Record<string, string>;
|
|
297
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
298
|
+
// Check for valid environment variable naming convention
|
|
299
|
+
if (!/^[A-Z_][A-Z0-9_]*$/i.test(key)) {
|
|
300
|
+
result.warnings.push(
|
|
301
|
+
`Environment variable '${key}' does not follow standard naming convention (alphanumeric and underscores only).`,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Check for empty values
|
|
306
|
+
if (value === "") {
|
|
307
|
+
result.warnings.push(`Environment variable '${key}' has an empty value.`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Check for reserved variable names that might cause conflicts
|
|
311
|
+
const reservedNames = [
|
|
312
|
+
"PATH",
|
|
313
|
+
"HOME",
|
|
314
|
+
"USER",
|
|
315
|
+
"PWD",
|
|
316
|
+
"SHELL",
|
|
317
|
+
"TERM",
|
|
318
|
+
"NODE_ENV",
|
|
319
|
+
];
|
|
320
|
+
if (reservedNames.includes(key.toUpperCase())) {
|
|
321
|
+
result.warnings.push(
|
|
322
|
+
`Environment variable '${key}' overrides a system variable, which may cause unexpected behavior.`,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Merge environment configurations with project taking precedence over user
|
|
332
|
+
*/
|
|
333
|
+
export function mergeEnvironmentConfig(
|
|
334
|
+
userEnv: Record<string, string> | undefined,
|
|
335
|
+
projectEnv: Record<string, string> | undefined,
|
|
336
|
+
options: EnvironmentMergeOptions = {},
|
|
337
|
+
): MergedEnvironmentContext {
|
|
338
|
+
const userVars = userEnv || {};
|
|
339
|
+
const projectVars = projectEnv || {};
|
|
340
|
+
const mergedVars: Record<string, string> = {};
|
|
341
|
+
const conflicts: MergedEnvironmentContext["conflicts"] = [];
|
|
342
|
+
|
|
343
|
+
// Start with user environment variables
|
|
344
|
+
Object.assign(mergedVars, userVars);
|
|
345
|
+
|
|
346
|
+
// Override with project environment variables and track conflicts
|
|
347
|
+
for (const [key, projectValue] of Object.entries(projectVars)) {
|
|
348
|
+
const userValue = userVars[key];
|
|
349
|
+
|
|
350
|
+
if (
|
|
351
|
+
userValue !== undefined &&
|
|
352
|
+
userValue !== projectValue &&
|
|
353
|
+
options.includeConflictWarnings !== false
|
|
354
|
+
) {
|
|
355
|
+
// Conflict detected - project value takes precedence
|
|
356
|
+
conflicts.push({
|
|
357
|
+
key,
|
|
358
|
+
userValue,
|
|
359
|
+
projectValue,
|
|
360
|
+
resolvedValue: projectValue,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
mergedVars[key] = projectValue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
userVars,
|
|
369
|
+
projectVars,
|
|
370
|
+
mergedVars,
|
|
371
|
+
conflicts,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// =============================================================================
|
|
376
|
+
// Hook Settings Functions (using centralized config path utilities)
|
|
377
|
+
// =============================================================================
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Get the user-specific hooks configuration file path (legacy function)
|
|
381
|
+
* @deprecated Use getUserConfigPaths() from configPaths.ts for better priority support
|
|
245
382
|
*/
|
|
246
383
|
export function getUserHooksConfigPath(): string {
|
|
247
|
-
return
|
|
384
|
+
return getUserConfigPath();
|
|
248
385
|
}
|
|
249
386
|
|
|
250
387
|
/**
|
|
251
|
-
* Get the project-specific hooks configuration file path
|
|
388
|
+
* Get the project-specific hooks configuration file path (legacy function)
|
|
389
|
+
* @deprecated Use getProjectConfigPaths() from configPaths.ts for better priority support
|
|
252
390
|
*/
|
|
253
391
|
export function getProjectHooksConfigPath(workdir: string): string {
|
|
254
|
-
return
|
|
392
|
+
return getProjectConfigPath(workdir);
|
|
255
393
|
}
|
|
256
394
|
|
|
257
395
|
/**
|
|
258
|
-
*
|
|
396
|
+
* Get the user-specific hooks configuration file paths in priority order
|
|
397
|
+
* @deprecated Use getUserConfigPaths() from configPaths.ts directly
|
|
259
398
|
*/
|
|
260
|
-
export function
|
|
399
|
+
export function getUserHooksConfigPaths(): string[] {
|
|
400
|
+
return getUserConfigPaths();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Get the project-specific hooks configuration file paths in priority order
|
|
405
|
+
* @deprecated Use getProjectConfigPaths() from configPaths.ts directly
|
|
406
|
+
*/
|
|
407
|
+
export function getProjectHooksConfigPaths(workdir: string): string[] {
|
|
408
|
+
return getProjectConfigPaths(workdir);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Load Wave configuration from a JSON file with graceful fallback
|
|
413
|
+
* This version is optimized for live reload scenarios where invalid config should not crash the system
|
|
414
|
+
*/
|
|
415
|
+
export function loadWaveConfigFromFileWithFallback(
|
|
261
416
|
filePath: string,
|
|
262
|
-
|
|
417
|
+
previousValidConfig?: WaveConfiguration | null,
|
|
418
|
+
): { config: WaveConfiguration | null; error?: string; usedFallback: boolean } {
|
|
419
|
+
if (!existsSync(filePath)) {
|
|
420
|
+
return { config: null, usedFallback: false };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
const content = readFileSync(filePath, "utf-8");
|
|
425
|
+
const config = JSON.parse(content) as WaveConfiguration;
|
|
426
|
+
|
|
427
|
+
// Validate basic structure
|
|
428
|
+
if (!config || typeof config !== "object") {
|
|
429
|
+
const error = `Invalid configuration structure in ${filePath}`;
|
|
430
|
+
return {
|
|
431
|
+
config: previousValidConfig || null,
|
|
432
|
+
error,
|
|
433
|
+
usedFallback: !!previousValidConfig,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Validate environment variables if present
|
|
438
|
+
if (config.env !== undefined) {
|
|
439
|
+
const envValidation = validateEnvironmentConfig(config.env, filePath);
|
|
440
|
+
|
|
441
|
+
if (!envValidation.isValid) {
|
|
442
|
+
const error = `Environment variable validation failed in ${filePath}: ${envValidation.errors.join(", ")}`;
|
|
443
|
+
return {
|
|
444
|
+
config: previousValidConfig || null,
|
|
445
|
+
error,
|
|
446
|
+
usedFallback: !!previousValidConfig,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Log warnings if any
|
|
451
|
+
if (envValidation.warnings.length > 0) {
|
|
452
|
+
console.warn(
|
|
453
|
+
`Environment variable warnings in ${filePath}:\n- ${envValidation.warnings.join("\n- ")}`,
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Return valid configuration
|
|
459
|
+
return {
|
|
460
|
+
config: {
|
|
461
|
+
hooks: config.hooks || undefined,
|
|
462
|
+
env: config.env || undefined,
|
|
463
|
+
},
|
|
464
|
+
usedFallback: false,
|
|
465
|
+
};
|
|
466
|
+
} catch (error) {
|
|
467
|
+
let errorMessage: string;
|
|
468
|
+
|
|
469
|
+
if (error instanceof SyntaxError) {
|
|
470
|
+
errorMessage = `Invalid JSON syntax in ${filePath}: ${error.message}`;
|
|
471
|
+
} else {
|
|
472
|
+
errorMessage = `Error loading configuration from ${filePath}: ${(error as Error).message}`;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return {
|
|
476
|
+
config: previousValidConfig || null,
|
|
477
|
+
error: errorMessage,
|
|
478
|
+
usedFallback: !!previousValidConfig,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Load Wave configuration from multiple file paths in priority order
|
|
485
|
+
* Returns the first valid configuration found, or null if none exist
|
|
486
|
+
*/
|
|
487
|
+
export function loadWaveConfigFromFiles(
|
|
488
|
+
filePaths: string[],
|
|
489
|
+
): WaveConfiguration | null {
|
|
490
|
+
for (const filePath of filePaths) {
|
|
491
|
+
const config = loadWaveConfigFromFile(filePath);
|
|
492
|
+
if (config !== null) {
|
|
493
|
+
return config;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Load Wave configuration from multiple file paths with graceful fallback
|
|
501
|
+
* Returns the first valid configuration found with fallback support
|
|
502
|
+
*/
|
|
503
|
+
export function loadWaveConfigFromFilesWithFallback(
|
|
504
|
+
filePaths: string[],
|
|
505
|
+
previousValidConfig?: WaveConfiguration | null,
|
|
506
|
+
): {
|
|
507
|
+
config: WaveConfiguration | null;
|
|
508
|
+
error?: string;
|
|
509
|
+
usedFallback: boolean;
|
|
510
|
+
usedPath?: string;
|
|
511
|
+
} {
|
|
512
|
+
let lastError: string | undefined;
|
|
513
|
+
|
|
514
|
+
for (const filePath of filePaths) {
|
|
515
|
+
const result = loadWaveConfigFromFileWithFallback(
|
|
516
|
+
filePath,
|
|
517
|
+
previousValidConfig,
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
if (result.config !== null && !result.usedFallback) {
|
|
521
|
+
// Found a valid config at this path
|
|
522
|
+
return {
|
|
523
|
+
config: result.config,
|
|
524
|
+
error: result.error,
|
|
525
|
+
usedFallback: result.usedFallback,
|
|
526
|
+
usedPath: filePath,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (result.error) {
|
|
531
|
+
lastError = result.error;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// No valid config found in any path
|
|
536
|
+
return {
|
|
537
|
+
config: previousValidConfig || null,
|
|
538
|
+
error: lastError,
|
|
539
|
+
usedFallback: !!previousValidConfig,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Load and merge Wave configuration with graceful fallback for live reload
|
|
545
|
+
* Provides error recovery by falling back to previous valid configuration
|
|
546
|
+
*/
|
|
547
|
+
export function loadMergedWaveConfigWithFallback(
|
|
548
|
+
workdir: string,
|
|
549
|
+
previousValidConfig?: WaveConfiguration | null,
|
|
550
|
+
): {
|
|
551
|
+
config: WaveConfiguration | null;
|
|
552
|
+
errors: string[];
|
|
553
|
+
usedFallback: boolean;
|
|
554
|
+
} {
|
|
555
|
+
const errors: string[] = [];
|
|
556
|
+
let usedFallback = false;
|
|
557
|
+
|
|
558
|
+
// Load user config with fallback (check .local.json first, then .json)
|
|
559
|
+
const userResult = loadWaveConfigFromFilesWithFallback(
|
|
560
|
+
getUserHooksConfigPaths(),
|
|
561
|
+
previousValidConfig,
|
|
562
|
+
);
|
|
563
|
+
if (userResult.error) {
|
|
564
|
+
errors.push(`User config: ${userResult.error}`);
|
|
565
|
+
}
|
|
566
|
+
if (userResult.usedFallback) {
|
|
567
|
+
usedFallback = true;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Load project config with fallback (check .local.json first, then .json)
|
|
571
|
+
const projectResult = loadWaveConfigFromFilesWithFallback(
|
|
572
|
+
getProjectHooksConfigPaths(workdir),
|
|
573
|
+
previousValidConfig,
|
|
574
|
+
);
|
|
575
|
+
if (projectResult.error) {
|
|
576
|
+
errors.push(`Project config: ${projectResult.error}`);
|
|
577
|
+
}
|
|
578
|
+
if (projectResult.usedFallback) {
|
|
579
|
+
usedFallback = true;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const userConfig = userResult.config;
|
|
583
|
+
const projectConfig = projectResult.config;
|
|
584
|
+
|
|
585
|
+
// If both configs failed and no fallback available
|
|
586
|
+
if (!userConfig && !projectConfig && errors.length > 0) {
|
|
587
|
+
return {
|
|
588
|
+
config: previousValidConfig || null,
|
|
589
|
+
errors,
|
|
590
|
+
usedFallback: !!previousValidConfig,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// No configuration found at all
|
|
595
|
+
if (!userConfig && !projectConfig) {
|
|
596
|
+
return { config: null, errors, usedFallback };
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Only one configuration found
|
|
600
|
+
if (!userConfig) return { config: projectConfig, errors, usedFallback };
|
|
601
|
+
if (!projectConfig) return { config: userConfig, errors, usedFallback };
|
|
602
|
+
|
|
603
|
+
// Merge configurations (project overrides user)
|
|
604
|
+
try {
|
|
605
|
+
const mergedHooks: PartialHookConfiguration = {};
|
|
606
|
+
|
|
607
|
+
// Merge environment variables using the new mergeEnvironmentConfig function
|
|
608
|
+
const environmentContext = mergeEnvironmentConfig(
|
|
609
|
+
userConfig.env,
|
|
610
|
+
projectConfig.env,
|
|
611
|
+
{ includeConflictWarnings: true },
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
// Merge hooks (combine arrays, project configs come after user configs)
|
|
615
|
+
const allEvents = new Set([
|
|
616
|
+
...Object.keys(userConfig.hooks || {}),
|
|
617
|
+
...Object.keys(projectConfig.hooks || {}),
|
|
618
|
+
]);
|
|
619
|
+
|
|
620
|
+
for (const event of allEvents) {
|
|
621
|
+
if (!isValidHookEvent(event)) continue;
|
|
622
|
+
|
|
623
|
+
const userEventConfigs = userConfig.hooks?.[event] || [];
|
|
624
|
+
const projectEventConfigs = projectConfig.hooks?.[event] || [];
|
|
625
|
+
|
|
626
|
+
// Project configurations take precedence
|
|
627
|
+
mergedHooks[event] = [...userEventConfigs, ...projectEventConfigs];
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const mergedConfig = {
|
|
631
|
+
hooks: Object.keys(mergedHooks).length > 0 ? mergedHooks : undefined,
|
|
632
|
+
env:
|
|
633
|
+
Object.keys(environmentContext.mergedVars).length > 0
|
|
634
|
+
? environmentContext.mergedVars
|
|
635
|
+
: undefined,
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
return { config: mergedConfig, errors, usedFallback };
|
|
639
|
+
} catch (error) {
|
|
640
|
+
errors.push(`Merge error: ${(error as Error).message}`);
|
|
641
|
+
return {
|
|
642
|
+
config: previousValidConfig || null,
|
|
643
|
+
errors,
|
|
644
|
+
usedFallback: !!previousValidConfig,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Load Wave configuration from a JSON file
|
|
651
|
+
* Supports both hooks and environment variables with proper validation
|
|
652
|
+
*/
|
|
653
|
+
export function loadWaveConfigFromFile(
|
|
654
|
+
filePath: string,
|
|
655
|
+
): WaveConfiguration | null {
|
|
263
656
|
if (!existsSync(filePath)) {
|
|
264
657
|
return null;
|
|
265
658
|
}
|
|
266
659
|
|
|
267
|
-
|
|
268
|
-
|
|
660
|
+
try {
|
|
661
|
+
const content = readFileSync(filePath, "utf-8");
|
|
662
|
+
const config = JSON.parse(content) as WaveConfiguration;
|
|
663
|
+
|
|
664
|
+
// Validate basic structure
|
|
665
|
+
if (!config || typeof config !== "object") {
|
|
666
|
+
throw new Error(`Invalid configuration structure in ${filePath}`);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Validate environment variables if present
|
|
670
|
+
if (config.env !== undefined) {
|
|
671
|
+
const envValidation = validateEnvironmentConfig(config.env, filePath);
|
|
672
|
+
|
|
673
|
+
if (!envValidation.isValid) {
|
|
674
|
+
throw new Error(
|
|
675
|
+
`Environment variable validation failed in ${filePath}: ${envValidation.errors.join(", ")}`,
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Log warnings if any
|
|
680
|
+
if (envValidation.warnings.length > 0) {
|
|
681
|
+
console.warn(
|
|
682
|
+
`Environment variable warnings in ${filePath}:\n- ${envValidation.warnings.join("\n- ")}`,
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return {
|
|
688
|
+
hooks: config.hooks || undefined,
|
|
689
|
+
env: config.env || undefined,
|
|
690
|
+
};
|
|
691
|
+
} catch (error) {
|
|
692
|
+
if (error instanceof SyntaxError) {
|
|
693
|
+
throw new Error(`Invalid JSON syntax in ${filePath}: ${error.message}`);
|
|
694
|
+
}
|
|
269
695
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
throw new Error(`Invalid hooks configuration structure in ${filePath}`);
|
|
696
|
+
// Re-throw validation errors and other errors as-is
|
|
697
|
+
throw error;
|
|
273
698
|
}
|
|
699
|
+
}
|
|
274
700
|
|
|
275
|
-
|
|
701
|
+
/**
|
|
702
|
+
* Load hooks configuration from a JSON file (legacy function)
|
|
703
|
+
*/
|
|
704
|
+
export function loadHooksConfigFromFile(
|
|
705
|
+
filePath: string,
|
|
706
|
+
): PartialHookConfiguration | null {
|
|
707
|
+
const waveConfig = loadWaveConfigFromFile(filePath);
|
|
708
|
+
if (!waveConfig) {
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return waveConfig.hooks || null;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Load user-specific Wave configuration
|
|
717
|
+
* Checks .local.json first, then falls back to .json
|
|
718
|
+
*/
|
|
719
|
+
export function loadUserWaveConfig(): WaveConfiguration | null {
|
|
720
|
+
return loadWaveConfigFromFiles(getUserHooksConfigPaths());
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Load project-specific Wave configuration
|
|
725
|
+
* Checks .local.json first, then falls back to .json
|
|
726
|
+
*/
|
|
727
|
+
export function loadProjectWaveConfig(
|
|
728
|
+
workdir: string,
|
|
729
|
+
): WaveConfiguration | null {
|
|
730
|
+
return loadWaveConfigFromFiles(getProjectHooksConfigPaths(workdir));
|
|
276
731
|
}
|
|
277
732
|
|
|
278
733
|
/**
|
|
279
|
-
* Load user-specific hooks configuration
|
|
734
|
+
* Load user-specific hooks configuration (legacy function)
|
|
280
735
|
*/
|
|
281
736
|
export function loadUserHooksConfig(): PartialHookConfiguration | null {
|
|
282
|
-
|
|
737
|
+
const waveConfig = loadUserWaveConfig();
|
|
738
|
+
return waveConfig?.hooks || null;
|
|
283
739
|
}
|
|
284
740
|
|
|
285
741
|
/**
|
|
286
|
-
* Load project-specific hooks configuration
|
|
742
|
+
* Load project-specific hooks configuration (legacy function)
|
|
287
743
|
*/
|
|
288
744
|
export function loadProjectHooksConfig(
|
|
289
745
|
workdir: string,
|
|
290
746
|
): PartialHookConfiguration | null {
|
|
291
|
-
|
|
747
|
+
const waveConfig = loadProjectWaveConfig(workdir);
|
|
748
|
+
return waveConfig?.hooks || null;
|
|
292
749
|
}
|
|
293
750
|
|
|
294
751
|
/**
|
|
295
|
-
* Load and merge
|
|
752
|
+
* Load and merge Wave configuration from both user and project sources
|
|
753
|
+
* Project configuration takes precedence over user configuration
|
|
754
|
+
* Checks .local.json files first, then falls back to .json files
|
|
296
755
|
*/
|
|
297
|
-
export function
|
|
756
|
+
export function loadMergedWaveConfig(
|
|
298
757
|
workdir: string,
|
|
299
|
-
):
|
|
300
|
-
const userConfig =
|
|
301
|
-
const projectConfig =
|
|
758
|
+
): WaveConfiguration | null {
|
|
759
|
+
const userConfig = loadUserWaveConfig();
|
|
760
|
+
const projectConfig = loadProjectWaveConfig(workdir);
|
|
302
761
|
|
|
303
762
|
// No configuration found
|
|
304
763
|
if (!userConfig && !projectConfig) {
|
|
@@ -310,51 +769,83 @@ export function loadMergedHooksConfig(
|
|
|
310
769
|
if (!projectConfig) return userConfig;
|
|
311
770
|
|
|
312
771
|
// Merge configurations (project overrides user)
|
|
313
|
-
const
|
|
772
|
+
const mergedHooks: PartialHookConfiguration = {};
|
|
773
|
+
|
|
774
|
+
// Merge environment variables using the new mergeEnvironmentConfig function
|
|
775
|
+
const environmentContext = mergeEnvironmentConfig(
|
|
776
|
+
userConfig.env,
|
|
777
|
+
projectConfig.env,
|
|
778
|
+
{ includeConflictWarnings: true },
|
|
779
|
+
);
|
|
314
780
|
|
|
315
|
-
//
|
|
781
|
+
// Log environment variable conflicts if any
|
|
782
|
+
if (environmentContext.conflicts.length > 0) {
|
|
783
|
+
console.warn(
|
|
784
|
+
`Environment variable conflicts detected (project values take precedence):\n${environmentContext.conflicts
|
|
785
|
+
.map(
|
|
786
|
+
(conflict) =>
|
|
787
|
+
`- ${conflict.key}: "${conflict.userValue}" → "${conflict.projectValue}"`,
|
|
788
|
+
)
|
|
789
|
+
.join("\n")}`,
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Merge hooks (combine arrays, project configs come after user configs)
|
|
316
794
|
const allEvents = new Set([
|
|
317
|
-
...Object.keys(userConfig),
|
|
318
|
-
...Object.keys(projectConfig),
|
|
795
|
+
...Object.keys(userConfig.hooks || {}),
|
|
796
|
+
...Object.keys(projectConfig.hooks || {}),
|
|
319
797
|
]);
|
|
320
798
|
|
|
321
799
|
for (const event of allEvents) {
|
|
322
800
|
if (!isValidHookEvent(event)) continue;
|
|
323
801
|
|
|
324
|
-
const userEventConfigs = userConfig[event] || [];
|
|
325
|
-
const projectEventConfigs = projectConfig[event] || [];
|
|
802
|
+
const userEventConfigs = userConfig.hooks?.[event] || [];
|
|
803
|
+
const projectEventConfigs = projectConfig.hooks?.[event] || [];
|
|
326
804
|
|
|
327
805
|
// Project configurations take precedence
|
|
328
|
-
|
|
806
|
+
mergedHooks[event] = [...userEventConfigs, ...projectEventConfigs];
|
|
329
807
|
}
|
|
330
808
|
|
|
331
|
-
return
|
|
809
|
+
return {
|
|
810
|
+
hooks: Object.keys(mergedHooks).length > 0 ? mergedHooks : undefined,
|
|
811
|
+
env:
|
|
812
|
+
Object.keys(environmentContext.mergedVars).length > 0
|
|
813
|
+
? environmentContext.mergedVars
|
|
814
|
+
: undefined,
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Load and merge hooks configuration from both user and project sources (legacy function)
|
|
820
|
+
*/
|
|
821
|
+
export function loadMergedHooksConfig(
|
|
822
|
+
workdir: string,
|
|
823
|
+
): PartialHookConfiguration | null {
|
|
824
|
+
const waveConfig = loadMergedWaveConfig(workdir);
|
|
825
|
+
return waveConfig?.hooks || null;
|
|
332
826
|
}
|
|
333
827
|
|
|
334
828
|
/**
|
|
335
829
|
* Check if hooks configuration exists (user or project)
|
|
830
|
+
* Checks both .local.json and .json variants
|
|
831
|
+
* @deprecated Use hasAnyConfig() from configPaths.ts for better functionality
|
|
336
832
|
*/
|
|
337
833
|
export function hasHooksConfiguration(workdir: string): boolean {
|
|
338
|
-
return (
|
|
339
|
-
existsSync(getUserHooksConfigPath()) ||
|
|
340
|
-
existsSync(getProjectHooksConfigPath(workdir))
|
|
341
|
-
);
|
|
834
|
+
return hasAnyConfig(workdir);
|
|
342
835
|
}
|
|
343
836
|
|
|
344
837
|
/**
|
|
345
838
|
* Get hooks configuration information for debugging
|
|
839
|
+
* Includes both .local.json and .json variants
|
|
840
|
+
* @deprecated Use getConfigurationInfo() from configPaths.ts for better functionality
|
|
346
841
|
*/
|
|
347
842
|
export function getHooksConfigurationInfo(workdir: string): {
|
|
348
843
|
hasUser: boolean;
|
|
349
844
|
hasProject: boolean;
|
|
350
845
|
paths: string[];
|
|
846
|
+
userPaths: string[];
|
|
847
|
+
projectPaths: string[];
|
|
848
|
+
existingPaths: string[];
|
|
351
849
|
} {
|
|
352
|
-
|
|
353
|
-
const projectPath = getProjectHooksConfigPath(workdir);
|
|
354
|
-
|
|
355
|
-
return {
|
|
356
|
-
hasUser: existsSync(userPath),
|
|
357
|
-
hasProject: existsSync(projectPath),
|
|
358
|
-
paths: [userPath, projectPath],
|
|
359
|
-
};
|
|
850
|
+
return getConfigurationInfo(workdir);
|
|
360
851
|
}
|