buildhive-agent 1.0.0-beta.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/README.md +166 -0
- package/dist/__tests__/fakes/FakeDockerManager.d.ts +115 -0
- package/dist/__tests__/fakes/FakeDockerManager.d.ts.map +1 -0
- package/dist/__tests__/fakes/FakeDockerManager.js +203 -0
- package/dist/__tests__/fakes/FakeDockerManager.js.map +1 -0
- package/dist/acceptanceChecker.d.ts +26 -0
- package/dist/acceptanceChecker.d.ts.map +1 -0
- package/dist/acceptanceChecker.js +64 -0
- package/dist/acceptanceChecker.js.map +1 -0
- package/dist/advancedAgent.d.ts +161 -0
- package/dist/advancedAgent.d.ts.map +1 -0
- package/dist/advancedAgent.js +604 -0
- package/dist/advancedAgent.js.map +1 -0
- package/dist/agent.d.ts +101 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +490 -0
- package/dist/agent.js.map +1 -0
- package/dist/api/jobStatusApi.d.ts +88 -0
- package/dist/api/jobStatusApi.d.ts.map +1 -0
- package/dist/api/jobStatusApi.js +240 -0
- package/dist/api/jobStatusApi.js.map +1 -0
- package/dist/autoUpdater.d.ts +135 -0
- package/dist/autoUpdater.d.ts.map +1 -0
- package/dist/autoUpdater.js +494 -0
- package/dist/autoUpdater.js.map +1 -0
- package/dist/cacheManager.d.ts +108 -0
- package/dist/cacheManager.d.ts.map +1 -0
- package/dist/cacheManager.js +300 -0
- package/dist/cacheManager.js.map +1 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +749 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/index.d.ts +30 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +35 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +45 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +269 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/types.d.ts +193 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +90 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config/validation.d.ts +28 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/config/validation.js +397 -0
- package/dist/config/validation.js.map +1 -0
- package/dist/docker.d.ts +96 -0
- package/dist/docker.d.ts.map +1 -0
- package/dist/docker.js +411 -0
- package/dist/docker.js.map +1 -0
- package/dist/enhancedJobExecutor.d.ts +81 -0
- package/dist/enhancedJobExecutor.d.ts.map +1 -0
- package/dist/enhancedJobExecutor.js +223 -0
- package/dist/enhancedJobExecutor.js.map +1 -0
- package/dist/executors/executorFactory.d.ts +46 -0
- package/dist/executors/executorFactory.d.ts.map +1 -0
- package/dist/executors/executorFactory.js +80 -0
- package/dist/executors/executorFactory.js.map +1 -0
- package/dist/executors/index.d.ts +7 -0
- package/dist/executors/index.d.ts.map +1 -0
- package/dist/executors/index.js +6 -0
- package/dist/executors/index.js.map +1 -0
- package/dist/executors/nativeExecutor.d.ts +60 -0
- package/dist/executors/nativeExecutor.d.ts.map +1 -0
- package/dist/executors/nativeExecutor.js +311 -0
- package/dist/executors/nativeExecutor.js.map +1 -0
- package/dist/executors/types.d.ts +38 -0
- package/dist/executors/types.d.ts.map +1 -0
- package/dist/executors/types.js +9 -0
- package/dist/executors/types.js.map +1 -0
- package/dist/healthMonitor.d.ts +213 -0
- package/dist/healthMonitor.d.ts.map +1 -0
- package/dist/healthMonitor.js +547 -0
- package/dist/healthMonitor.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/jobExecutor.d.ts +117 -0
- package/dist/jobExecutor.d.ts.map +1 -0
- package/dist/jobExecutor.js +458 -0
- package/dist/jobExecutor.js.map +1 -0
- package/dist/lifecycleExecutor.d.ts +54 -0
- package/dist/lifecycleExecutor.d.ts.map +1 -0
- package/dist/lifecycleExecutor.js +230 -0
- package/dist/lifecycleExecutor.js.map +1 -0
- package/dist/main.d.ts +15 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +77 -0
- package/dist/main.js.map +1 -0
- package/dist/metrics.d.ts +103 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +360 -0
- package/dist/metrics.js.map +1 -0
- package/dist/recipes/builtinRecipes.d.ts +11 -0
- package/dist/recipes/builtinRecipes.d.ts.map +1 -0
- package/dist/recipes/builtinRecipes.js +688 -0
- package/dist/recipes/builtinRecipes.js.map +1 -0
- package/dist/recipes/index.d.ts +18 -0
- package/dist/recipes/index.d.ts.map +1 -0
- package/dist/recipes/index.js +17 -0
- package/dist/recipes/index.js.map +1 -0
- package/dist/recipes/recipeRegistry.d.ts +49 -0
- package/dist/recipes/recipeRegistry.d.ts.map +1 -0
- package/dist/recipes/recipeRegistry.js +264 -0
- package/dist/recipes/recipeRegistry.js.map +1 -0
- package/dist/recipes/types.d.ts +116 -0
- package/dist/recipes/types.d.ts.map +1 -0
- package/dist/recipes/types.js +10 -0
- package/dist/recipes/types.js.map +1 -0
- package/dist/recovery.d.ts +133 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +299 -0
- package/dist/recovery.js.map +1 -0
- package/dist/registration/apiClient.d.ts +44 -0
- package/dist/registration/apiClient.d.ts.map +1 -0
- package/dist/registration/apiClient.js +149 -0
- package/dist/registration/apiClient.js.map +1 -0
- package/dist/registration/index.d.ts +41 -0
- package/dist/registration/index.d.ts.map +1 -0
- package/dist/registration/index.js +141 -0
- package/dist/registration/index.js.map +1 -0
- package/dist/registration/machineId.d.ts +30 -0
- package/dist/registration/machineId.d.ts.map +1 -0
- package/dist/registration/machineId.js +89 -0
- package/dist/registration/machineId.js.map +1 -0
- package/dist/registration/types.d.ts +32 -0
- package/dist/registration/types.d.ts.map +1 -0
- package/dist/registration/types.js +9 -0
- package/dist/registration/types.js.map +1 -0
- package/dist/resourceGovernor.d.ts +57 -0
- package/dist/resourceGovernor.d.ts.map +1 -0
- package/dist/resourceGovernor.js +125 -0
- package/dist/resourceGovernor.js.map +1 -0
- package/dist/security/secretManager.d.ts +107 -0
- package/dist/security/secretManager.d.ts.map +1 -0
- package/dist/security/secretManager.js +361 -0
- package/dist/security/secretManager.js.map +1 -0
- package/dist/security.d.ts +134 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +470 -0
- package/dist/security.js.map +1 -0
- package/dist/storage/artifactUploader.d.ts +155 -0
- package/dist/storage/artifactUploader.d.ts.map +1 -0
- package/dist/storage/artifactUploader.js +554 -0
- package/dist/storage/artifactUploader.js.map +1 -0
- package/dist/types.d.ts +49 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/capabilities.d.ts +23 -0
- package/dist/utils/capabilities.d.ts.map +1 -0
- package/dist/utils/capabilities.js +200 -0
- package/dist/utils/capabilities.js.map +1 -0
- package/dist/utils/logger.d.ts +20 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +188 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/sdkScanner.d.ts +105 -0
- package/dist/utils/sdkScanner.d.ts.map +1 -0
- package/dist/utils/sdkScanner.js +459 -0
- package/dist/utils/sdkScanner.js.map +1 -0
- package/dist/websocketClient.d.ts +154 -0
- package/dist/websocketClient.d.ts.map +1 -0
- package/dist/websocketClient.js +422 -0
- package/dist/websocketClient.js.map +1 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# BuildHive Agent
|
|
2
|
+
|
|
3
|
+
The BuildHive Agent is a lightweight service that runs on developer machines to execute CI/CD build jobs in a secure, isolated environment.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
The agent uses a hierarchical configuration system that loads settings from:
|
|
8
|
+
|
|
9
|
+
1. Default configuration values
|
|
10
|
+
2. Configuration files (JSON format)
|
|
11
|
+
3. Environment variable overrides
|
|
12
|
+
|
|
13
|
+
### Configuration Files
|
|
14
|
+
|
|
15
|
+
The agent searches for configuration files in the following locations (in order):
|
|
16
|
+
|
|
17
|
+
1. `./buildhive-agent.json` (current directory)
|
|
18
|
+
2. `./config/buildhive-agent.json`
|
|
19
|
+
3. `./.config/buildhive-agent.json`
|
|
20
|
+
4. `~/.buildhive/buildhive-agent.json` (user home directory)
|
|
21
|
+
5. `/etc/buildhive/buildhive-agent.json` (system-wide)
|
|
22
|
+
|
|
23
|
+
Alternative file names are also supported:
|
|
24
|
+
- `buildhive-agent.config.json`
|
|
25
|
+
- `.buildhive-agent.json`
|
|
26
|
+
|
|
27
|
+
### Environment Variables
|
|
28
|
+
|
|
29
|
+
All configuration options can be overridden using environment variables:
|
|
30
|
+
|
|
31
|
+
| Environment Variable | Configuration Field | Type | Example |
|
|
32
|
+
|---------------------|---------------------|------|---------|
|
|
33
|
+
| `BUILDHIVE_PLATFORM_URL` | `platformUrl` | string | `https://api.buildhive.dev` |
|
|
34
|
+
| `BUILDHIVE_API_KEY` | `apiKey` | string | `your-api-key` |
|
|
35
|
+
| `BUILDHIVE_AGENT_ID` | `agentId` | string | `agent-123` |
|
|
36
|
+
| `BUILDHIVE_AGENT_NAME` | `name` | string | `My Agent` |
|
|
37
|
+
| `BUILDHIVE_MAX_CONCURRENT_JOBS` | `maxConcurrentJobs` | number | `2` |
|
|
38
|
+
| `BUILDHIVE_HEARTBEAT_INTERVAL` | `heartbeatInterval` | number | `30` |
|
|
39
|
+
| `BUILDHIVE_TAGS` | `tags` | string (comma-separated) | `android,linux` |
|
|
40
|
+
| `BUILDHIVE_LOG_LEVEL` | `logLevel` | string | `info` |
|
|
41
|
+
| `BUILDHIVE_JOB_TIMEOUT_MINUTES` | `jobTimeoutMinutes` | number | `60` |
|
|
42
|
+
| `BUILDHIVE_ENABLE_METRICS` | `enableMetrics` | boolean | `true` |
|
|
43
|
+
| `BUILDHIVE_ENABLE_AUTO_UPDATES` | `enableAutoUpdates` | boolean | `false` |
|
|
44
|
+
|
|
45
|
+
### Configuration Schema
|
|
46
|
+
|
|
47
|
+
#### Required Fields
|
|
48
|
+
|
|
49
|
+
- `platformUrl`: URL of the BuildHive platform API
|
|
50
|
+
- `apiKey`: Authentication key for the agent
|
|
51
|
+
- `agentId`: Unique identifier for this agent
|
|
52
|
+
- `name`: Human-readable name for the agent
|
|
53
|
+
|
|
54
|
+
#### Optional Fields
|
|
55
|
+
|
|
56
|
+
- `maxConcurrentJobs` (default: 1): Maximum number of jobs to run simultaneously
|
|
57
|
+
- `heartbeatInterval` (default: 30): Heartbeat interval in seconds
|
|
58
|
+
- `tags` (default: []): Tags for job matching
|
|
59
|
+
- `logLevel` (default: "info"): Logging level (debug, info, warn, error)
|
|
60
|
+
- `jobTimeoutMinutes` (default: 60): Maximum job execution time
|
|
61
|
+
- `enableMetrics` (default: true): Enable metrics collection
|
|
62
|
+
- `enableAutoUpdates` (default: true): Enable automatic agent updates
|
|
63
|
+
|
|
64
|
+
#### Docker Configuration
|
|
65
|
+
|
|
66
|
+
- `dockerConfig.baseImage` (default: "ubuntu:22.04"): Base Docker image
|
|
67
|
+
- `dockerConfig.networkMode` (default: "none"): Network isolation mode
|
|
68
|
+
- `dockerConfig.memoryLimit` (default: "2g"): Memory limit for containers
|
|
69
|
+
- `dockerConfig.cpuLimit` (default: "1.0"): CPU limit for containers
|
|
70
|
+
- `dockerConfig.volumeMounts` (default: []): Volume mount configurations
|
|
71
|
+
|
|
72
|
+
#### Resource Limits
|
|
73
|
+
|
|
74
|
+
- `maxMemoryUsage` (default: 80): Maximum memory usage percentage
|
|
75
|
+
- `maxCpuUsage` (default: 80): Maximum CPU usage percentage
|
|
76
|
+
- `maxDiskUsage` (default: 90): Maximum disk usage percentage
|
|
77
|
+
|
|
78
|
+
#### Security
|
|
79
|
+
|
|
80
|
+
- `allowedRepositories`: Whitelist of allowed repositories (optional)
|
|
81
|
+
- `blockedRepositories`: Blacklist of blocked repositories (optional)
|
|
82
|
+
|
|
83
|
+
Note: You cannot specify both `allowedRepositories` and `blockedRepositories`.
|
|
84
|
+
|
|
85
|
+
### Example Configuration
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"platformUrl": "https://api.buildhive.dev",
|
|
90
|
+
"apiKey": "your-api-key-here",
|
|
91
|
+
"agentId": "agent-unique-id",
|
|
92
|
+
"name": "My Build Agent",
|
|
93
|
+
"maxConcurrentJobs": 2,
|
|
94
|
+
"heartbeatInterval": 30,
|
|
95
|
+
"tags": ["android", "linux", "docker"],
|
|
96
|
+
"dockerConfig": {
|
|
97
|
+
"baseImage": "ubuntu:22.04",
|
|
98
|
+
"networkMode": "none",
|
|
99
|
+
"memoryLimit": "4g",
|
|
100
|
+
"cpuLimit": "2.0",
|
|
101
|
+
"volumeMounts": [
|
|
102
|
+
{
|
|
103
|
+
"hostPath": "/tmp/buildhive-cache",
|
|
104
|
+
"containerPath": "/cache",
|
|
105
|
+
"readOnly": false
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
"maxMemoryUsage": 80,
|
|
110
|
+
"maxCpuUsage": 75,
|
|
111
|
+
"maxDiskUsage": 90,
|
|
112
|
+
"logLevel": "info",
|
|
113
|
+
"logRetentionDays": 7,
|
|
114
|
+
"allowedRepositories": [
|
|
115
|
+
"myorg/repo1",
|
|
116
|
+
"myorg/repo2"
|
|
117
|
+
],
|
|
118
|
+
"jobTimeoutMinutes": 120,
|
|
119
|
+
"connectionTimeoutSeconds": 30,
|
|
120
|
+
"enableMetrics": true,
|
|
121
|
+
"enableAutoUpdates": true
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Installation
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npm install
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Build the project
|
|
135
|
+
npm run build
|
|
136
|
+
|
|
137
|
+
# Run tests
|
|
138
|
+
npm test
|
|
139
|
+
|
|
140
|
+
# Run tests in watch mode
|
|
141
|
+
npm run test:watch
|
|
142
|
+
|
|
143
|
+
# Run tests with coverage
|
|
144
|
+
npm run test:coverage
|
|
145
|
+
|
|
146
|
+
# Development mode with auto-rebuild
|
|
147
|
+
npm run dev
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Usage
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { loadConfig, saveConfig, validateConfigFile } from '@buildhive/agent';
|
|
154
|
+
|
|
155
|
+
// Load configuration
|
|
156
|
+
const config = await loadConfig();
|
|
157
|
+
|
|
158
|
+
// Save configuration
|
|
159
|
+
await saveConfig(config, './my-config.json');
|
|
160
|
+
|
|
161
|
+
// Validate a configuration file
|
|
162
|
+
const validation = validateConfigFile('./config.json');
|
|
163
|
+
if (!validation.isValid) {
|
|
164
|
+
console.error('Configuration errors:', validation.errors);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FakeDockerManager — in-memory test double for DockerManager.
|
|
3
|
+
*
|
|
4
|
+
* Implements the same public API surface as the real DockerManager
|
|
5
|
+
* (createContainer, startContainer, executeCommand, stopContainer,
|
|
6
|
+
* removeContainer, pullImage, copyFromContainer) without touching
|
|
7
|
+
* the real Docker daemon or the filesystem.
|
|
8
|
+
*
|
|
9
|
+
* Usage in tests:
|
|
10
|
+
*
|
|
11
|
+
* const fake = new FakeDockerManager();
|
|
12
|
+
* fake.setCommandResult('echo hi', { exitCode: 0, output: 'hi' });
|
|
13
|
+
* const id = await fake.createContainer({ image: 'node:20', ... });
|
|
14
|
+
* await fake.startContainer(id);
|
|
15
|
+
* const result = await fake.executeCommand(id, 'echo hi');
|
|
16
|
+
* expect(result.exitCode).toBe(0);
|
|
17
|
+
*/
|
|
18
|
+
import type { ContainerConfig, ExecutionOptions, ExecutionResult } from '../../docker.js';
|
|
19
|
+
type ContainerState = 'created' | 'running' | 'stopped' | 'removed';
|
|
20
|
+
interface ContainerRecord {
|
|
21
|
+
config: ContainerConfig;
|
|
22
|
+
state: ContainerState;
|
|
23
|
+
id: string;
|
|
24
|
+
}
|
|
25
|
+
export interface FakeCommandResult {
|
|
26
|
+
exitCode: number;
|
|
27
|
+
output: string;
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare class FakeDockerManager {
|
|
31
|
+
private containers;
|
|
32
|
+
private pulledImages;
|
|
33
|
+
private commandOverrides;
|
|
34
|
+
private defaultCommandResult;
|
|
35
|
+
private copyLog;
|
|
36
|
+
private idCounter;
|
|
37
|
+
/**
|
|
38
|
+
* Override the result returned when executeCommand is called with the exact
|
|
39
|
+
* command string. Pass undefined to remove the override.
|
|
40
|
+
*/
|
|
41
|
+
setCommandResult(command: string, result: FakeCommandResult | undefined): void;
|
|
42
|
+
/**
|
|
43
|
+
* Set the result returned by executeCommand when no per-command override
|
|
44
|
+
* matches. Defaults to { exitCode: 0, output: '' }.
|
|
45
|
+
*/
|
|
46
|
+
setDefaultCommandResult(result: FakeCommandResult): void;
|
|
47
|
+
/**
|
|
48
|
+
* Reset all internal state (containers, pulled images, overrides, copy log).
|
|
49
|
+
* Call this in beforeEach to ensure test isolation.
|
|
50
|
+
*/
|
|
51
|
+
reset(): void;
|
|
52
|
+
/** Returns true if the container ID is known (not removed). */
|
|
53
|
+
hasContainer(containerId: string): boolean;
|
|
54
|
+
/** Returns the state of a container, or undefined if it does not exist. */
|
|
55
|
+
getContainerState(containerId: string): ContainerState | undefined;
|
|
56
|
+
/** Returns the config used to create a container, or undefined. */
|
|
57
|
+
getContainerConfig(containerId: string): ContainerConfig | undefined;
|
|
58
|
+
/** Returns all containers currently tracked (not yet removed). */
|
|
59
|
+
getActiveContainers(): ReadonlyMap<string, ContainerRecord>;
|
|
60
|
+
/** Returns whether an image has been pulled via pullImage(). */
|
|
61
|
+
isImagePulled(image: string): boolean;
|
|
62
|
+
/** Returns the log of all copyFromContainer calls in call order. */
|
|
63
|
+
getCopyLog(): ReadonlyArray<{
|
|
64
|
+
containerId: string;
|
|
65
|
+
containerPath: string;
|
|
66
|
+
hostPath: string;
|
|
67
|
+
}>;
|
|
68
|
+
/**
|
|
69
|
+
* Resolves immediately. Marks the image as pulled so tests can assert
|
|
70
|
+
* pullImage() was called.
|
|
71
|
+
*/
|
|
72
|
+
pullImage(image: string): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Creates an in-memory container record in the 'created' state and returns
|
|
75
|
+
* its ID. The ID follows the same "buildhive-<n>" pattern as the real
|
|
76
|
+
* implementation (predictable in tests).
|
|
77
|
+
*/
|
|
78
|
+
createContainer(containerConfig: ContainerConfig): Promise<string>;
|
|
79
|
+
/**
|
|
80
|
+
* Transitions the container from 'created' to 'running'.
|
|
81
|
+
* Throws if the container does not exist or is not in 'created' state.
|
|
82
|
+
*/
|
|
83
|
+
startContainer(containerId: string): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Executes a command against a running container.
|
|
86
|
+
*
|
|
87
|
+
* - If a per-command override has been registered via setCommandResult() the
|
|
88
|
+
* override result is returned.
|
|
89
|
+
* - Otherwise the default result (exitCode: 0, output: '') is returned.
|
|
90
|
+
* - The optional onOutput callback is invoked with the output string when
|
|
91
|
+
* output is non-empty, mirroring the streaming behaviour of the real impl.
|
|
92
|
+
* - Throws if the container is not in 'running' state.
|
|
93
|
+
*/
|
|
94
|
+
executeCommand(containerId: string, command: string, options?: ExecutionOptions): Promise<ExecutionResult>;
|
|
95
|
+
/**
|
|
96
|
+
* Transitions the container to 'stopped' state.
|
|
97
|
+
* Silently succeeds if the container is already stopped (matches the real
|
|
98
|
+
* impl's defensive behaviour).
|
|
99
|
+
*/
|
|
100
|
+
stopContainer(containerId: string, _timeout?: number): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Stops the container (if running) and removes it from the internal map.
|
|
103
|
+
* Does NOT throw if the container is not found — mirrors the real impl's
|
|
104
|
+
* "don't throw from cleanup" contract.
|
|
105
|
+
*/
|
|
106
|
+
removeContainer(containerId: string): Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Records the copy operation in the copy log so tests can assert it was
|
|
109
|
+
* called with the correct paths. Throws if the container is not running.
|
|
110
|
+
*/
|
|
111
|
+
copyFromContainer(containerId: string, containerPath: string, hostPath: string): Promise<void>;
|
|
112
|
+
private requireContainer;
|
|
113
|
+
}
|
|
114
|
+
export {};
|
|
115
|
+
//# sourceMappingURL=FakeDockerManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FakeDockerManager.d.ts","sourceRoot":"","sources":["../../../src/__tests__/fakes/FakeDockerManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAM1F,KAAK,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAEpE,UAAU,eAAe;IACvB,MAAM,EAAE,eAAe,CAAC;IACxB,KAAK,EAAE,cAAc,CAAC;IACtB,EAAE,EAAE,MAAM,CAAC;CACZ;AAMD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD,qBAAa,iBAAiB;IAE5B,OAAO,CAAC,UAAU,CAAsC;IAGxD,OAAO,CAAC,YAAY,CAAqB;IAKzC,OAAO,CAAC,gBAAgB,CAAwC;IAGhE,OAAO,CAAC,oBAAoB,CAAkD;IAG9E,OAAO,CAAC,OAAO,CAA+E;IAG9F,OAAO,CAAC,SAAS,CAAK;IAMtB;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,SAAS,GAAG,IAAI;IAQ9E;;;OAGG;IACH,uBAAuB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAIxD;;;OAGG;IACH,KAAK,IAAI,IAAI;IAab,+DAA+D;IAC/D,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAI1C,2EAA2E;IAC3E,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAIlE,mEAAmE;IACnE,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAIpE,kEAAkE;IAClE,mBAAmB,IAAI,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC;IAI3D,gEAAgE;IAChE,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIrC,oEAAoE;IACpE,UAAU,IAAI,aAAa,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAQ7F;;;OAGG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C;;;;OAIG;IACG,eAAe,CAAC,eAAe,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IAUxE;;;OAGG;IACG,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUxD;;;;;;;;;OASG;IACG,cAAc,CAClB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC;IAqB3B;;;;OAIG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO1E;;;;OAIG;IACG,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASzD;;;OAGG;IACG,iBAAiB,CACrB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAchB,OAAO,CAAC,gBAAgB;CAOzB"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FakeDockerManager — in-memory test double for DockerManager.
|
|
3
|
+
*
|
|
4
|
+
* Implements the same public API surface as the real DockerManager
|
|
5
|
+
* (createContainer, startContainer, executeCommand, stopContainer,
|
|
6
|
+
* removeContainer, pullImage, copyFromContainer) without touching
|
|
7
|
+
* the real Docker daemon or the filesystem.
|
|
8
|
+
*
|
|
9
|
+
* Usage in tests:
|
|
10
|
+
*
|
|
11
|
+
* const fake = new FakeDockerManager();
|
|
12
|
+
* fake.setCommandResult('echo hi', { exitCode: 0, output: 'hi' });
|
|
13
|
+
* const id = await fake.createContainer({ image: 'node:20', ... });
|
|
14
|
+
* await fake.startContainer(id);
|
|
15
|
+
* const result = await fake.executeCommand(id, 'echo hi');
|
|
16
|
+
* expect(result.exitCode).toBe(0);
|
|
17
|
+
*/
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// FakeDockerManager
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
export class FakeDockerManager {
|
|
22
|
+
// Map of containerId -> record
|
|
23
|
+
containers = new Map();
|
|
24
|
+
// Set of images that have been pulled
|
|
25
|
+
pulledImages = new Set();
|
|
26
|
+
// Per-command overrides keyed by the command string.
|
|
27
|
+
// When a command matches a key the configured result is returned;
|
|
28
|
+
// otherwise the default result is used.
|
|
29
|
+
commandOverrides = new Map();
|
|
30
|
+
// Default result returned by executeCommand when no override matches.
|
|
31
|
+
defaultCommandResult = { exitCode: 0, output: '' };
|
|
32
|
+
// Track every copy operation for assertion in tests.
|
|
33
|
+
copyLog = [];
|
|
34
|
+
// Counter used to generate deterministic container IDs in tests.
|
|
35
|
+
idCounter = 0;
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Configuration helpers (call these from test setup)
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
/**
|
|
40
|
+
* Override the result returned when executeCommand is called with the exact
|
|
41
|
+
* command string. Pass undefined to remove the override.
|
|
42
|
+
*/
|
|
43
|
+
setCommandResult(command, result) {
|
|
44
|
+
if (result === undefined) {
|
|
45
|
+
this.commandOverrides.delete(command);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
this.commandOverrides.set(command, result);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Set the result returned by executeCommand when no per-command override
|
|
53
|
+
* matches. Defaults to { exitCode: 0, output: '' }.
|
|
54
|
+
*/
|
|
55
|
+
setDefaultCommandResult(result) {
|
|
56
|
+
this.defaultCommandResult = result;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Reset all internal state (containers, pulled images, overrides, copy log).
|
|
60
|
+
* Call this in beforeEach to ensure test isolation.
|
|
61
|
+
*/
|
|
62
|
+
reset() {
|
|
63
|
+
this.containers.clear();
|
|
64
|
+
this.pulledImages.clear();
|
|
65
|
+
this.commandOverrides.clear();
|
|
66
|
+
this.copyLog = [];
|
|
67
|
+
this.idCounter = 0;
|
|
68
|
+
this.defaultCommandResult = { exitCode: 0, output: '' };
|
|
69
|
+
}
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Inspection helpers (for assertions in tests)
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
/** Returns true if the container ID is known (not removed). */
|
|
74
|
+
hasContainer(containerId) {
|
|
75
|
+
return this.containers.has(containerId);
|
|
76
|
+
}
|
|
77
|
+
/** Returns the state of a container, or undefined if it does not exist. */
|
|
78
|
+
getContainerState(containerId) {
|
|
79
|
+
return this.containers.get(containerId)?.state;
|
|
80
|
+
}
|
|
81
|
+
/** Returns the config used to create a container, or undefined. */
|
|
82
|
+
getContainerConfig(containerId) {
|
|
83
|
+
return this.containers.get(containerId)?.config;
|
|
84
|
+
}
|
|
85
|
+
/** Returns all containers currently tracked (not yet removed). */
|
|
86
|
+
getActiveContainers() {
|
|
87
|
+
return this.containers;
|
|
88
|
+
}
|
|
89
|
+
/** Returns whether an image has been pulled via pullImage(). */
|
|
90
|
+
isImagePulled(image) {
|
|
91
|
+
return this.pulledImages.has(image);
|
|
92
|
+
}
|
|
93
|
+
/** Returns the log of all copyFromContainer calls in call order. */
|
|
94
|
+
getCopyLog() {
|
|
95
|
+
return this.copyLog;
|
|
96
|
+
}
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Public API — mirrors DockerManager method signatures exactly
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
/**
|
|
101
|
+
* Resolves immediately. Marks the image as pulled so tests can assert
|
|
102
|
+
* pullImage() was called.
|
|
103
|
+
*/
|
|
104
|
+
async pullImage(image) {
|
|
105
|
+
this.pulledImages.add(image);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Creates an in-memory container record in the 'created' state and returns
|
|
109
|
+
* its ID. The ID follows the same "buildhive-<n>" pattern as the real
|
|
110
|
+
* implementation (predictable in tests).
|
|
111
|
+
*/
|
|
112
|
+
async createContainer(containerConfig) {
|
|
113
|
+
const containerId = `buildhive-fake-${++this.idCounter}`;
|
|
114
|
+
this.containers.set(containerId, {
|
|
115
|
+
id: containerId,
|
|
116
|
+
config: containerConfig,
|
|
117
|
+
state: 'created',
|
|
118
|
+
});
|
|
119
|
+
return containerId;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Transitions the container from 'created' to 'running'.
|
|
123
|
+
* Throws if the container does not exist or is not in 'created' state.
|
|
124
|
+
*/
|
|
125
|
+
async startContainer(containerId) {
|
|
126
|
+
const record = this.requireContainer(containerId);
|
|
127
|
+
if (record.state !== 'created') {
|
|
128
|
+
throw new Error(`FakeDockerManager: cannot start container ${containerId} — current state is '${record.state}'`);
|
|
129
|
+
}
|
|
130
|
+
record.state = 'running';
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Executes a command against a running container.
|
|
134
|
+
*
|
|
135
|
+
* - If a per-command override has been registered via setCommandResult() the
|
|
136
|
+
* override result is returned.
|
|
137
|
+
* - Otherwise the default result (exitCode: 0, output: '') is returned.
|
|
138
|
+
* - The optional onOutput callback is invoked with the output string when
|
|
139
|
+
* output is non-empty, mirroring the streaming behaviour of the real impl.
|
|
140
|
+
* - Throws if the container is not in 'running' state.
|
|
141
|
+
*/
|
|
142
|
+
async executeCommand(containerId, command, options = {}) {
|
|
143
|
+
const record = this.requireContainer(containerId);
|
|
144
|
+
if (record.state !== 'running') {
|
|
145
|
+
throw new Error(`FakeDockerManager: cannot execute command in container ${containerId} — current state is '${record.state}'`);
|
|
146
|
+
}
|
|
147
|
+
const override = this.commandOverrides.get(command);
|
|
148
|
+
const result = override
|
|
149
|
+
? { ...override }
|
|
150
|
+
: { ...this.defaultCommandResult };
|
|
151
|
+
// Mirror the real impl: call onOutput if there is output to stream.
|
|
152
|
+
if (options.onOutput && result.output) {
|
|
153
|
+
options.onOutput(result.output);
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Transitions the container to 'stopped' state.
|
|
159
|
+
* Silently succeeds if the container is already stopped (matches the real
|
|
160
|
+
* impl's defensive behaviour).
|
|
161
|
+
*/
|
|
162
|
+
async stopContainer(containerId, _timeout) {
|
|
163
|
+
const record = this.requireContainer(containerId);
|
|
164
|
+
if (record.state !== 'removed') {
|
|
165
|
+
record.state = 'stopped';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Stops the container (if running) and removes it from the internal map.
|
|
170
|
+
* Does NOT throw if the container is not found — mirrors the real impl's
|
|
171
|
+
* "don't throw from cleanup" contract.
|
|
172
|
+
*/
|
|
173
|
+
async removeContainer(containerId) {
|
|
174
|
+
const record = this.containers.get(containerId);
|
|
175
|
+
if (!record) {
|
|
176
|
+
return; // already removed — no-op
|
|
177
|
+
}
|
|
178
|
+
record.state = 'removed';
|
|
179
|
+
this.containers.delete(containerId);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Records the copy operation in the copy log so tests can assert it was
|
|
183
|
+
* called with the correct paths. Throws if the container is not running.
|
|
184
|
+
*/
|
|
185
|
+
async copyFromContainer(containerId, containerPath, hostPath) {
|
|
186
|
+
const record = this.requireContainer(containerId);
|
|
187
|
+
if (record.state !== 'running') {
|
|
188
|
+
throw new Error(`FakeDockerManager: cannot copy from container ${containerId} — current state is '${record.state}'`);
|
|
189
|
+
}
|
|
190
|
+
this.copyLog.push({ containerId, containerPath, hostPath });
|
|
191
|
+
}
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Private helpers
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
requireContainer(containerId) {
|
|
196
|
+
const record = this.containers.get(containerId);
|
|
197
|
+
if (!record) {
|
|
198
|
+
throw new Error(`FakeDockerManager: container not found: ${containerId}`);
|
|
199
|
+
}
|
|
200
|
+
return record;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=FakeDockerManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FakeDockerManager.js","sourceRoot":"","sources":["../../../src/__tests__/fakes/FakeDockerManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acceptance Checker
|
|
3
|
+
*
|
|
4
|
+
* Evaluates whether a job should be accepted based on configured
|
|
5
|
+
* acceptance rules (recipe patterns, repository patterns, duration, Docker).
|
|
6
|
+
*/
|
|
7
|
+
import { AcceptanceRules } from './config/types.js';
|
|
8
|
+
export interface JobCandidate {
|
|
9
|
+
recipe?: string;
|
|
10
|
+
repository?: string;
|
|
11
|
+
estimatedDurationMinutes?: number;
|
|
12
|
+
requiresDocker?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface AcceptanceDecision {
|
|
15
|
+
accepted: boolean;
|
|
16
|
+
reason?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class AcceptanceChecker {
|
|
19
|
+
private rules;
|
|
20
|
+
constructor(rules: AcceptanceRules);
|
|
21
|
+
/**
|
|
22
|
+
* Check if a job should be accepted based on the configured acceptance rules.
|
|
23
|
+
*/
|
|
24
|
+
shouldAccept(job: JobCandidate): AcceptanceDecision;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=acceptanceChecker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acceptanceChecker.d.ts","sourceRoot":"","sources":["../src/acceptanceChecker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoBD,qBAAa,iBAAiB;IAChB,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,eAAe;IAE1C;;OAEG;IACH,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,kBAAkB;CAqCpD"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acceptance Checker
|
|
3
|
+
*
|
|
4
|
+
* Evaluates whether a job should be accepted based on configured
|
|
5
|
+
* acceptance rules (recipe patterns, repository patterns, duration, Docker).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Simple glob matcher supporting * and ? wildcards.
|
|
9
|
+
* Avoids external dependency on minimatch.
|
|
10
|
+
*/
|
|
11
|
+
function globMatch(pattern, value) {
|
|
12
|
+
// Convert glob to regex
|
|
13
|
+
const escaped = pattern
|
|
14
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex special chars (except * and ?)
|
|
15
|
+
.replace(/\*/g, '.*')
|
|
16
|
+
.replace(/\?/g, '.');
|
|
17
|
+
const regex = new RegExp(`^${escaped}$`, 'i');
|
|
18
|
+
return regex.test(value);
|
|
19
|
+
}
|
|
20
|
+
function matchesAny(patterns, value) {
|
|
21
|
+
return patterns.some(p => globMatch(p, value));
|
|
22
|
+
}
|
|
23
|
+
export class AcceptanceChecker {
|
|
24
|
+
rules;
|
|
25
|
+
constructor(rules) {
|
|
26
|
+
this.rules = rules;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if a job should be accepted based on the configured acceptance rules.
|
|
30
|
+
*/
|
|
31
|
+
shouldAccept(job) {
|
|
32
|
+
// Check blocked recipes first (takes precedence)
|
|
33
|
+
if (job.recipe && this.rules.blockedRecipes.length > 0) {
|
|
34
|
+
if (matchesAny(this.rules.blockedRecipes, job.recipe)) {
|
|
35
|
+
return { accepted: false, reason: `Recipe "${job.recipe}" is blocked` };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Check allowed recipes
|
|
39
|
+
if (job.recipe && this.rules.allowedRecipes.length > 0) {
|
|
40
|
+
if (!matchesAny(this.rules.allowedRecipes, job.recipe)) {
|
|
41
|
+
return { accepted: false, reason: `Recipe "${job.recipe}" is not in allowed list` };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Check allowed repositories
|
|
45
|
+
if (job.repository && this.rules.allowedRepositories.length > 0) {
|
|
46
|
+
if (!matchesAny(this.rules.allowedRepositories, job.repository)) {
|
|
47
|
+
return { accepted: false, reason: `Repository "${job.repository}" is not in allowed list` };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Check max duration
|
|
51
|
+
if (job.estimatedDurationMinutes !== undefined && job.estimatedDurationMinutes > this.rules.maxJobDurationMinutes) {
|
|
52
|
+
return {
|
|
53
|
+
accepted: false,
|
|
54
|
+
reason: `Estimated duration (${job.estimatedDurationMinutes}m) exceeds max (${this.rules.maxJobDurationMinutes}m)`
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// Check Docker requirement
|
|
58
|
+
if (this.rules.requireDocker && job.requiresDocker === false) {
|
|
59
|
+
return { accepted: false, reason: 'Agent requires Docker but job does not use Docker' };
|
|
60
|
+
}
|
|
61
|
+
return { accepted: true };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=acceptanceChecker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acceptanceChecker.js","sourceRoot":"","sources":["../src/acceptanceChecker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgBH;;;GAGG;AACH,SAAS,SAAS,CAAC,OAAe,EAAE,KAAa;IAC/C,wBAAwB;IACxB,MAAM,OAAO,GAAG,OAAO;SACpB,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC,8CAA8C;SACnF,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC;SACpB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACvB,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,IAAI,OAAO,GAAG,EAAE,GAAG,CAAC,CAAC;IAC9C,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,UAAU,CAAC,QAAkB,EAAE,KAAa;IACnD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,OAAO,iBAAiB;IACR;IAApB,YAAoB,KAAsB;QAAtB,UAAK,GAAL,KAAK,CAAiB;IAAG,CAAC;IAE9C;;OAEG;IACH,YAAY,CAAC,GAAiB;QAC5B,iDAAiD;QACjD,IAAI,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvD,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBACtD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,GAAG,CAAC,MAAM,cAAc,EAAE,CAAC;YAC1E,CAAC;QACH,CAAC;QAED,wBAAwB;QACxB,IAAI,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBACvD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,GAAG,CAAC,MAAM,0BAA0B,EAAE,CAAC;YACtF,CAAC;QACH,CAAC;QAED,6BAA6B;QAC7B,IAAI,GAAG,CAAC,UAAU,IAAI,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,mBAAmB,EAAE,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;gBAChE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,GAAG,CAAC,UAAU,0BAA0B,EAAE,CAAC;YAC9F,CAAC;QACH,CAAC;QAED,qBAAqB;QACrB,IAAI,GAAG,CAAC,wBAAwB,KAAK,SAAS,IAAI,GAAG,CAAC,wBAAwB,GAAG,IAAI,CAAC,KAAK,CAAC,qBAAqB,EAAE,CAAC;YAClH,OAAO;gBACL,QAAQ,EAAE,KAAK;gBACf,MAAM,EAAE,uBAAuB,GAAG,CAAC,wBAAwB,mBAAmB,IAAI,CAAC,KAAK,CAAC,qBAAqB,IAAI;aACnH,CAAC;QACJ,CAAC;QAED,2BAA2B;QAC3B,IAAI,IAAI,CAAC,KAAK,CAAC,aAAa,IAAI,GAAG,CAAC,cAAc,KAAK,KAAK,EAAE,CAAC;YAC7D,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,mDAAmD,EAAE,CAAC;QAC1F,CAAC;QAED,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC5B,CAAC;CACF"}
|