testblocks 0.2.0 → 0.3.0
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/cli/index.js +22 -2
- package/dist/server/startServer.d.ts +7 -0
- package/dist/server/startServer.js +405 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -40,6 +40,7 @@ const path = __importStar(require("path"));
|
|
|
40
40
|
const glob_1 = require("glob");
|
|
41
41
|
const executor_1 = require("./executor");
|
|
42
42
|
const reporters_1 = require("./reporters");
|
|
43
|
+
const startServer_1 = require("../server/startServer");
|
|
43
44
|
const program = new commander_1.Command();
|
|
44
45
|
program
|
|
45
46
|
.name('testblocks')
|
|
@@ -229,7 +230,7 @@ program
|
|
|
229
230
|
'test:junit': 'testblocks run tests/**/*.testblocks.json -r junit -o reports',
|
|
230
231
|
},
|
|
231
232
|
devDependencies: {
|
|
232
|
-
testblocks: '^
|
|
233
|
+
testblocks: '^0.3.0',
|
|
233
234
|
},
|
|
234
235
|
};
|
|
235
236
|
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
|
|
@@ -340,7 +341,8 @@ Thumbs.db
|
|
|
340
341
|
console.log(' 1. cd ' + (directory === '.' ? '' : directory));
|
|
341
342
|
console.log(' 2. npm install');
|
|
342
343
|
console.log(' 3. npm test\n');
|
|
343
|
-
console.log('
|
|
344
|
+
console.log('To open the visual test editor:');
|
|
345
|
+
console.log(' testblocks serve\n');
|
|
344
346
|
});
|
|
345
347
|
program
|
|
346
348
|
.command('list')
|
|
@@ -372,6 +374,24 @@ program
|
|
|
372
374
|
process.exit(1);
|
|
373
375
|
}
|
|
374
376
|
});
|
|
377
|
+
program
|
|
378
|
+
.command('serve')
|
|
379
|
+
.description('Start the TestBlocks web UI')
|
|
380
|
+
.option('-p, --port <port>', 'Port to run on', '3000')
|
|
381
|
+
.option('--plugins-dir <dir>', 'Plugins directory', './plugins')
|
|
382
|
+
.option('--globals-dir <dir>', 'Globals directory (where globals.json is located)', '.')
|
|
383
|
+
.option('-o, --open', 'Open browser automatically', false)
|
|
384
|
+
.action(async (options) => {
|
|
385
|
+
const port = parseInt(options.port, 10);
|
|
386
|
+
const pluginsDir = path.resolve(options.pluginsDir);
|
|
387
|
+
const globalsDir = path.resolve(options.globalsDir);
|
|
388
|
+
await (0, startServer_1.startServer)({
|
|
389
|
+
port,
|
|
390
|
+
pluginsDir,
|
|
391
|
+
globalsDir,
|
|
392
|
+
open: options.open,
|
|
393
|
+
});
|
|
394
|
+
});
|
|
375
395
|
function createReporter(type, outputDir) {
|
|
376
396
|
switch (type) {
|
|
377
397
|
case 'json':
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.startServer = startServer;
|
|
40
|
+
const express_1 = __importDefault(require("express"));
|
|
41
|
+
const cors_1 = __importDefault(require("cors"));
|
|
42
|
+
const path_1 = __importDefault(require("path"));
|
|
43
|
+
const fs_1 = __importDefault(require("fs"));
|
|
44
|
+
const executor_1 = require("./executor");
|
|
45
|
+
const reporters_1 = require("../cli/reporters");
|
|
46
|
+
const plugins_1 = require("./plugins");
|
|
47
|
+
const globals_1 = require("./globals");
|
|
48
|
+
const codegenManager_1 = require("./codegenManager");
|
|
49
|
+
async function startServer(options = {}) {
|
|
50
|
+
const port = options.port || 3000;
|
|
51
|
+
const workingDir = process.cwd();
|
|
52
|
+
// Set directories
|
|
53
|
+
const pluginsDir = options.pluginsDir || path_1.default.join(workingDir, 'plugins');
|
|
54
|
+
const globalsDir = options.globalsDir || workingDir;
|
|
55
|
+
(0, plugins_1.setPluginsDirectory)(pluginsDir);
|
|
56
|
+
(0, globals_1.setGlobalsDirectory)(globalsDir);
|
|
57
|
+
// Load plugins and globals
|
|
58
|
+
try {
|
|
59
|
+
await (0, plugins_1.loadAllPlugins)();
|
|
60
|
+
(0, plugins_1.initializeServerPlugins)();
|
|
61
|
+
(0, globals_1.initializeGlobalsAndSnippets)();
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.error('Failed to load plugins:', err);
|
|
65
|
+
}
|
|
66
|
+
const app = (0, express_1.default)();
|
|
67
|
+
app.use((0, cors_1.default)());
|
|
68
|
+
app.use(express_1.default.json({ limit: '10mb' }));
|
|
69
|
+
// Serve static client files
|
|
70
|
+
const clientDir = path_1.default.join(__dirname, '..', 'client');
|
|
71
|
+
if (fs_1.default.existsSync(clientDir)) {
|
|
72
|
+
app.use(express_1.default.static(clientDir));
|
|
73
|
+
}
|
|
74
|
+
// Health check
|
|
75
|
+
app.get('/api/health', (_req, res) => {
|
|
76
|
+
res.json({ status: 'ok', version: '1.0.0' });
|
|
77
|
+
});
|
|
78
|
+
// List available plugins
|
|
79
|
+
app.get('/api/plugins', (_req, res) => {
|
|
80
|
+
const available = (0, plugins_1.discoverPlugins)();
|
|
81
|
+
const loaded = (0, plugins_1.getServerPlugins)().map(p => ({
|
|
82
|
+
name: p.name,
|
|
83
|
+
version: p.version,
|
|
84
|
+
description: p.description,
|
|
85
|
+
blockCount: p.blocks.length,
|
|
86
|
+
}));
|
|
87
|
+
res.json({
|
|
88
|
+
directory: (0, plugins_1.getPluginsDirectory)(),
|
|
89
|
+
available,
|
|
90
|
+
loaded,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
// Load specific plugins
|
|
94
|
+
app.post('/api/plugins/load', async (req, res) => {
|
|
95
|
+
try {
|
|
96
|
+
const { plugins } = req.body;
|
|
97
|
+
await (0, plugins_1.loadTestFilePlugins)(plugins);
|
|
98
|
+
res.json({ loaded: plugins });
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
res.status(500).json({ error: error.message });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
// Get globals and snippets
|
|
105
|
+
app.get('/api/globals', (_req, res) => {
|
|
106
|
+
const globals = (0, globals_1.getGlobals)();
|
|
107
|
+
const snippets = (0, globals_1.getAllSnippets)().map(s => ({
|
|
108
|
+
name: s.name,
|
|
109
|
+
description: s.description,
|
|
110
|
+
category: s.category,
|
|
111
|
+
params: s.params,
|
|
112
|
+
stepCount: s.steps.length,
|
|
113
|
+
}));
|
|
114
|
+
res.json({
|
|
115
|
+
directory: (0, globals_1.getGlobalsDirectory)(),
|
|
116
|
+
globals,
|
|
117
|
+
snippets,
|
|
118
|
+
testIdAttribute: (0, globals_1.getTestIdAttribute)(),
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
// Update test ID attribute
|
|
122
|
+
app.put('/api/globals/test-id-attribute', (req, res) => {
|
|
123
|
+
const { testIdAttribute } = req.body;
|
|
124
|
+
if (!testIdAttribute || typeof testIdAttribute !== 'string') {
|
|
125
|
+
return res.status(400).json({ error: 'testIdAttribute is required and must be a string' });
|
|
126
|
+
}
|
|
127
|
+
(0, globals_1.setTestIdAttribute)(testIdAttribute);
|
|
128
|
+
res.json({ testIdAttribute: (0, globals_1.getTestIdAttribute)() });
|
|
129
|
+
});
|
|
130
|
+
// Run tests
|
|
131
|
+
app.post('/api/run', async (req, res) => {
|
|
132
|
+
try {
|
|
133
|
+
const testFile = req.body;
|
|
134
|
+
if (!testFile || !testFile.tests) {
|
|
135
|
+
return res.status(400).json({ error: 'Invalid test file format' });
|
|
136
|
+
}
|
|
137
|
+
console.log(`Running ${testFile.tests.length} tests from "${testFile.name}"...`);
|
|
138
|
+
const globalVars = (0, globals_1.getGlobalVariables)();
|
|
139
|
+
const testIdAttr = (0, globals_1.getTestIdAttribute)();
|
|
140
|
+
const executor = new executor_1.TestExecutor({
|
|
141
|
+
headless: req.query.headless !== 'false',
|
|
142
|
+
timeout: Number(req.query.timeout) || 30000,
|
|
143
|
+
variables: globalVars,
|
|
144
|
+
testIdAttribute: testIdAttr,
|
|
145
|
+
baseDir: globalsDir,
|
|
146
|
+
});
|
|
147
|
+
const results = await executor.runTestFile(testFile);
|
|
148
|
+
const passed = results.filter(r => r.status === 'passed').length;
|
|
149
|
+
const failed = results.filter(r => r.status === 'failed').length;
|
|
150
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
151
|
+
res.json(results);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
const err = error;
|
|
155
|
+
console.error('Test execution failed:', err.message);
|
|
156
|
+
res.status(500).json({
|
|
157
|
+
error: 'Test execution failed',
|
|
158
|
+
message: err.message,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
// Run a single test
|
|
163
|
+
app.post('/api/run/:testId', async (req, res) => {
|
|
164
|
+
try {
|
|
165
|
+
const testFile = req.body;
|
|
166
|
+
const { testId } = req.params;
|
|
167
|
+
const test = testFile.tests.find(t => t.id === testId);
|
|
168
|
+
if (!test) {
|
|
169
|
+
return res.status(404).json({ error: `Test not found: ${testId}` });
|
|
170
|
+
}
|
|
171
|
+
const globalVars = (0, globals_1.getGlobalVariables)();
|
|
172
|
+
const testIdAttr = (0, globals_1.getTestIdAttribute)();
|
|
173
|
+
const executor = new executor_1.TestExecutor({
|
|
174
|
+
headless: req.query.headless !== 'false',
|
|
175
|
+
timeout: Number(req.query.timeout) || 30000,
|
|
176
|
+
variables: globalVars,
|
|
177
|
+
testIdAttribute: testIdAttr,
|
|
178
|
+
baseDir: globalsDir,
|
|
179
|
+
});
|
|
180
|
+
if (testFile.procedures) {
|
|
181
|
+
executor.registerProcedures(testFile.procedures);
|
|
182
|
+
}
|
|
183
|
+
await executor.initialize();
|
|
184
|
+
const result = await executor.runTest(test, testFile.variables);
|
|
185
|
+
await executor.cleanup();
|
|
186
|
+
res.json(result);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
console.error('Test execution failed:', error);
|
|
190
|
+
res.status(500).json({
|
|
191
|
+
error: 'Test execution failed',
|
|
192
|
+
message: error.message,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
// Validate test file
|
|
197
|
+
app.post('/api/validate', (req, res) => {
|
|
198
|
+
try {
|
|
199
|
+
const testFile = req.body;
|
|
200
|
+
const errors = [];
|
|
201
|
+
if (!testFile.version) {
|
|
202
|
+
errors.push('Missing version field');
|
|
203
|
+
}
|
|
204
|
+
if (!testFile.name) {
|
|
205
|
+
errors.push('Missing name field');
|
|
206
|
+
}
|
|
207
|
+
if (!testFile.tests || !Array.isArray(testFile.tests)) {
|
|
208
|
+
errors.push('Missing or invalid tests array');
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
testFile.tests.forEach((test, index) => {
|
|
212
|
+
if (!test.id) {
|
|
213
|
+
errors.push(`Test at index ${index} is missing an id`);
|
|
214
|
+
}
|
|
215
|
+
if (!test.name) {
|
|
216
|
+
errors.push(`Test at index ${index} is missing a name`);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
if (errors.length > 0) {
|
|
221
|
+
res.status(400).json({ valid: false, errors });
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
res.json({ valid: true });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
res.status(400).json({
|
|
229
|
+
valid: false,
|
|
230
|
+
errors: ['Invalid JSON: ' + error.message],
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
// Recording endpoints
|
|
235
|
+
app.post('/api/record/start', async (req, res) => {
|
|
236
|
+
try {
|
|
237
|
+
const { url, testIdAttribute } = req.body;
|
|
238
|
+
if (!url) {
|
|
239
|
+
return res.status(400).json({ error: 'URL is required' });
|
|
240
|
+
}
|
|
241
|
+
console.log(`Starting recording session for URL: ${url}`);
|
|
242
|
+
const sessionId = await codegenManager_1.codegenManager.startRecording(url, {
|
|
243
|
+
testIdAttribute: testIdAttribute || undefined,
|
|
244
|
+
});
|
|
245
|
+
res.json({
|
|
246
|
+
sessionId,
|
|
247
|
+
status: 'running',
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
console.error('Failed to start recording:', error);
|
|
252
|
+
res.status(500).json({
|
|
253
|
+
error: 'Failed to start recording',
|
|
254
|
+
message: error.message,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
app.post('/api/record/stop', async (req, res) => {
|
|
259
|
+
try {
|
|
260
|
+
const { sessionId } = req.body;
|
|
261
|
+
if (!sessionId) {
|
|
262
|
+
return res.status(400).json({ error: 'Session ID is required' });
|
|
263
|
+
}
|
|
264
|
+
console.log(`Stopping recording session: ${sessionId}`);
|
|
265
|
+
const steps = await codegenManager_1.codegenManager.stopRecording(sessionId);
|
|
266
|
+
res.json({
|
|
267
|
+
status: 'completed',
|
|
268
|
+
steps,
|
|
269
|
+
});
|
|
270
|
+
setTimeout(() => {
|
|
271
|
+
codegenManager_1.codegenManager.cleanup(sessionId);
|
|
272
|
+
}, 5000);
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
console.error('Failed to stop recording:', error);
|
|
276
|
+
res.status(500).json({
|
|
277
|
+
error: 'Failed to stop recording',
|
|
278
|
+
message: error.message,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
app.get('/api/record/status/:sessionId', (req, res) => {
|
|
283
|
+
const { sessionId } = req.params;
|
|
284
|
+
const session = codegenManager_1.codegenManager.getStatus(sessionId);
|
|
285
|
+
if (!session) {
|
|
286
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
287
|
+
}
|
|
288
|
+
res.json({
|
|
289
|
+
sessionId: session.id,
|
|
290
|
+
status: session.status,
|
|
291
|
+
url: session.url,
|
|
292
|
+
error: session.error,
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
// Report generation
|
|
296
|
+
app.post('/api/reports/html', (req, res) => {
|
|
297
|
+
try {
|
|
298
|
+
const { testFile, results } = req.body;
|
|
299
|
+
if (!testFile || !results) {
|
|
300
|
+
return res.status(400).json({ error: 'testFile and results are required' });
|
|
301
|
+
}
|
|
302
|
+
const timestamp = new Date().toISOString();
|
|
303
|
+
const reportData = {
|
|
304
|
+
timestamp,
|
|
305
|
+
summary: {
|
|
306
|
+
totalTests: results.length,
|
|
307
|
+
passed: results.filter(r => r.status === 'passed').length,
|
|
308
|
+
failed: results.filter(r => r.status !== 'passed').length,
|
|
309
|
+
duration: results.reduce((sum, r) => sum + r.duration, 0),
|
|
310
|
+
},
|
|
311
|
+
testFiles: [{
|
|
312
|
+
file: testFile.name || 'TestBlocks Test',
|
|
313
|
+
testFile,
|
|
314
|
+
results,
|
|
315
|
+
}],
|
|
316
|
+
};
|
|
317
|
+
const html = (0, reporters_1.generateHTMLReport)(reportData);
|
|
318
|
+
const filename = `report-${(0, reporters_1.getTimestamp)()}.html`;
|
|
319
|
+
res.setHeader('Content-Type', 'text/html');
|
|
320
|
+
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
321
|
+
res.send(html);
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
console.error('Failed to generate HTML report:', error);
|
|
325
|
+
res.status(500).json({
|
|
326
|
+
error: 'Failed to generate report',
|
|
327
|
+
message: error.message,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
app.post('/api/reports/junit', (req, res) => {
|
|
332
|
+
try {
|
|
333
|
+
const { testFile, results } = req.body;
|
|
334
|
+
if (!testFile || !results) {
|
|
335
|
+
return res.status(400).json({ error: 'testFile and results are required' });
|
|
336
|
+
}
|
|
337
|
+
const timestamp = new Date().toISOString();
|
|
338
|
+
const reportData = {
|
|
339
|
+
timestamp,
|
|
340
|
+
summary: {
|
|
341
|
+
totalTests: results.length,
|
|
342
|
+
passed: results.filter(r => r.status === 'passed').length,
|
|
343
|
+
failed: results.filter(r => r.status !== 'passed').length,
|
|
344
|
+
duration: results.reduce((sum, r) => sum + r.duration, 0),
|
|
345
|
+
},
|
|
346
|
+
testFiles: [{
|
|
347
|
+
file: testFile.name || 'TestBlocks Test',
|
|
348
|
+
testFile,
|
|
349
|
+
results,
|
|
350
|
+
}],
|
|
351
|
+
};
|
|
352
|
+
const xml = (0, reporters_1.generateJUnitXML)(reportData);
|
|
353
|
+
const filename = `junit-${(0, reporters_1.getTimestamp)()}.xml`;
|
|
354
|
+
res.setHeader('Content-Type', 'application/xml');
|
|
355
|
+
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
356
|
+
res.send(xml);
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
console.error('Failed to generate JUnit report:', error);
|
|
360
|
+
res.status(500).json({
|
|
361
|
+
error: 'Failed to generate report',
|
|
362
|
+
message: error.message,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
// Serve index.html for all non-API routes (SPA support)
|
|
367
|
+
app.get('*', (_req, res) => {
|
|
368
|
+
const indexPath = path_1.default.join(clientDir, 'index.html');
|
|
369
|
+
if (fs_1.default.existsSync(indexPath)) {
|
|
370
|
+
res.sendFile(indexPath);
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
res.status(404).json({ error: 'Web UI not found. Make sure testblocks is properly installed.' });
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
// Cleanup handlers
|
|
377
|
+
process.on('SIGTERM', () => {
|
|
378
|
+
console.log('Received SIGTERM, cleaning up...');
|
|
379
|
+
codegenManager_1.codegenManager.cleanupAll();
|
|
380
|
+
process.exit(0);
|
|
381
|
+
});
|
|
382
|
+
process.on('SIGINT', () => {
|
|
383
|
+
console.log('Received SIGINT, cleaning up...');
|
|
384
|
+
codegenManager_1.codegenManager.cleanupAll();
|
|
385
|
+
process.exit(0);
|
|
386
|
+
});
|
|
387
|
+
// Start server
|
|
388
|
+
app.listen(port, () => {
|
|
389
|
+
console.log(`\nTestBlocks Web UI running at http://localhost:${port}\n`);
|
|
390
|
+
console.log('Directories:');
|
|
391
|
+
console.log(` Working directory: ${workingDir}`);
|
|
392
|
+
console.log(` Plugins: ${pluginsDir}`);
|
|
393
|
+
console.log(` Globals: ${globalsDir}`);
|
|
394
|
+
console.log('\nPress Ctrl+C to stop\n');
|
|
395
|
+
// Open browser if requested
|
|
396
|
+
if (options.open) {
|
|
397
|
+
const url = `http://localhost:${port}`;
|
|
398
|
+
const command = process.platform === 'darwin' ? 'open' :
|
|
399
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
400
|
+
Promise.resolve().then(() => __importStar(require('child_process'))).then(cp => {
|
|
401
|
+
cp.exec(`${command} ${url}`);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|