agentgui 1.0.388 → 1.0.389
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/package.json +1 -1
- package/server.js +50 -53
- package/test-cancel.mjs +0 -185
- package/verify-cancel-impl.mjs +0 -119
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -1447,6 +1447,56 @@ const server = http.createServer(async (req, res) => {
|
|
|
1447
1447
|
return;
|
|
1448
1448
|
}
|
|
1449
1449
|
|
|
1450
|
+
// POST /runs/stream - Create stateless run and stream output (MUST be before generic /runs/:id route)
|
|
1451
|
+
if (pathOnly === '/api/runs/stream' && req.method === 'POST') {
|
|
1452
|
+
const body = await parseBody(req);
|
|
1453
|
+
const { agent_id, input, config } = body;
|
|
1454
|
+
if (!agent_id) {
|
|
1455
|
+
sendJSON(req, res, 422, { error: 'agent_id is required' });
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
const agent = discoveredAgents.find(a => a.id === agent_id);
|
|
1459
|
+
if (!agent) {
|
|
1460
|
+
sendJSON(req, res, 404, { error: 'Agent not found' });
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
const run = queries.createRun(agent_id, null, input, config);
|
|
1464
|
+
const sseManager = new SSEStreamManager(res, run.run_id);
|
|
1465
|
+
sseManager.start();
|
|
1466
|
+
sseManager.sendProgress({ type: 'run_created', run_id: run.run_id });
|
|
1467
|
+
|
|
1468
|
+
const eventHandler = (eventData) => {
|
|
1469
|
+
if (eventData.sessionId === run.run_id || eventData.conversationId === run.thread_id) {
|
|
1470
|
+
if (eventData.type === 'streaming_progress' && eventData.block) {
|
|
1471
|
+
sseManager.sendProgress(eventData.block);
|
|
1472
|
+
} else if (eventData.type === 'streaming_error') {
|
|
1473
|
+
sseManager.sendError(eventData.error || 'Execution error');
|
|
1474
|
+
} else if (eventData.type === 'streaming_complete') {
|
|
1475
|
+
sseManager.sendComplete({ eventCount: eventData.eventCount }, { timestamp: eventData.timestamp });
|
|
1476
|
+
sseManager.cleanup();
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
};
|
|
1480
|
+
|
|
1481
|
+
sseStreamHandlers.set(run.run_id, eventHandler);
|
|
1482
|
+
req.on('close', () => {
|
|
1483
|
+
sseStreamHandlers.delete(run.run_id);
|
|
1484
|
+
sseManager.cleanup();
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
const statelessThreadId = queries.getRun(run.run_id)?.thread_id;
|
|
1488
|
+
if (statelessThreadId) {
|
|
1489
|
+
const conv = queries.getConversation(statelessThreadId);
|
|
1490
|
+
if (conv && input?.content) {
|
|
1491
|
+
runClaudeWithStreaming(agent_id, statelessThreadId, input.content, config?.model || null).catch((err) => {
|
|
1492
|
+
sseManager.sendError(err.message);
|
|
1493
|
+
sseManager.cleanup();
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1450
1500
|
const oldRunByIdMatch = pathOnly.match(/^\/api\/runs\/([^/]+)$/);
|
|
1451
1501
|
if (oldRunByIdMatch) {
|
|
1452
1502
|
const runId = oldRunByIdMatch[1];
|
|
@@ -1918,59 +1968,6 @@ const server = http.createServer(async (req, res) => {
|
|
|
1918
1968
|
return;
|
|
1919
1969
|
}
|
|
1920
1970
|
|
|
1921
|
-
if (pathOnly === '/api/runs/stream' && req.method === 'POST') {
|
|
1922
|
-
const body = await parseBody(req);
|
|
1923
|
-
const { agent_id, input, config } = body;
|
|
1924
|
-
if (!agent_id) {
|
|
1925
|
-
sendJSON(req, res, 422, { error: 'agent_id is required' });
|
|
1926
|
-
return;
|
|
1927
|
-
}
|
|
1928
|
-
const agent = discoveredAgents.find(a => a.id === agent_id);
|
|
1929
|
-
if (!agent) {
|
|
1930
|
-
sendJSON(req, res, 404, { error: 'Agent not found' });
|
|
1931
|
-
return;
|
|
1932
|
-
}
|
|
1933
|
-
const run = queries.createRun(agent_id, null, input, config);
|
|
1934
|
-
const sseManager = new SSEStreamManager(res, run.run_id);
|
|
1935
|
-
sseManager.start();
|
|
1936
|
-
sseManager.sendProgress({ type: 'run_created', run_id: run.run_id });
|
|
1937
|
-
|
|
1938
|
-
const eventHandler = (eventData) => {
|
|
1939
|
-
if (eventData.sessionId === run.run_id || eventData.conversationId === run.thread_id) {
|
|
1940
|
-
if (eventData.type === 'streaming_progress' && eventData.block) {
|
|
1941
|
-
sseManager.sendProgress(eventData.block);
|
|
1942
|
-
} else if (eventData.type === 'streaming_error') {
|
|
1943
|
-
sseManager.sendError(eventData.error || 'Execution error');
|
|
1944
|
-
} else if (eventData.type === 'streaming_complete') {
|
|
1945
|
-
sseManager.sendComplete({ eventCount: eventData.eventCount }, { timestamp: eventData.timestamp });
|
|
1946
|
-
sseManager.cleanup();
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
};
|
|
1950
|
-
|
|
1951
|
-
sseStreamHandlers.set(run.run_id, eventHandler);
|
|
1952
|
-
req.on('close', () => {
|
|
1953
|
-
sseStreamHandlers.delete(run.run_id);
|
|
1954
|
-
sseManager.cleanup();
|
|
1955
|
-
});
|
|
1956
|
-
|
|
1957
|
-
const statelessThreadId = queries.getRun(run.run_id)?.thread_id;
|
|
1958
|
-
if (statelessThreadId) {
|
|
1959
|
-
const conv = queries.getConversation(statelessThreadId);
|
|
1960
|
-
if (conv && input?.content) {
|
|
1961
|
-
const session = queries.createSession(statelessThreadId);
|
|
1962
|
-
acpQueries.updateRunStatus(run.run_id, 'active');
|
|
1963
|
-
activeExecutions.set(statelessThreadId, { pid: null, startTime: Date.now(), sessionId: session.id, lastActivity: Date.now() });
|
|
1964
|
-
activeProcessesByRunId.set(run.run_id, { threadId: statelessThreadId, sessionId: session.id });
|
|
1965
|
-
queries.setIsStreaming(statelessThreadId, true);
|
|
1966
|
-
processMessageWithStreaming(statelessThreadId, null, session.id, input.content, agent_id, config?.model || null)
|
|
1967
|
-
.then(() => { acpQueries.updateRunStatus(run.run_id, 'success'); activeProcessesByRunId.delete(run.run_id); })
|
|
1968
|
-
.catch((err) => { acpQueries.updateRunStatus(run.run_id, 'error'); activeProcessesByRunId.delete(run.run_id); sseManager.sendError(err.message); sseManager.cleanup(); });
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
return;
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
1971
|
if (pathOnly === '/api/runs/wait' && req.method === 'POST') {
|
|
1975
1972
|
const body = await parseBody(req);
|
|
1976
1973
|
const { agent_id, input, config } = body;
|
package/test-cancel.mjs
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
// Integration test for run cancellation and control
|
|
2
|
-
import http from 'http';
|
|
3
|
-
import { randomUUID } from 'crypto';
|
|
4
|
-
import Database from 'better-sqlite3';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import os from 'os';
|
|
7
|
-
import { createACPQueries } from './acp-queries.js';
|
|
8
|
-
|
|
9
|
-
const dbPath = path.join(os.homedir(), '.gmgui', 'data.db');
|
|
10
|
-
const db = new Database(dbPath);
|
|
11
|
-
const prep = (sql) => db.prepare(sql);
|
|
12
|
-
const acpQueries = createACPQueries(db, prep);
|
|
13
|
-
|
|
14
|
-
const BASE_URL = 'http://localhost:3000/gm';
|
|
15
|
-
const testResults = {
|
|
16
|
-
passed: [],
|
|
17
|
-
failed: []
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
function testPass(name) {
|
|
21
|
-
testResults.passed.push(name);
|
|
22
|
-
console.log(`✓ ${name}`);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function testFail(name, error) {
|
|
26
|
-
testResults.failed.push({ name, error });
|
|
27
|
-
console.log(`✗ ${name}: ${error}`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function makeRequest(method, path, body = null) {
|
|
31
|
-
return new Promise((resolve, reject) => {
|
|
32
|
-
const fullPath = `/gm${path}`;
|
|
33
|
-
const options = {
|
|
34
|
-
method,
|
|
35
|
-
hostname: 'localhost',
|
|
36
|
-
port: 3000,
|
|
37
|
-
path: fullPath,
|
|
38
|
-
headers: {
|
|
39
|
-
'Content-Type': 'application/json'
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const req = http.request(options, (res) => {
|
|
44
|
-
let data = '';
|
|
45
|
-
res.on('data', chunk => data += chunk);
|
|
46
|
-
res.on('end', () => {
|
|
47
|
-
try {
|
|
48
|
-
const parsed = data ? JSON.parse(data) : null;
|
|
49
|
-
resolve({ status: res.statusCode, data: parsed, headers: res.headers });
|
|
50
|
-
} catch {
|
|
51
|
-
resolve({ status: res.statusCode, data: data, headers: res.headers });
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
req.on('error', reject);
|
|
57
|
-
if (body) req.write(JSON.stringify(body));
|
|
58
|
-
req.end();
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function runTests() {
|
|
63
|
-
console.log('=== RUNNING INTEGRATION TESTS ===\n');
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
// Test 1: Create a thread
|
|
67
|
-
console.log('[Test 1] Creating thread...');
|
|
68
|
-
const threadResp = await makeRequest('POST', '/api/threads', {});
|
|
69
|
-
if ((threadResp.status === 200 || threadResp.status === 201) && threadResp.data.thread_id) {
|
|
70
|
-
testPass('Thread creation');
|
|
71
|
-
} else {
|
|
72
|
-
testFail('Thread creation', `Status ${threadResp.status}`);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const threadId = threadResp.data.thread_id;
|
|
77
|
-
|
|
78
|
-
// Test 2: Create a run (stateless, without thread)
|
|
79
|
-
console.log('[Test 2] Creating stateless run...');
|
|
80
|
-
const runResp = await makeRequest('POST', '/api/runs', {
|
|
81
|
-
agent_id: 'claude-code',
|
|
82
|
-
input: 'test input'
|
|
83
|
-
});
|
|
84
|
-
if (runResp.status === 200 && runResp.data.run_id) {
|
|
85
|
-
testPass('Stateless run creation');
|
|
86
|
-
} else {
|
|
87
|
-
testFail('Stateless run creation', `Status ${runResp.status}`);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const runId = runResp.data.run_id;
|
|
92
|
-
|
|
93
|
-
// Test 3: Verify run status is pending
|
|
94
|
-
console.log('[Test 3] Verifying run status...');
|
|
95
|
-
const run = acpQueries.getRun(runId);
|
|
96
|
-
if (run && run.status === 'pending') {
|
|
97
|
-
testPass('Run status is pending');
|
|
98
|
-
} else {
|
|
99
|
-
testFail('Run status is pending', `Status is ${run?.status}`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Test 4: Cancel the run using /api/runs/{run_id}/cancel
|
|
103
|
-
console.log('[Test 4] Cancelling run via /api/runs/{run_id}/cancel...');
|
|
104
|
-
const cancelResp = await makeRequest('POST', `/api/runs/${runId}/cancel`);
|
|
105
|
-
if (cancelResp.status === 200 && cancelResp.data.status === 'cancelled') {
|
|
106
|
-
testPass('Run cancellation via /api/runs');
|
|
107
|
-
} else {
|
|
108
|
-
testFail('Run cancellation via /api/runs', `Status ${cancelResp.status}, run status ${cancelResp.data?.status}`);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Test 5: Verify run status is cancelled in database
|
|
112
|
-
console.log('[Test 5] Verifying cancelled status in DB...');
|
|
113
|
-
const cancelledRun = acpQueries.getRun(runId);
|
|
114
|
-
if (cancelledRun && cancelledRun.status === 'cancelled') {
|
|
115
|
-
testPass('Cancelled status persisted in database');
|
|
116
|
-
} else {
|
|
117
|
-
testFail('Cancelled status persisted in database', `Status is ${cancelledRun?.status}`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Test 6: Try to cancel again - should get 409 conflict
|
|
121
|
-
console.log('[Test 6] Testing 409 conflict on re-cancel...');
|
|
122
|
-
const recancel = await makeRequest('POST', `/api/runs/${runId}/cancel`);
|
|
123
|
-
if (recancel.status === 409) {
|
|
124
|
-
testPass('409 conflict on already-cancelled run');
|
|
125
|
-
} else {
|
|
126
|
-
testFail('409 conflict on already-cancelled run', `Got status ${recancel.status}`);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Test 7: Test wait endpoint with already-completed run
|
|
130
|
-
console.log('[Test 7] Testing wait endpoint with completed run...');
|
|
131
|
-
const waitStart = Date.now();
|
|
132
|
-
const waitResp = await makeRequest('GET', `/api/runs/${runId}/wait`);
|
|
133
|
-
const waitDuration = Date.now() - waitStart;
|
|
134
|
-
if (waitResp.status === 200 && waitDuration < 5000) {
|
|
135
|
-
testPass('Wait endpoint returns immediately for completed run');
|
|
136
|
-
} else {
|
|
137
|
-
testFail('Wait endpoint returns immediately for completed run', `Took ${waitDuration}ms`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Test 8: Test cancellation of non-existent run
|
|
141
|
-
console.log('[Test 8] Testing 404 on non-existent run...');
|
|
142
|
-
const fakeRunId = randomUUID();
|
|
143
|
-
const notFound = await makeRequest('POST', `/api/runs/${fakeRunId}/cancel`);
|
|
144
|
-
if (notFound.status === 404) {
|
|
145
|
-
testPass('404 on non-existent run');
|
|
146
|
-
} else {
|
|
147
|
-
testFail('404 on non-existent run', `Got status ${notFound.status}`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Cleanup
|
|
151
|
-
console.log('\n[Cleanup] Deleting test thread...');
|
|
152
|
-
try {
|
|
153
|
-
acpQueries.deleteThread(threadId);
|
|
154
|
-
console.log('Cleanup complete');
|
|
155
|
-
} catch (e) {
|
|
156
|
-
console.log('Cleanup warning:', e.message);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
} catch (error) {
|
|
160
|
-
console.error('Test suite error:', error);
|
|
161
|
-
testFail('Test suite execution', error.message);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
db.close();
|
|
165
|
-
|
|
166
|
-
// Summary
|
|
167
|
-
console.log('\n=== TEST SUMMARY ===');
|
|
168
|
-
console.log(`Passed: ${testResults.passed.length}`);
|
|
169
|
-
console.log(`Failed: ${testResults.failed.length}`);
|
|
170
|
-
if (testResults.failed.length > 0) {
|
|
171
|
-
console.log('\nFailed tests:');
|
|
172
|
-
testResults.failed.forEach(f => console.log(` - ${f.name}: ${f.error}`));
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return testResults.passed.length > 0 && testResults.failed.length === 0;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Run the tests
|
|
179
|
-
runTests().then(success => {
|
|
180
|
-
console.log(`\n${success ? '✓ ALL TESTS PASSED' : '✗ SOME TESTS FAILED'}`);
|
|
181
|
-
process.exit(success ? 0 : 1);
|
|
182
|
-
}).catch(err => {
|
|
183
|
-
console.error('Fatal test error:', err);
|
|
184
|
-
process.exit(1);
|
|
185
|
-
});
|
package/verify-cancel-impl.mjs
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
// Verify run cancellation implementation without needing running server
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
|
|
4
|
-
const serverContent = fs.readFileSync('/config/workspace/agentgui/server.js', 'utf-8');
|
|
5
|
-
|
|
6
|
-
console.log('=== VERIFYING RUN CANCELLATION IMPLEMENTATION ===\n');
|
|
7
|
-
|
|
8
|
-
const checks = [
|
|
9
|
-
{
|
|
10
|
-
name: 'Enhanced /api/runs/{run_id}/cancel endpoint',
|
|
11
|
-
test: () => {
|
|
12
|
-
const hasGetRun = serverContent.includes('acpQueries.getRun(runId)') || serverContent.includes('queries.getRun(runId)');
|
|
13
|
-
const hasStatusCheck = serverContent.includes("['success', 'error', 'cancelled'].includes");
|
|
14
|
-
const has409 = serverContent.includes('sendJSON(req, res, 409');
|
|
15
|
-
return hasGetRun && hasStatusCheck && has409;
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
name: 'Process termination with SIGTERM then SIGKILL',
|
|
20
|
-
test: () => {
|
|
21
|
-
const hasSigterm = serverContent.includes("process.kill(-execution.pid, 'SIGTERM')") ||
|
|
22
|
-
serverContent.includes("process.kill(execution.pid, 'SIGTERM')");
|
|
23
|
-
const hasSigkill = serverContent.includes("'SIGKILL'");
|
|
24
|
-
const hasTimeout = serverContent.includes('setTimeout') && serverContent.includes('3000');
|
|
25
|
-
return hasSigterm && hasSigkill && hasTimeout;
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
name: 'WebSocket broadcast on cancellation',
|
|
30
|
-
test: () => {
|
|
31
|
-
return serverContent.includes("type: 'streaming_cancelled'") &&
|
|
32
|
-
serverContent.includes('broadcastSync');
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
name: 'Active executions cleanup',
|
|
37
|
-
test: () => {
|
|
38
|
-
return serverContent.includes('activeExecutions.delete(threadId)') &&
|
|
39
|
-
serverContent.includes('queries.setIsStreaming(threadId, false)');
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
name: 'Thread-based cancel endpoint /api/threads/{thread_id}/runs/{run_id}/cancel',
|
|
44
|
-
test: () => {
|
|
45
|
-
return serverContent.includes('threadRunCancelMatch') &&
|
|
46
|
-
serverContent.includes('/api/threads/([^/]+)/runs/([^/]+)/cancel');
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
name: 'Thread-based wait endpoint /api/threads/{thread_id}/runs/{run_id}/wait',
|
|
51
|
-
test: () => {
|
|
52
|
-
return serverContent.includes('threadRunWaitMatch') &&
|
|
53
|
-
serverContent.includes('/api/threads/([^/]+)/runs/([^/]+)/wait');
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
name: 'Wait endpoint long-polling (30s timeout, 500ms poll)',
|
|
58
|
-
test: () => {
|
|
59
|
-
const hasWait = serverContent.includes('/wait') && serverContent.includes('GET');
|
|
60
|
-
const hasPoll = serverContent.includes('setInterval') && serverContent.includes('500');
|
|
61
|
-
const hasTimeout = serverContent.includes('30000');
|
|
62
|
-
return hasWait && hasPoll && hasTimeout;
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: 'Thread validation in thread-based endpoints',
|
|
67
|
-
test: () => {
|
|
68
|
-
return serverContent.includes('run.thread_id !== threadId') &&
|
|
69
|
-
serverContent.includes('Run does not belong to specified thread');
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: 'Session status update on cancellation',
|
|
74
|
-
test: () => {
|
|
75
|
-
return serverContent.includes("status: 'error'") &&
|
|
76
|
-
serverContent.includes("error: 'Cancelled by user'");
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
name: 'Database run status update to cancelled',
|
|
81
|
-
test: () => {
|
|
82
|
-
const hasCancelRun = serverContent.includes('cancelRun(runId)') ||
|
|
83
|
-
serverContent.includes('cancelledRun');
|
|
84
|
-
const hasUpdateStatus = serverContent.includes('updateRunStatus');
|
|
85
|
-
return hasCancelRun || hasUpdateStatus;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
];
|
|
89
|
-
|
|
90
|
-
let passed = 0;
|
|
91
|
-
let failed = 0;
|
|
92
|
-
|
|
93
|
-
checks.forEach(check => {
|
|
94
|
-
try {
|
|
95
|
-
const result = check.test();
|
|
96
|
-
if (result) {
|
|
97
|
-
console.log(`✓ ${check.name}`);
|
|
98
|
-
passed++;
|
|
99
|
-
} else {
|
|
100
|
-
console.log(`✗ ${check.name}`);
|
|
101
|
-
failed++;
|
|
102
|
-
}
|
|
103
|
-
} catch (e) {
|
|
104
|
-
console.log(`✗ ${check.name} (error: ${e.message})`);
|
|
105
|
-
failed++;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
console.log(`\n=== SUMMARY ===`);
|
|
110
|
-
console.log(`Passed: ${passed}/${checks.length}`);
|
|
111
|
-
console.log(`Failed: ${failed}/${checks.length}`);
|
|
112
|
-
|
|
113
|
-
if (passed === checks.length) {
|
|
114
|
-
console.log('\n✓ ALL IMPLEMENTATION CHECKS PASSED');
|
|
115
|
-
process.exit(0);
|
|
116
|
-
} else {
|
|
117
|
-
console.log('\n✗ SOME IMPLEMENTATION CHECKS FAILED');
|
|
118
|
-
process.exit(1);
|
|
119
|
-
}
|