@amodalai/amodal 0.3.27 → 0.3.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/dist/src/commands/dev.d.ts.map +1 -1
- package/dist/src/commands/dev.js +28 -11
- package/dist/src/commands/dev.js.map +1 -1
- package/dist/src/commands/eval.d.ts.map +1 -1
- package/dist/src/commands/eval.js +4 -2
- package/dist/src/commands/eval.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/src/commands/dev.ts +31 -11
- package/src/commands/eval.ts +4 -2
- package/src/e2e-commands.test.ts +9 -291
- package/src/e2e-subprocess.test.ts +153 -0
- package/dist/src/fixtures/incident-response.d.ts +0 -92
- package/dist/src/fixtures/incident-response.d.ts.map +0 -1
- package/dist/src/fixtures/incident-response.js +0 -209
- package/dist/src/fixtures/incident-response.js.map +0 -1
- package/dist/src/shared/find-free-port.d.ts +0 -21
- package/dist/src/shared/find-free-port.d.ts.map +0 -1
- package/dist/src/shared/find-free-port.js +0 -62
- package/dist/src/shared/find-free-port.js.map +0 -1
- package/src/e2e-automations.test.ts +0 -305
- package/src/e2e-incident-response.test.ts +0 -345
- package/src/e2e-plugin-connections.test.ts +0 -407
- package/src/e2e-plugins.test.ts +0 -491
- package/src/e2e.test.ts +0 -493
- package/src/fixtures/incident-response.ts +0 -233
- package/src/shared/find-free-port.ts +0 -67
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Amodal Labs, Inc.
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Fixture data for the incident response e2e test.
|
|
8
|
-
*
|
|
9
|
-
* Four content types:
|
|
10
|
-
* 1. Connection: statuspage API (mock)
|
|
11
|
-
* 2. Skill: incident triage methodology
|
|
12
|
-
* 3. Knowledge: oncall runbook
|
|
13
|
-
* 4. Automation: daily health check
|
|
14
|
-
*
|
|
15
|
-
* The mock API returns deterministic data so we can assert on the
|
|
16
|
-
* agent's response content.
|
|
17
|
-
*/
|
|
18
|
-
import http from 'node:http';
|
|
19
|
-
export declare const CONFIG: {
|
|
20
|
-
name: string;
|
|
21
|
-
version: string;
|
|
22
|
-
description: string;
|
|
23
|
-
models: {
|
|
24
|
-
main: {
|
|
25
|
-
provider: string;
|
|
26
|
-
model: string;
|
|
27
|
-
};
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
export declare const STATUSPAGE_SPEC: {
|
|
31
|
-
baseUrl: string;
|
|
32
|
-
specUrl: string;
|
|
33
|
-
format: "openapi";
|
|
34
|
-
auth: {
|
|
35
|
-
type: string;
|
|
36
|
-
header: string;
|
|
37
|
-
prefix: string;
|
|
38
|
-
token: string;
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
export declare const STATUSPAGE_ACCESS: {
|
|
42
|
-
endpoints: {
|
|
43
|
-
'GET /components': {
|
|
44
|
-
returns: string[];
|
|
45
|
-
};
|
|
46
|
-
'GET /incidents': {
|
|
47
|
-
returns: string[];
|
|
48
|
-
};
|
|
49
|
-
'GET /incidents/:id': {
|
|
50
|
-
returns: string[];
|
|
51
|
-
};
|
|
52
|
-
'POST /incidents': {
|
|
53
|
-
returns: string[];
|
|
54
|
-
confirm: "review";
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
export declare const STATUSPAGE_SURFACE = "## Included\n\n### GET /components\nList all monitored components and their current status\n\n### GET /incidents\nList recent incidents\n\n### GET /incidents/:id\nGet incident details\n\n## Excluded\n\n### POST /incidents\nCreate a new incident (requires confirmation)\n";
|
|
59
|
-
export declare const TRIAGE_SKILL = "---\nname: incident-triage\ndescription: Methodology for triaging service incidents based on component status\ntrigger: When the user asks about service health, incidents, or outages\n---\n\n## Incident Triage\n\nFollow this methodology when assessing service health:\n\n1. **Check component status** \u2014 Query GET /components to see current state of all services\n2. **Identify degraded components** \u2014 Any component not in \"operational\" status needs attention\n3. **Assess severity** \u2014 Use the severity matrix from the oncall runbook\n4. **Check recent incidents** \u2014 Query GET /incidents for correlated issues\n5. **Recommend action** \u2014 Based on severity, recommend the appropriate response from the runbook\n\nAlways report the exact component names and their statuses. Never fabricate status data.\n";
|
|
60
|
-
export declare const ONCALL_RUNBOOK = "# On-Call Runbook\n\n## Severity Matrix\n\n| Level | Criteria | Response Time | Escalation |\n|-------|----------|---------------|------------|\n| SEV1 | Multiple components down | 5 min | Page on-call lead immediately |\n| SEV2 | Single component degraded | 15 min | Notify #incidents channel |\n| SEV3 | Performance degradation | 1 hour | Create ticket |\n| SEV4 | Cosmetic or minor | Next business day | Log for review |\n\n## Key Contacts\n\n- **On-call lead**: Alice (alice@example.com)\n- **Platform team**: Bob (bob@example.com)\n- **Database team**: Charlie (charlie@example.com)\n\n## Components\n\n- **api-gateway**: Main API entry point. SEV1 if down.\n- **auth-service**: Authentication. SEV1 if down.\n- **database-primary**: Primary Postgres. SEV1 if down.\n- **worker-pool**: Background jobs. SEV2 if degraded.\n- **cdn**: Static assets. SEV3 if degraded.\n";
|
|
61
|
-
export declare const HEALTH_CHECK_AUTOMATION = "# Automation: Daily Health Check\n\nSchedule: 0 8 * * *\n\n## Check\nQuery the statuspage API for current component status. Report any components not in \"operational\" state.\n\n## Output\nSummary of component statuses with severity assessment per the oncall runbook.\n\n## Delivery\nPost to #ops-daily channel.\n";
|
|
62
|
-
/** Deterministic mock data the API returns. */
|
|
63
|
-
export declare const MOCK_COMPONENTS: {
|
|
64
|
-
id: string;
|
|
65
|
-
name: string;
|
|
66
|
-
status: string;
|
|
67
|
-
updated_at: string;
|
|
68
|
-
}[];
|
|
69
|
-
export declare const MOCK_INCIDENTS: {
|
|
70
|
-
id: string;
|
|
71
|
-
name: string;
|
|
72
|
-
status: string;
|
|
73
|
-
impact: string;
|
|
74
|
-
created_at: string;
|
|
75
|
-
body: string;
|
|
76
|
-
components: string[];
|
|
77
|
-
}[];
|
|
78
|
-
/**
|
|
79
|
-
* Create a mock StatusPage API server that returns deterministic data.
|
|
80
|
-
* Returns a handle with start/stop methods.
|
|
81
|
-
*/
|
|
82
|
-
export declare function createMockStatusPageApi(): {
|
|
83
|
-
server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
|
|
84
|
-
requests: {
|
|
85
|
-
method: string;
|
|
86
|
-
url: string;
|
|
87
|
-
}[];
|
|
88
|
-
readonly port: number;
|
|
89
|
-
start: () => Promise<number>;
|
|
90
|
-
stop: () => Promise<void>;
|
|
91
|
-
};
|
|
92
|
-
//# sourceMappingURL=incident-response.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"incident-response.d.ts","sourceRoot":"","sources":["../../../src/fixtures/incident-response.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;;GAWG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAM7B,eAAO,MAAM,MAAM;;;;;;;;;;CAOlB,CAAC;AAMF,eAAO,MAAM,eAAe;;;;;;;;;;CAU3B,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;CAO7B,CAAC;AAEF,eAAO,MAAM,kBAAkB,mRAe9B,CAAC;AAMF,eAAO,MAAM,YAAY,g0BAiBxB,CAAC;AAMF,eAAO,MAAM,cAAc,62BAwB1B,CAAC;AAMF,eAAO,MAAM,uBAAuB,+TAYnC,CAAC;AAMF,+CAA+C;AAC/C,eAAO,MAAM,eAAe;;;;;GAM3B,CAAC;AAEF,eAAO,MAAM,cAAc;;;;;;;;GAU1B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,uBAAuB;;;gBACN,MAAM;aAAO,MAAM;;;;;EAuDnD"}
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Amodal Labs, Inc.
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Fixture data for the incident response e2e test.
|
|
8
|
-
*
|
|
9
|
-
* Four content types:
|
|
10
|
-
* 1. Connection: statuspage API (mock)
|
|
11
|
-
* 2. Skill: incident triage methodology
|
|
12
|
-
* 3. Knowledge: oncall runbook
|
|
13
|
-
* 4. Automation: daily health check
|
|
14
|
-
*
|
|
15
|
-
* The mock API returns deterministic data so we can assert on the
|
|
16
|
-
* agent's response content.
|
|
17
|
-
*/
|
|
18
|
-
import http from 'node:http';
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
// amodal.json
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
export const CONFIG = {
|
|
23
|
-
name: 'incident-response-agent',
|
|
24
|
-
version: '1.0.0',
|
|
25
|
-
description: 'Monitors services and triages incidents',
|
|
26
|
-
models: {
|
|
27
|
-
main: { provider: 'anthropic', model: 'claude-sonnet-4-20250514' },
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
// Connection: statuspage
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
export const STATUSPAGE_SPEC = {
|
|
34
|
-
baseUrl: 'https://statuspage.example.com/api/v1',
|
|
35
|
-
specUrl: 'https://statuspage.example.com/api/v1/openapi.json',
|
|
36
|
-
format: 'openapi',
|
|
37
|
-
auth: {
|
|
38
|
-
type: 'bearer',
|
|
39
|
-
header: 'Authorization',
|
|
40
|
-
prefix: 'Bearer',
|
|
41
|
-
token: 'env:STATUSPAGE_TOKEN',
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
export const STATUSPAGE_ACCESS = {
|
|
45
|
-
endpoints: {
|
|
46
|
-
'GET /components': { returns: ['id', 'name', 'status', 'updated_at'] },
|
|
47
|
-
'GET /incidents': { returns: ['id', 'name', 'status', 'impact', 'created_at'] },
|
|
48
|
-
'GET /incidents/:id': { returns: ['id', 'name', 'status', 'impact', 'body', 'components'] },
|
|
49
|
-
'POST /incidents': { returns: ['id'], confirm: 'review' },
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
export const STATUSPAGE_SURFACE = `## Included
|
|
53
|
-
|
|
54
|
-
### GET /components
|
|
55
|
-
List all monitored components and their current status
|
|
56
|
-
|
|
57
|
-
### GET /incidents
|
|
58
|
-
List recent incidents
|
|
59
|
-
|
|
60
|
-
### GET /incidents/:id
|
|
61
|
-
Get incident details
|
|
62
|
-
|
|
63
|
-
## Excluded
|
|
64
|
-
|
|
65
|
-
### POST /incidents
|
|
66
|
-
Create a new incident (requires confirmation)
|
|
67
|
-
`;
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
// Skill: incident-triage
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
export const TRIAGE_SKILL = `---
|
|
72
|
-
name: incident-triage
|
|
73
|
-
description: Methodology for triaging service incidents based on component status
|
|
74
|
-
trigger: When the user asks about service health, incidents, or outages
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
## Incident Triage
|
|
78
|
-
|
|
79
|
-
Follow this methodology when assessing service health:
|
|
80
|
-
|
|
81
|
-
1. **Check component status** — Query GET /components to see current state of all services
|
|
82
|
-
2. **Identify degraded components** — Any component not in "operational" status needs attention
|
|
83
|
-
3. **Assess severity** — Use the severity matrix from the oncall runbook
|
|
84
|
-
4. **Check recent incidents** — Query GET /incidents for correlated issues
|
|
85
|
-
5. **Recommend action** — Based on severity, recommend the appropriate response from the runbook
|
|
86
|
-
|
|
87
|
-
Always report the exact component names and their statuses. Never fabricate status data.
|
|
88
|
-
`;
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
// Knowledge: oncall-runbook
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
export const ONCALL_RUNBOOK = `# On-Call Runbook
|
|
93
|
-
|
|
94
|
-
## Severity Matrix
|
|
95
|
-
|
|
96
|
-
| Level | Criteria | Response Time | Escalation |
|
|
97
|
-
|-------|----------|---------------|------------|
|
|
98
|
-
| SEV1 | Multiple components down | 5 min | Page on-call lead immediately |
|
|
99
|
-
| SEV2 | Single component degraded | 15 min | Notify #incidents channel |
|
|
100
|
-
| SEV3 | Performance degradation | 1 hour | Create ticket |
|
|
101
|
-
| SEV4 | Cosmetic or minor | Next business day | Log for review |
|
|
102
|
-
|
|
103
|
-
## Key Contacts
|
|
104
|
-
|
|
105
|
-
- **On-call lead**: Alice (alice@example.com)
|
|
106
|
-
- **Platform team**: Bob (bob@example.com)
|
|
107
|
-
- **Database team**: Charlie (charlie@example.com)
|
|
108
|
-
|
|
109
|
-
## Components
|
|
110
|
-
|
|
111
|
-
- **api-gateway**: Main API entry point. SEV1 if down.
|
|
112
|
-
- **auth-service**: Authentication. SEV1 if down.
|
|
113
|
-
- **database-primary**: Primary Postgres. SEV1 if down.
|
|
114
|
-
- **worker-pool**: Background jobs. SEV2 if degraded.
|
|
115
|
-
- **cdn**: Static assets. SEV3 if degraded.
|
|
116
|
-
`;
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
// Automation: health-check
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
export const HEALTH_CHECK_AUTOMATION = `# Automation: Daily Health Check
|
|
121
|
-
|
|
122
|
-
Schedule: 0 8 * * *
|
|
123
|
-
|
|
124
|
-
## Check
|
|
125
|
-
Query the statuspage API for current component status. Report any components not in "operational" state.
|
|
126
|
-
|
|
127
|
-
## Output
|
|
128
|
-
Summary of component statuses with severity assessment per the oncall runbook.
|
|
129
|
-
|
|
130
|
-
## Delivery
|
|
131
|
-
Post to #ops-daily channel.
|
|
132
|
-
`;
|
|
133
|
-
// ---------------------------------------------------------------------------
|
|
134
|
-
// Mock StatusPage API
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
|
-
/** Deterministic mock data the API returns. */
|
|
137
|
-
export const MOCK_COMPONENTS = [
|
|
138
|
-
{ id: 'comp-1', name: 'api-gateway', status: 'operational', updated_at: '2026-03-18T08:00:00Z' },
|
|
139
|
-
{ id: 'comp-2', name: 'auth-service', status: 'operational', updated_at: '2026-03-18T08:00:00Z' },
|
|
140
|
-
{ id: 'comp-3', name: 'database-primary', status: 'degraded_performance', updated_at: '2026-03-18T09:15:00Z' },
|
|
141
|
-
{ id: 'comp-4', name: 'worker-pool', status: 'operational', updated_at: '2026-03-18T08:00:00Z' },
|
|
142
|
-
{ id: 'comp-5', name: 'cdn', status: 'operational', updated_at: '2026-03-18T08:00:00Z' },
|
|
143
|
-
];
|
|
144
|
-
export const MOCK_INCIDENTS = [
|
|
145
|
-
{
|
|
146
|
-
id: 'inc-42',
|
|
147
|
-
name: 'Database latency spike',
|
|
148
|
-
status: 'investigating',
|
|
149
|
-
impact: 'minor',
|
|
150
|
-
created_at: '2026-03-18T09:10:00Z',
|
|
151
|
-
body: 'We are investigating elevated query latency on the primary database cluster.',
|
|
152
|
-
components: ['database-primary'],
|
|
153
|
-
},
|
|
154
|
-
];
|
|
155
|
-
/**
|
|
156
|
-
* Create a mock StatusPage API server that returns deterministic data.
|
|
157
|
-
* Returns a handle with start/stop methods.
|
|
158
|
-
*/
|
|
159
|
-
export function createMockStatusPageApi() {
|
|
160
|
-
const requests = [];
|
|
161
|
-
const server = http.createServer((req, res) => {
|
|
162
|
-
const url = req.url ?? '';
|
|
163
|
-
const method = req.method ?? '';
|
|
164
|
-
requests.push({ method, url });
|
|
165
|
-
const json = (data) => {
|
|
166
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
167
|
-
res.end(JSON.stringify(data));
|
|
168
|
-
};
|
|
169
|
-
if (method === 'GET' && url === '/components') {
|
|
170
|
-
json(MOCK_COMPONENTS);
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (method === 'GET' && url === '/incidents') {
|
|
174
|
-
json(MOCK_INCIDENTS);
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
const incidentMatch = /^\/incidents\/([^/]+)$/.exec(url);
|
|
178
|
-
if (method === 'GET' && incidentMatch) {
|
|
179
|
-
const incident = MOCK_INCIDENTS.find((i) => i.id === incidentMatch[1]);
|
|
180
|
-
if (incident) {
|
|
181
|
-
json(incident);
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
res.writeHead(404);
|
|
185
|
-
res.end(JSON.stringify({ error: 'Not found' }));
|
|
186
|
-
}
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
res.writeHead(404);
|
|
190
|
-
res.end(JSON.stringify({ error: 'Not found' }));
|
|
191
|
-
});
|
|
192
|
-
let port = 0;
|
|
193
|
-
return {
|
|
194
|
-
server,
|
|
195
|
-
requests,
|
|
196
|
-
get port() { return port; },
|
|
197
|
-
start: () => new Promise((resolve) => {
|
|
198
|
-
server.listen(0, '127.0.0.1', () => {
|
|
199
|
-
const addr = server.address();
|
|
200
|
-
port = typeof addr === 'object' && addr ? addr.port : 0;
|
|
201
|
-
resolve(port);
|
|
202
|
-
});
|
|
203
|
-
}),
|
|
204
|
-
stop: () => new Promise((resolve, reject) => {
|
|
205
|
-
server.close((err) => (err ? reject(err) : resolve()));
|
|
206
|
-
}),
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
//# sourceMappingURL=incident-response.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"incident-response.js","sourceRoot":"","sources":["../../../src/fixtures/incident-response.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Find a free TCP port, starting from `preferred`. If `preferred` is taken,
|
|
8
|
-
* tries incrementally higher ports up to `preferred + maxAttempts`.
|
|
9
|
-
*
|
|
10
|
-
* Uses the "bind then close" technique: creates a TCP server on the candidate
|
|
11
|
-
* port, reads the actual assigned port, then closes the server. This avoids
|
|
12
|
-
* TOCTOU races better than simply checking if a port is open — the OS reserves
|
|
13
|
-
* the port for the brief lifetime of the server.
|
|
14
|
-
*/
|
|
15
|
-
export declare function findFreePort(preferred: number, maxAttempts?: number): Promise<number>;
|
|
16
|
-
export declare class PortAllocationError extends Error {
|
|
17
|
-
readonly preferred: number;
|
|
18
|
-
readonly maxAttempts: number;
|
|
19
|
-
constructor(preferred: number, maxAttempts: number);
|
|
20
|
-
}
|
|
21
|
-
//# sourceMappingURL=find-free-port.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"find-free-port.d.ts","sourceRoot":"","sources":["../../../src/shared/find-free-port.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,SAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAUvF;AA0BD,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEjB,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CASnD"}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/
|
|
6
|
-
import { createServer } from 'node:net';
|
|
7
|
-
/**
|
|
8
|
-
* Find a free TCP port, starting from `preferred`. If `preferred` is taken,
|
|
9
|
-
* tries incrementally higher ports up to `preferred + maxAttempts`.
|
|
10
|
-
*
|
|
11
|
-
* Uses the "bind then close" technique: creates a TCP server on the candidate
|
|
12
|
-
* port, reads the actual assigned port, then closes the server. This avoids
|
|
13
|
-
* TOCTOU races better than simply checking if a port is open — the OS reserves
|
|
14
|
-
* the port for the brief lifetime of the server.
|
|
15
|
-
*/
|
|
16
|
-
export async function findFreePort(preferred, maxAttempts = 10) {
|
|
17
|
-
for (let offset = 0; offset < maxAttempts; offset++) {
|
|
18
|
-
const candidate = preferred + offset;
|
|
19
|
-
const port = await tryPort(candidate);
|
|
20
|
-
if (port !== null)
|
|
21
|
-
return port;
|
|
22
|
-
}
|
|
23
|
-
// Fallback: let the OS pick any available port
|
|
24
|
-
const port = await tryPort(0);
|
|
25
|
-
if (port !== null)
|
|
26
|
-
return port;
|
|
27
|
-
throw new PortAllocationError(preferred, maxAttempts);
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Attempt to bind a TCP server to the given port. Returns the port number on
|
|
31
|
-
* success, or null if the port is in use.
|
|
32
|
-
*/
|
|
33
|
-
function tryPort(port) {
|
|
34
|
-
return new Promise((resolve) => {
|
|
35
|
-
const server = createServer();
|
|
36
|
-
server.once('error', () => {
|
|
37
|
-
resolve(null);
|
|
38
|
-
});
|
|
39
|
-
server.listen(port, '127.0.0.1', () => {
|
|
40
|
-
const addr = server.address();
|
|
41
|
-
const assignedPort = typeof addr === 'object' && addr !== null ? addr.port : null;
|
|
42
|
-
server.close(() => {
|
|
43
|
-
resolve(assignedPort);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
// Error
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
export class PortAllocationError extends Error {
|
|
52
|
-
preferred;
|
|
53
|
-
maxAttempts;
|
|
54
|
-
constructor(preferred, maxAttempts) {
|
|
55
|
-
super(`Failed to find a free port starting from ${String(preferred)} ` +
|
|
56
|
-
`after ${String(maxAttempts)} attempts`);
|
|
57
|
-
this.name = 'PortAllocationError';
|
|
58
|
-
this.preferred = preferred;
|
|
59
|
-
this.maxAttempts = maxAttempts;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
//# sourceMappingURL=find-free-port.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"find-free-port.js","sourceRoot":"","sources":["../../../src/shared/find-free-port.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAC,YAAY,EAAC,MAAM,UAAU,CAAC;AAEtC;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,SAAiB,EAAE,WAAW,GAAG,EAAE;IACpE,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC;QACpD,MAAM,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;QACrC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;IACjC,CAAC;IACD,+CAA+C;IAC/C,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,IAAI,mBAAmB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;AACxD,CAAC;AAED;;;GAGG;AACH,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;YACxB,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE;YACpC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9B,MAAM,YAAY,GAAG,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;YAClF,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;gBAChB,OAAO,CAAC,YAAY,CAAC,CAAC;YACxB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,QAAQ;AACR,8EAA8E;AAE9E,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IACnC,SAAS,CAAS;IAClB,WAAW,CAAS;IAE7B,YAAY,SAAiB,EAAE,WAAmB;QAChD,KAAK,CACH,4CAA4C,MAAM,CAAC,SAAS,CAAC,GAAG;YAChE,SAAS,MAAM,CAAC,WAAW,CAAC,WAAW,CACxC,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;CACF"}
|