creevey 0.10.0-beta.44 → 0.10.0-beta.45
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/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/master/api.d.ts +5 -10
- package/dist/server/master/api.js +19 -18
- package/dist/server/master/api.js.map +1 -1
- package/dist/server/master/handlers/capture-handler.d.ts +5 -2
- package/dist/server/master/handlers/capture-handler.js +6 -16
- package/dist/server/master/handlers/capture-handler.js.map +1 -1
- package/dist/server/master/handlers/ping-handler.d.ts +2 -2
- package/dist/server/master/handlers/ping-handler.js +2 -1
- package/dist/server/master/handlers/ping-handler.js.map +1 -1
- package/dist/server/master/handlers/static-handler.d.ts +1 -2
- package/dist/server/master/handlers/static-handler.js +10 -20
- package/dist/server/master/handlers/static-handler.js.map +1 -1
- package/dist/server/master/handlers/stories-handler.d.ts +4 -2
- package/dist/server/master/handlers/stories-handler.js +13 -27
- package/dist/server/master/handlers/stories-handler.js.map +1 -1
- package/dist/server/master/server.js +182 -70
- package/dist/server/master/server.js.map +1 -1
- package/dist/server/playwright/docker-file.js +2 -2
- package/dist/server/playwright/docker-file.js.map +1 -1
- package/package.json +3 -3
- package/src/server/index.ts +2 -2
- package/src/server/master/api.ts +24 -27
- package/src/server/master/handlers/capture-handler.ts +5 -24
- package/src/server/master/handlers/ping-handler.ts +4 -3
- package/src/server/master/handlers/static-handler.ts +10 -21
- package/src/server/master/handlers/stories-handler.ts +12 -40
- package/src/server/master/server.ts +194 -78
- package/src/server/playwright/docker-file.ts +2 -2
@@ -4,101 +4,213 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
6
|
exports.start = start;
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
8
|
+
const url_1 = __importDefault(require("url"));
|
7
9
|
const path_1 = __importDefault(require("path"));
|
8
|
-
const
|
9
|
-
const
|
10
|
+
const http_1 = require("http");
|
11
|
+
const ws_1 = require("ws");
|
12
|
+
const url_2 = require("url");
|
13
|
+
const utils_js_1 = require("../utils.js");
|
10
14
|
const messages_js_1 = require("../messages.js");
|
11
15
|
const types_js_1 = require("../../types.js");
|
12
16
|
const logger_js_1 = require("../logger.js");
|
13
17
|
const index_js_1 = require("./handlers/index.js");
|
14
|
-
|
18
|
+
function json(handler, defaultValue) {
|
19
|
+
return (request, response) => {
|
20
|
+
const chunks = [];
|
21
|
+
request.on('data', (chunk) => {
|
22
|
+
chunks.push(chunk);
|
23
|
+
});
|
24
|
+
request.on('end', () => {
|
25
|
+
try {
|
26
|
+
const body = Buffer.concat(chunks);
|
27
|
+
const value = body.length === 0 ? defaultValue : JSON.parse(body.toString('utf-8'));
|
28
|
+
handler(value);
|
29
|
+
response.end();
|
30
|
+
}
|
31
|
+
catch (error) {
|
32
|
+
(0, logger_js_1.logger)().error('Failed to parse JSON', error);
|
33
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
34
|
+
response.statusCode = 500;
|
35
|
+
response.setHeader('Content-Type', 'text/plain');
|
36
|
+
response.end(`Failed to parse JSON: ${errorMessage}`);
|
37
|
+
}
|
38
|
+
});
|
39
|
+
request.on('error', (error) => {
|
40
|
+
(0, logger_js_1.logger)().error('Failed to parse JSON', error);
|
41
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
42
|
+
response.statusCode = 500;
|
43
|
+
response.setHeader('Content-Type', 'text/plain');
|
44
|
+
response.end(`Failed to parse JSON: ${errorMessage}`);
|
45
|
+
});
|
46
|
+
};
|
47
|
+
}
|
48
|
+
function file(handler) {
|
49
|
+
return (request, response) => {
|
50
|
+
const parsedUrl = url_1.default.parse(request.url ?? '/', true);
|
51
|
+
const requestedPath = parsedUrl.pathname ?? '/';
|
52
|
+
try {
|
53
|
+
const filePath = handler(requestedPath);
|
54
|
+
if (filePath) {
|
55
|
+
const stat = fs_1.default.statSync(filePath);
|
56
|
+
// Set appropriate MIME type
|
57
|
+
const ext = path_1.default.extname(filePath).toLowerCase();
|
58
|
+
const mimeTypes = {
|
59
|
+
'.html': 'text/html',
|
60
|
+
'.js': 'application/javascript',
|
61
|
+
'.css': 'text/css',
|
62
|
+
'.json': 'application/json',
|
63
|
+
'.png': 'image/png',
|
64
|
+
'.jpg': 'image/jpeg',
|
65
|
+
'.jpeg': 'image/jpeg',
|
66
|
+
'.gif': 'image/gif',
|
67
|
+
'.svg': 'image/svg+xml',
|
68
|
+
'.ico': 'image/x-icon',
|
69
|
+
};
|
70
|
+
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
71
|
+
response.statusCode = 200;
|
72
|
+
response.setHeader('Content-Type', contentType);
|
73
|
+
response.setHeader('Content-Length', stat.size);
|
74
|
+
// Stream the file
|
75
|
+
const stream = fs_1.default.createReadStream(filePath);
|
76
|
+
stream.pipe(response);
|
77
|
+
stream.on('error', (error) => {
|
78
|
+
(0, logger_js_1.logger)().error('Error streaming file', error);
|
79
|
+
if (!response.headersSent) {
|
80
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
81
|
+
response.statusCode = 500;
|
82
|
+
response.setHeader('Content-Type', 'text/plain');
|
83
|
+
response.end(`Internal server error: ${errorMessage}`);
|
84
|
+
}
|
85
|
+
});
|
86
|
+
}
|
87
|
+
else {
|
88
|
+
(0, logger_js_1.logger)().error('File not found', requestedPath);
|
89
|
+
response.statusCode = 404;
|
90
|
+
response.setHeader('Content-Type', 'text/plain');
|
91
|
+
response.end('File not found');
|
92
|
+
}
|
93
|
+
}
|
94
|
+
catch (error) {
|
95
|
+
(0, logger_js_1.logger)().error('Failed to serve file', error);
|
96
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
97
|
+
response.statusCode = 500;
|
98
|
+
response.setHeader('Content-Type', 'text/plain');
|
99
|
+
response.end(`Failed to serve file: ${errorMessage}`);
|
100
|
+
}
|
101
|
+
};
|
102
|
+
}
|
103
|
+
const importMetaUrl = (0, url_2.pathToFileURL)(__filename).href;
|
15
104
|
function start(reportDir, port, ui) {
|
105
|
+
let wss = null;
|
106
|
+
let creeveyApi = null;
|
16
107
|
let resolveApi = types_js_1.noop;
|
17
|
-
const
|
18
|
-
|
19
|
-
const
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
108
|
+
const webDir = path_1.default.join(path_1.default.dirname((0, url_2.fileURLToPath)(importMetaUrl)), '../../client/web');
|
109
|
+
const server = (0, http_1.createServer)();
|
110
|
+
const routes = [
|
111
|
+
{
|
112
|
+
path: '/ping',
|
113
|
+
method: 'GET',
|
114
|
+
handler: index_js_1.pingHandler,
|
115
|
+
},
|
116
|
+
{
|
117
|
+
path: '/stories',
|
118
|
+
method: 'POST',
|
119
|
+
handler: json(index_js_1.storiesHandler, { stories: [] }),
|
120
|
+
},
|
121
|
+
{
|
122
|
+
path: '/capture',
|
123
|
+
method: 'POST',
|
124
|
+
handler: json(index_js_1.captureHandler, { workerId: 0, options: undefined }),
|
125
|
+
},
|
126
|
+
{
|
127
|
+
path: '/report/',
|
128
|
+
method: 'GET',
|
129
|
+
handler: file((0, index_js_1.staticHandler)(reportDir, '/report/')),
|
130
|
+
},
|
131
|
+
{
|
132
|
+
path: '/',
|
133
|
+
method: 'GET',
|
134
|
+
handler: file((0, index_js_1.staticHandler)(webDir)),
|
135
|
+
},
|
136
|
+
];
|
137
|
+
const router = (request, response) => {
|
138
|
+
const parsedUrl = url_1.default.parse(request.url ?? '/', true);
|
139
|
+
const path = parsedUrl.pathname ?? '/';
|
140
|
+
const method = request.method ?? 'GET';
|
141
|
+
try {
|
142
|
+
const route = routes.find((route) => path.startsWith(route.path) && route.method === method);
|
143
|
+
if (route) {
|
144
|
+
route.handler(request, response);
|
145
|
+
}
|
146
|
+
else {
|
147
|
+
response.statusCode = 404;
|
148
|
+
response.setHeader('Content-Type', 'text/plain');
|
149
|
+
response.end('Not Found');
|
150
|
+
}
|
151
|
+
}
|
152
|
+
catch (error) {
|
153
|
+
(0, logger_js_1.logger)().error('Request handling error', error);
|
154
|
+
response.statusCode = 500;
|
155
|
+
response.setHeader('Content-Type', 'text/plain');
|
156
|
+
response.end('Internal Server Error');
|
157
|
+
}
|
158
|
+
};
|
159
|
+
server.on('request', (request, response) => {
|
160
|
+
response.setHeader('Access-Control-Allow-Origin', '*');
|
161
|
+
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
162
|
+
response.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
|
27
163
|
if (request.method === 'OPTIONS') {
|
28
|
-
|
164
|
+
response.statusCode = 200;
|
165
|
+
response.end();
|
166
|
+
return;
|
29
167
|
}
|
30
|
-
|
168
|
+
router(request, response);
|
31
169
|
});
|
32
|
-
// Health check endpoint
|
33
|
-
server.get('/ping', index_js_1.pingHandler);
|
34
|
-
// Stories endpoint
|
35
|
-
server.post('/stories', (0, index_js_1.createStoriesHandler)());
|
36
|
-
// Capture endpoint
|
37
|
-
server.post('/capture', index_js_1.captureHandler);
|
38
|
-
// Serve report files
|
39
|
-
server.get('/report/*', (0, index_js_1.createStaticFileHandler)(reportDir, '/report/'));
|
40
|
-
// Serve static files
|
41
|
-
const webDir = path_1.default.join(path_1.default.dirname((0, url_1.fileURLToPath)(importMetaUrl)), '../../client/web');
|
42
|
-
server.get('/*', (0, index_js_1.createStaticFileHandler)(webDir));
|
43
|
-
// If UI mode, wait for CreeveyApi to be resolved
|
44
170
|
if (ui) {
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
publish: broadcast,
|
55
|
-
};
|
56
|
-
let api = null;
|
57
|
-
server.use(async (request, _response, next) => {
|
58
|
-
if (!api && request.path === '/') {
|
59
|
-
api = await creeveyApi;
|
60
|
-
api.subscribe(customWsServer);
|
61
|
-
}
|
62
|
-
next();
|
63
|
-
});
|
64
|
-
// Create WebSocket listener
|
65
|
-
server.ws('/', (ws) => {
|
66
|
-
// Add connection to the set of active connections
|
67
|
-
activeConnections.add(ws);
|
68
|
-
// Handle message events
|
69
|
-
ws.on('message', (message) => {
|
70
|
-
api?.handleMessage(ws, message);
|
171
|
+
wss = new ws_1.WebSocketServer({ server });
|
172
|
+
wss.on('connection', (ws) => {
|
173
|
+
ws.on('message', (message, isBinary) => {
|
174
|
+
if (creeveyApi) {
|
175
|
+
// NOTE Text messages are passed as Buffer https://github.com/websockets/ws/releases/tag/8.0.0
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
177
|
+
creeveyApi.handleMessage(ws, isBinary ? message : message.toString('utf-8'));
|
178
|
+
return;
|
179
|
+
}
|
71
180
|
});
|
72
|
-
|
73
|
-
|
74
|
-
activeConnections.delete(ws);
|
181
|
+
ws.on('error', (error) => {
|
182
|
+
(0, logger_js_1.logger)().error('WebSocket error', error);
|
75
183
|
});
|
76
184
|
});
|
185
|
+
wss.on('error', (error) => {
|
186
|
+
(0, logger_js_1.logger)().error('WebSocket error', error);
|
187
|
+
});
|
77
188
|
}
|
78
|
-
// Shutdown handling
|
79
189
|
(0, messages_js_1.subscribeOn)('shutdown', () => {
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
catch (error) {
|
86
|
-
(0, logger_js_1.logger)().error('Error closing WebSocket connection', error);
|
87
|
-
}
|
190
|
+
if (wss) {
|
191
|
+
wss.clients.forEach((ws) => {
|
192
|
+
ws.close();
|
193
|
+
});
|
194
|
+
wss.close();
|
88
195
|
}
|
89
|
-
// Close the server
|
90
196
|
server.close();
|
91
197
|
});
|
92
|
-
// Start server
|
93
198
|
server
|
94
|
-
.listen(port)
|
95
|
-
.then(() => {
|
199
|
+
.listen(port, () => {
|
96
200
|
(0, logger_js_1.logger)().info(`Server starting on port ${port}`);
|
97
201
|
})
|
98
|
-
.
|
202
|
+
.on('error', (error) => {
|
99
203
|
(0, logger_js_1.logger)().error('Failed to start server', error);
|
100
204
|
process.exit(1);
|
101
205
|
});
|
206
|
+
void new Promise((resolve) => (resolveApi = resolve))
|
207
|
+
.then((api) => {
|
208
|
+
creeveyApi = api;
|
209
|
+
if (wss) {
|
210
|
+
creeveyApi.subscribe(wss);
|
211
|
+
}
|
212
|
+
})
|
213
|
+
.catch(utils_js_1.shutdownOnException);
|
102
214
|
// Return the function to resolve the API
|
103
215
|
return resolveApi;
|
104
216
|
}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/server/master/server.ts"],"names":[],"mappings":";;;;;
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/server/master/server.ts"],"names":[],"mappings":"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"}
|
@@ -7,14 +7,14 @@ exports.playwrightDockerFile = playwrightDockerFile;
|
|
7
7
|
const promises_1 = require("fs/promises");
|
8
8
|
const url_1 = require("url");
|
9
9
|
const semver_1 = __importDefault(require("semver"));
|
10
|
-
const shelljs_1 = require("shelljs");
|
10
|
+
const shelljs_1 = __importDefault(require("shelljs"));
|
11
11
|
const importMetaUrl = (0, url_1.pathToFileURL)(__filename).href;
|
12
12
|
// TODO Support custom docker images
|
13
13
|
async function playwrightDockerFile(browser, version) {
|
14
14
|
const sv = semver_1.default.coerce(version);
|
15
15
|
let npmRegistry;
|
16
16
|
try {
|
17
|
-
npmRegistry =
|
17
|
+
npmRegistry = shelljs_1.default.exec('npm config get registry', { silent: true }).stdout.trim();
|
18
18
|
}
|
19
19
|
catch {
|
20
20
|
/* noop */
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"docker-file.js","sourceRoot":"","sources":["../../../src/server/playwright/docker-file.ts"],"names":[],"mappings":";;;;;AAQA,oDAqCC;AA7CD,0CAAuC;AACvC,6BAAoC;AACpC,oDAA4B;AAC5B,
|
1
|
+
{"version":3,"file":"docker-file.js","sourceRoot":"","sources":["../../../src/server/playwright/docker-file.ts"],"names":[],"mappings":";;;;;AAQA,oDAqCC;AA7CD,0CAAuC;AACvC,6BAAoC;AACpC,oDAA4B;AAC5B,sDAAyB;AAEzB,MAAM,aAAa,GAAG,IAAA,mBAAa,EAAC,UAAU,CAAC,CAAC,IAAI,CAAC;AAErD,oCAAoC;AAC7B,KAAK,UAAU,oBAAoB,CAAC,OAAe,EAAE,OAAe;IACzE,MAAM,EAAE,GAAG,gBAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAElC,IAAI,WAAW,CAAC;IAChB,IAAI,CAAC;QACH,WAAW,GAAG,iBAAE,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACnF,CAAC;IAAC,MAAM,CAAC;QACP,UAAU;IACZ,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,IAAA,mBAAQ,EAAC,IAAI,GAAG,CAAC,oBAAoB,EAAE,aAAa,CAAC,EAAE,OAAO,CAAC,CAAC;IAEtF,MAAM,UAAU,GAAG;;;;;;EAMnB,OAAO;SACN,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,qBAAqB,CAAC;SAC1E,IAAI,CAAC,IAAI,CAAC;MAEP,WAAW;QACT,CAAC,CAAC;qBACW,WAAW,kBAAkB;QAC1C,CAAC,CAAC,EACN;2BACuB,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE;uBAC/B,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,wBAAwB,OAAO;;;;;CAKhF,CAAC;IAEA,OAAO,UAAU,CAAC,OAAO,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;AACrD,CAAC"}
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "creevey",
|
3
3
|
"description": "Cross-browser screenshot testing tool for Storybook with fancy UI Runner",
|
4
|
-
"version": "0.10.0-beta.
|
4
|
+
"version": "0.10.0-beta.45",
|
5
5
|
"type": "commonjs",
|
6
6
|
"bin": "dist/cli.js",
|
7
7
|
"main": "./dist/index.js",
|
@@ -52,7 +52,8 @@
|
|
52
52
|
"lint:tsc": "tsc --noEmit",
|
53
53
|
"lint:eslint": "eslint",
|
54
54
|
"lint:prettier": "prettier --check .",
|
55
|
-
"test": "vitest",
|
55
|
+
"test": "vitest run",
|
56
|
+
"test:watch": "vitest",
|
56
57
|
"start": "concurrently \"yarn start:client\" \"yarn start:storybook\" \"yarn start:creevey\"",
|
57
58
|
"start:client": "vite",
|
58
59
|
"start:storybook": "storybook dev --ci -p 6006",
|
@@ -111,7 +112,6 @@
|
|
111
112
|
"dockerode": "^4.0.5",
|
112
113
|
"find-cache-dir": "^5.0.0",
|
113
114
|
"get-port": "^7.1.0",
|
114
|
-
"hyper-express": "^6.17.3",
|
115
115
|
"lodash": "^4.17.21",
|
116
116
|
"loglevel": "^1.9.2",
|
117
117
|
"loglevel-plugin-prefix": "^0.8.4",
|
package/src/server/index.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import cluster from 'cluster';
|
2
2
|
import path from 'path';
|
3
|
-
import
|
3
|
+
import sh from 'shelljs';
|
4
4
|
import { getUserAgent } from 'package-manager-detector/detect';
|
5
5
|
import { resolveCommand } from 'package-manager-detector/commands';
|
6
6
|
import { readConfig, defaultBrowser } from './config.js';
|
@@ -121,7 +121,7 @@ export default async function (options: Options): Promise<void> {
|
|
121
121
|
if (remoteUrl && localUrl != remoteUrl) logger().info(`On your network - ${remoteUrl}`);
|
122
122
|
logger().info('Waiting Storybook...');
|
123
123
|
|
124
|
-
const storybook = exec(storybookCommand, { async: true });
|
124
|
+
const storybook = sh.exec(storybookCommand, { async: true });
|
125
125
|
subscribeOn('shutdown', () => {
|
126
126
|
if (storybook.pid) void killTree(storybook.pid);
|
127
127
|
});
|
package/src/server/master/api.ts
CHANGED
@@ -1,25 +1,28 @@
|
|
1
|
-
import
|
2
|
-
import { Request, Response, CreeveyUpdate } from '../../types.js';
|
1
|
+
import { Data, WebSocket, WebSocketServer } from 'ws';
|
2
|
+
import type { Request, Response, CreeveyUpdate } from '../../types.js';
|
3
|
+
import type { TestsManager } from './testsManager.js';
|
4
|
+
import type Runner from './runner.js';
|
3
5
|
import { logger } from '../logger.js';
|
4
|
-
import { TestsManager } from './testsManager.js';
|
5
|
-
import HyperExpress from 'hyper-express';
|
6
6
|
|
7
|
-
|
8
|
-
clients
|
9
|
-
|
7
|
+
function broadcast(wss: WebSocketServer, message: Response): void {
|
8
|
+
wss.clients.forEach((ws) => {
|
9
|
+
if (ws.readyState === WebSocket.OPEN) {
|
10
|
+
ws.send(JSON.stringify(message));
|
11
|
+
}
|
12
|
+
});
|
10
13
|
}
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
function send(ws: WebSocket, message: Response): void {
|
16
|
+
if (ws.readyState === WebSocket.OPEN) {
|
17
|
+
ws.send(JSON.stringify(message));
|
18
|
+
}
|
16
19
|
}
|
17
20
|
|
18
|
-
// The class-based implementation of CreeveyApi
|
21
|
+
// The class-based implementation of CreeveyApi for native WebSockets
|
19
22
|
export class CreeveyApi {
|
20
23
|
private runner: Runner | null = null;
|
21
24
|
private testsManager: TestsManager;
|
22
|
-
private wss:
|
25
|
+
private wss: WebSocketServer | null = null;
|
23
26
|
|
24
27
|
constructor(testsManager: TestsManager, runner?: Runner) {
|
25
28
|
this.testsManager = testsManager;
|
@@ -30,7 +33,7 @@ export class CreeveyApi {
|
|
30
33
|
}
|
31
34
|
}
|
32
35
|
|
33
|
-
subscribe(wss:
|
36
|
+
subscribe(wss: WebSocketServer): void {
|
34
37
|
this.wss = wss;
|
35
38
|
|
36
39
|
// If we have a runner, subscribe to its updates
|
@@ -46,26 +49,20 @@ export class CreeveyApi {
|
|
46
49
|
}
|
47
50
|
}
|
48
51
|
|
49
|
-
handleMessage(ws:
|
52
|
+
handleMessage(ws: WebSocket, message: Data): void {
|
50
53
|
if (typeof message != 'string') {
|
51
|
-
|
52
|
-
|
53
|
-
} else {
|
54
|
-
logger().info('unhandled message', message);
|
55
|
-
return;
|
56
|
-
}
|
54
|
+
logger().info('unhandled message', message);
|
55
|
+
return;
|
57
56
|
}
|
58
57
|
|
59
58
|
const command = JSON.parse(message) as Request;
|
60
|
-
const sendResponse = (response: Response) => {
|
61
|
-
ws.send(JSON.stringify(response));
|
62
|
-
};
|
63
59
|
|
64
60
|
if (this.runner) {
|
65
61
|
// Normal mode handling with runner
|
66
62
|
switch (command.type) {
|
67
63
|
case 'status': {
|
68
|
-
|
64
|
+
const status = this.runner.status;
|
65
|
+
send(ws, { type: 'status', payload: status });
|
69
66
|
return;
|
70
67
|
}
|
71
68
|
case 'start': {
|
@@ -98,7 +95,7 @@ export class CreeveyApi {
|
|
98
95
|
}
|
99
96
|
case 'status': {
|
100
97
|
// In update mode, respond with static status including tests data
|
101
|
-
|
98
|
+
send(ws, {
|
102
99
|
type: 'status',
|
103
100
|
payload: {
|
104
101
|
isRunning: false,
|
@@ -123,6 +120,6 @@ export class CreeveyApi {
|
|
123
120
|
|
124
121
|
const message: Response = { type: 'update', payload };
|
125
122
|
|
126
|
-
|
123
|
+
broadcast(this.wss, message);
|
127
124
|
}
|
128
125
|
}
|
@@ -1,39 +1,20 @@
|
|
1
|
-
import { Request, Response } from 'hyper-express';
|
2
1
|
import cluster from 'cluster';
|
3
2
|
import { subscribeOnWorker, sendStoriesMessage } from '../../messages.js';
|
4
3
|
import { CaptureOptions, isDefined } from '../../../types.js';
|
5
4
|
|
6
|
-
export
|
7
|
-
const { workerId, options } = await request.json<
|
8
|
-
{ workerId: number; options?: CaptureOptions },
|
9
|
-
{
|
10
|
-
workerId: number;
|
11
|
-
options?: CaptureOptions;
|
12
|
-
}
|
13
|
-
>({
|
14
|
-
workerId: 0,
|
15
|
-
options: undefined,
|
16
|
-
});
|
17
|
-
|
5
|
+
export function captureHandler({ workerId, options }: { workerId: number; options?: CaptureOptions }): void {
|
18
6
|
const worker = Object.values(cluster.workers ?? {})
|
19
7
|
.filter(isDefined)
|
20
8
|
.find((worker) => worker.process.pid == workerId);
|
21
9
|
|
22
10
|
// NOTE: Hypothetical case when someone send to us capture req and we don't have a worker with browser session for it
|
23
11
|
if (!worker) {
|
24
|
-
response.send();
|
25
12
|
return;
|
26
13
|
}
|
27
14
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
unsubscribe();
|
32
|
-
resolve();
|
33
|
-
});
|
34
|
-
sendStoriesMessage(worker, { type: 'capture', payload: options });
|
15
|
+
const unsubscribe = subscribeOnWorker(worker, 'stories', (message) => {
|
16
|
+
if (message.type != 'capture') return;
|
17
|
+
unsubscribe();
|
35
18
|
});
|
36
|
-
|
37
|
-
// TODO Pass screenshot result to show it in inspector
|
38
|
-
response.send('Ok');
|
19
|
+
sendStoriesMessage(worker, { type: 'capture', payload: options });
|
39
20
|
}
|
@@ -1,5 +1,6 @@
|
|
1
|
-
import {
|
1
|
+
import { IncomingMessage, ServerResponse } from 'http';
|
2
2
|
|
3
|
-
export function pingHandler(_request:
|
4
|
-
response.
|
3
|
+
export function pingHandler(_request: IncomingMessage, response: ServerResponse): void {
|
4
|
+
response.setHeader('Content-Type', 'text/plain');
|
5
|
+
response.end('pong');
|
5
6
|
}
|
@@ -1,29 +1,18 @@
|
|
1
|
-
import { Request, Response } from 'hyper-express';
|
2
1
|
import path from 'path';
|
3
2
|
import fs from 'fs';
|
4
|
-
import { logger } from '../../logger.js';
|
5
3
|
|
6
|
-
export function
|
7
|
-
return (
|
8
|
-
|
9
|
-
|
10
|
-
const relativePath = pathPrefix ? decodedPath.replace(pathPrefix, '') : decodedPath;
|
11
|
-
let filePath = path.join(baseDir, relativePath || 'index.html');
|
4
|
+
export function staticHandler(baseDir: string, pathPrefix?: string) {
|
5
|
+
return (requestedPath: string): string | undefined => {
|
6
|
+
const relativePath = pathPrefix ? requestedPath.replace(pathPrefix, '') : requestedPath;
|
7
|
+
let filePath = path.join(baseDir, relativePath || 'index.html');
|
12
8
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
if (!fs.existsSync(filePath)) {
|
19
|
-
response.status(404).send('File not found');
|
20
|
-
return;
|
21
|
-
}
|
9
|
+
// If the path points to a directory, append index.html
|
10
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
|
11
|
+
filePath = path.join(filePath, 'index.html');
|
12
|
+
}
|
22
13
|
|
23
|
-
|
24
|
-
|
25
|
-
logger().error('Error serving file', error);
|
26
|
-
response.status(500).send('Internal server error');
|
14
|
+
if (!fs.existsSync(filePath)) {
|
15
|
+
return undefined;
|
27
16
|
}
|
28
17
|
};
|
29
18
|
}
|