@midscene/test 0.0.0 → 1.10.4-beta-20260715034325.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.
@@ -0,0 +1,1170 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ defineWorkflowProject: ()=>defineWorkflowProject,
28
+ discoverWorkflowFiles: ()=>discoverWorkflowFiles,
29
+ runWorkflowProject: ()=>runWorkflowProject,
30
+ loadWorkflowProjectSync: ()=>loadWorkflowProjectSync,
31
+ validateWorkflowFileSelection: ()=>validateWorkflowFileSelection,
32
+ DEFAULT_WORKFLOW_FILE_SELECTION: ()=>DEFAULT_WORKFLOW_FILE_SELECTION,
33
+ discoverWorkflowConfig: ()=>discoverWorkflowConfig
34
+ });
35
+ const external_node_module_namespaceObject = require("node:module");
36
+ const external_node_path_namespaceObject = require("node:path");
37
+ function _define_property(obj, key, value) {
38
+ if (key in obj) Object.defineProperty(obj, key, {
39
+ value: value,
40
+ enumerable: true,
41
+ configurable: true,
42
+ writable: true
43
+ });
44
+ else obj[key] = value;
45
+ return obj;
46
+ }
47
+ class WorkflowError extends Error {
48
+ toJSON() {
49
+ return {
50
+ name: this.name,
51
+ message: this.message,
52
+ code: this.code,
53
+ ...void 0 === this.details ? {} : {
54
+ details: this.details
55
+ }
56
+ };
57
+ }
58
+ constructor(message, options = {}){
59
+ super(message, {
60
+ cause: options.cause
61
+ }), _define_property(this, "code", void 0), _define_property(this, "details", void 0);
62
+ this.name = new.target.name;
63
+ this.code = options.code ?? 'WORKFLOW_ERROR';
64
+ this.details = options.details;
65
+ }
66
+ }
67
+ class WorkflowParseError extends WorkflowError {
68
+ constructor(message, details, cause){
69
+ super(message, {
70
+ code: 'WORKFLOW_PARSE_ERROR',
71
+ details,
72
+ cause
73
+ });
74
+ }
75
+ }
76
+ class NodeDefinitionError extends WorkflowError {
77
+ constructor(message, details){
78
+ super(message, {
79
+ code: 'NODE_DEFINITION_ERROR',
80
+ details
81
+ });
82
+ }
83
+ }
84
+ class DuplicateNodeError extends WorkflowError {
85
+ constructor(node){
86
+ super(`Node "${node}" is already registered.`, {
87
+ code: 'DUPLICATE_NODE',
88
+ details: {
89
+ node
90
+ }
91
+ }), _define_property(this, "node", void 0);
92
+ this.node = node;
93
+ }
94
+ }
95
+ class NodeNotFoundError extends WorkflowError {
96
+ constructor(node){
97
+ super(`Node "${node}" is not registered.`, {
98
+ code: 'NODE_NOT_FOUND',
99
+ details: {
100
+ node
101
+ }
102
+ }), _define_property(this, "node", void 0);
103
+ this.node = node;
104
+ }
105
+ }
106
+ class StepTimeoutError extends WorkflowError {
107
+ constructor(timeoutMs, node){
108
+ super(node ? `Node "${node}" timed out after ${timeoutMs}ms.` : `Step timed out after ${timeoutMs}ms.`, {
109
+ code: 'STEP_TIMEOUT',
110
+ details: {
111
+ timeoutMs,
112
+ ...void 0 === node ? {} : {
113
+ node
114
+ }
115
+ }
116
+ }), _define_property(this, "timeoutMs", void 0), _define_property(this, "node", void 0);
117
+ this.timeoutMs = timeoutMs;
118
+ this.node = node;
119
+ }
120
+ }
121
+ class NodeExecutionError extends WorkflowError {
122
+ constructor(node, cause){
123
+ const causeMessage = cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');
124
+ super(`Node "${node}" failed: ${causeMessage}`, {
125
+ code: 'NODE_EXECUTION_ERROR',
126
+ details: {
127
+ node
128
+ },
129
+ cause
130
+ }), _define_property(this, "node", void 0);
131
+ this.node = node;
132
+ }
133
+ }
134
+ class WorkflowDocumentSetupError extends WorkflowError {
135
+ constructor(cause, details){
136
+ const causeMessage = cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');
137
+ super(`Workflow document setup failed: ${causeMessage}`, {
138
+ code: 'WORKFLOW_DOCUMENT_SETUP_ERROR',
139
+ details,
140
+ cause
141
+ });
142
+ }
143
+ }
144
+ class WorkflowDocumentTeardownError extends WorkflowError {
145
+ constructor(cause, details){
146
+ const causeMessage = cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');
147
+ super(`Workflow document teardown failed: ${causeMessage}`, {
148
+ code: 'WORKFLOW_DOCUMENT_TEARDOWN_ERROR',
149
+ details,
150
+ cause
151
+ });
152
+ }
153
+ }
154
+ class WorkflowLifecycleError extends WorkflowError {
155
+ constructor(message, details){
156
+ super(message, {
157
+ code: 'WORKFLOW_LIFECYCLE_ERROR',
158
+ details
159
+ });
160
+ }
161
+ }
162
+ function normalizeNodeExecutionError(error, node) {
163
+ return error instanceof WorkflowError ? error : new NodeExecutionError(node, error);
164
+ }
165
+ const validateDefinition = (options)=>{
166
+ if (!options || 'object' != typeof options) throw new NodeDefinitionError('Node definition must be an object.');
167
+ if ('string' != typeof options.name || 0 === options.name.trim().length) throw new NodeDefinitionError('Node name must be a non-empty string.');
168
+ if ('function' != typeof options.execute) throw new NodeDefinitionError(`Node "${options.name}" must provide an execute function.`, {
169
+ node: options.name
170
+ });
171
+ };
172
+ function defineNode(options) {
173
+ validateDefinition(options);
174
+ return options;
175
+ }
176
+ function _check_private_redeclaration(obj, privateCollection) {
177
+ if (privateCollection.has(obj)) throw new TypeError("Cannot initialize the same private elements twice on an object");
178
+ }
179
+ function _class_apply_descriptor_get(receiver, descriptor) {
180
+ if (descriptor.get) return descriptor.get.call(receiver);
181
+ return descriptor.value;
182
+ }
183
+ function _class_extract_field_descriptor(receiver, privateMap, action) {
184
+ if (!privateMap.has(receiver)) throw new TypeError("attempted to " + action + " private field on non-instance");
185
+ return privateMap.get(receiver);
186
+ }
187
+ function _class_private_field_get(receiver, privateMap) {
188
+ var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
189
+ return _class_apply_descriptor_get(receiver, descriptor);
190
+ }
191
+ function _class_private_field_init(obj, privateMap, value) {
192
+ _check_private_redeclaration(obj, privateMap);
193
+ privateMap.set(obj, value);
194
+ }
195
+ var _nodes = /*#__PURE__*/ new WeakMap();
196
+ class NodeRegistry {
197
+ register(node) {
198
+ defineNode(node);
199
+ if (_class_private_field_get(this, _nodes).has(node.name)) throw new DuplicateNodeError(node.name);
200
+ _class_private_field_get(this, _nodes).set(node.name, node);
201
+ return this;
202
+ }
203
+ get(name) {
204
+ return _class_private_field_get(this, _nodes).get(name);
205
+ }
206
+ require(name) {
207
+ const node = this.get(name);
208
+ if (!node) throw new NodeNotFoundError(name);
209
+ return node;
210
+ }
211
+ has(name) {
212
+ return _class_private_field_get(this, _nodes).has(name);
213
+ }
214
+ names() {
215
+ return [
216
+ ..._class_private_field_get(this, _nodes).keys()
217
+ ];
218
+ }
219
+ constructor(nodes = []){
220
+ _class_private_field_init(this, _nodes, {
221
+ writable: true,
222
+ value: new Map()
223
+ });
224
+ for (const node of nodes)this.register(node);
225
+ }
226
+ }
227
+ const defineWorkflowProject = (definition)=>definition;
228
+ const unwrapProjectDefinition = (loaded)=>{
229
+ if ('object' == typeof loaded && null !== loaded && 'default' in loaded) return loaded.default;
230
+ return loaded;
231
+ };
232
+ const isAbsolutePattern = (pattern)=>(0, external_node_path_namespaceObject.isAbsolute)(pattern) || /^[A-Za-z]:[\\/]/.test(pattern) || pattern.startsWith('\\\\');
233
+ const validatePatterns = (value, field)=>{
234
+ if (!Array.isArray(value)) throw new TypeError(`Workflow config files.${field} must be an array.`);
235
+ if ('include' === field && 0 === value.length) throw new TypeError('Workflow config files.include must be a non-empty array.');
236
+ return value.map((pattern, index)=>{
237
+ if ('string' != typeof pattern || 0 === pattern.trim().length) throw new TypeError(`Workflow config files.${field}[${index}] must be a non-empty string.`);
238
+ if (isAbsolutePattern(pattern)) throw new TypeError(`Workflow config files.${field}[${index}] must be relative to the project root.`);
239
+ if (pattern.split(/[\\/]/).includes('..')) throw new TypeError(`Workflow config files.${field}[${index}] must not contain a ".." path segment.`);
240
+ if (pattern.includes('\\')) throw new TypeError(`Workflow config files.${field}[${index}] must use POSIX path separators (/).`);
241
+ if (pattern.startsWith('!')) {
242
+ if ('include' === field) throw new TypeError(`Workflow config files.include[${index}] must not be a negated pattern. Use files.exclude instead.`);
243
+ throw new TypeError(`Workflow config files.exclude[${index}] must not be a negated pattern.`);
244
+ }
245
+ return pattern;
246
+ });
247
+ };
248
+ const validateWorkflowFileSelection = (value)=>{
249
+ if (void 0 === value) return;
250
+ if ('object' != typeof value || null === value || Array.isArray(value)) throw new TypeError('Workflow config files must be an object.');
251
+ const files = value;
252
+ const include = validatePatterns(files.include, 'include');
253
+ const exclude = void 0 === files.exclude ? void 0 : validatePatterns(files.exclude, 'exclude');
254
+ return {
255
+ include,
256
+ ...exclude ? {
257
+ exclude
258
+ } : {}
259
+ };
260
+ };
261
+ function loadWorkflowProjectSync(configPath) {
262
+ let definition = {
263
+ nodes: []
264
+ };
265
+ if (configPath) {
266
+ const absolutePath = (0, external_node_path_namespaceObject.resolve)(configPath);
267
+ try {
268
+ definition = unwrapProjectDefinition((0, external_node_module_namespaceObject.createRequire)(absolutePath)(absolutePath));
269
+ } catch (error) {
270
+ throw new Error(`Failed to load workflow config "${absolutePath}". Use a CommonJS config (.cjs).`, {
271
+ cause: error
272
+ });
273
+ }
274
+ }
275
+ if ('object' != typeof definition || null === definition || !('nodes' in definition) || !Array.isArray(definition.nodes)) throw new TypeError('Workflow config must export an object with a nodes array.');
276
+ const projectDefinition = definition;
277
+ if ('setupWorkflow' in definition) throw new TypeError('Workflow config setupWorkflow is no longer supported. Use setupDocument instead.');
278
+ if (void 0 !== projectDefinition.root && ('string' != typeof projectDefinition.root || 0 === projectDefinition.root.trim().length)) throw new TypeError('Workflow config root must be a non-empty string.');
279
+ const files = validateWorkflowFileSelection(projectDefinition.files);
280
+ if (void 0 !== projectDefinition.setupDocument && 'function' != typeof projectDefinition.setupDocument) throw new TypeError('Workflow config setupDocument must be a function.');
281
+ const nodes = new NodeRegistry(projectDefinition.nodes);
282
+ return {
283
+ ...projectDefinition.root ? {
284
+ root: projectDefinition.root
285
+ } : {},
286
+ ...files ? {
287
+ files
288
+ } : {},
289
+ nodes,
290
+ ...projectDefinition.setupDocument ? {
291
+ setupDocument: projectDefinition.setupDocument
292
+ } : {},
293
+ resolveNode: (name)=>nodes.get(name)
294
+ };
295
+ }
296
+ const external_node_fs_namespaceObject = require("node:fs");
297
+ const external_tinyglobby_namespaceObject = require("tinyglobby");
298
+ const external_node_crypto_namespaceObject = require("node:crypto");
299
+ const supportedMetaKeys = new Set([
300
+ 'timeout',
301
+ 'continue-on-error'
302
+ ]);
303
+ function isMapping(value) {
304
+ return 'object' == typeof value && null !== value && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
305
+ }
306
+ function formatStep(index) {
307
+ return `step ${index + 1}`;
308
+ }
309
+ function normalizeMeta(value, index) {
310
+ if (void 0 === value) return {
311
+ continueOnError: false
312
+ };
313
+ if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} "$" must be a mapping.`, {
314
+ index
315
+ });
316
+ for (const key of Object.keys(value))if (!supportedMetaKeys.has(key)) throw new WorkflowParseError(`${formatStep(index)} has unsupported engine metadata "${key}".`, {
317
+ index,
318
+ key
319
+ });
320
+ const timeout = value.timeout;
321
+ if (void 0 !== timeout && ('number' != typeof timeout || !Number.isFinite(timeout) || timeout <= 0)) throw new WorkflowParseError(`${formatStep(index)} "$.timeout" must be a positive number of milliseconds.`, {
322
+ index,
323
+ timeout
324
+ });
325
+ const continueOnError = value['continue-on-error'];
326
+ if (void 0 !== continueOnError && 'boolean' != typeof continueOnError) throw new WorkflowParseError(`${formatStep(index)} "$.continue-on-error" must be a boolean.`, {
327
+ index,
328
+ continueOnError
329
+ });
330
+ return {
331
+ ...void 0 === timeout ? {} : {
332
+ timeoutMs: timeout
333
+ },
334
+ continueOnError: continueOnError ?? false
335
+ };
336
+ }
337
+ function validateCommonNodeInput(input, index) {
338
+ if (void 0 !== input.prompt && 'string' != typeof input.prompt) throw new WorkflowParseError(`${formatStep(index)} "prompt" must be a string.`, {
339
+ index,
340
+ prompt: input.prompt
341
+ });
342
+ }
343
+ function normalizeStep(value, index = 0) {
344
+ if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} must be a mapping.`, {
345
+ index
346
+ });
347
+ const entries = Object.entries(value);
348
+ if (1 !== entries.length) throw new WorkflowParseError(`${formatStep(index)} must contain exactly one node.`, {
349
+ index,
350
+ nodes: Object.keys(value)
351
+ });
352
+ const [node, rawValue] = entries[0];
353
+ if (0 === node.trim().length) throw new WorkflowParseError(`${formatStep(index)} node name must not be empty.`, {
354
+ index
355
+ });
356
+ if ('string' == typeof rawValue) return {
357
+ node,
358
+ input: {
359
+ prompt: rawValue
360
+ },
361
+ meta: {
362
+ continueOnError: false
363
+ }
364
+ };
365
+ if (!isMapping(rawValue)) throw new WorkflowParseError(`${formatStep(index)} value must be a string shorthand or a mapping.`, {
366
+ index,
367
+ node
368
+ });
369
+ const { $: rawMeta, ...input } = rawValue;
370
+ validateCommonNodeInput(input, index);
371
+ return {
372
+ node,
373
+ input,
374
+ meta: normalizeMeta(rawMeta, index)
375
+ };
376
+ }
377
+ function normalizeSteps(steps) {
378
+ if (!Array.isArray(steps)) throw new WorkflowParseError('Steps must be an array.');
379
+ return steps.map((step, index)=>normalizeStep(step, index));
380
+ }
381
+ function validateNodeOutput(output, node) {
382
+ if (void 0 === output) return;
383
+ if ('object' != typeof output || null === output || Array.isArray(output)) throw new NodeExecutionError(node, new TypeError('Node output must be an object or undefined.'));
384
+ if ('summary' in output && void 0 !== output.summary && 'string' != typeof output.summary) throw new NodeExecutionError(node, new TypeError('Node output summary must be a string.'));
385
+ return output;
386
+ }
387
+ function createStepResultBase(step, startedAt, phase, stepIndex) {
388
+ const endedAt = new Date();
389
+ return {
390
+ node: step.node,
391
+ phase,
392
+ stepIndex,
393
+ input: step.input,
394
+ meta: step.meta,
395
+ startedAt: startedAt.toISOString(),
396
+ endedAt: endedAt.toISOString(),
397
+ durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime())
398
+ };
399
+ }
400
+ async function executeNode(step, execute, phase, stepIndex) {
401
+ validateCommonNodeInput(step.input, stepIndex);
402
+ const startedAt = new Date();
403
+ const abortController = new AbortController();
404
+ const timeoutMs = step.meta.timeoutMs;
405
+ let timeout;
406
+ let timeoutError;
407
+ const execution = Promise.resolve().then(()=>execute(abortController.signal));
408
+ const timedExecution = void 0 === timeoutMs ? execution : Promise.race([
409
+ execution,
410
+ new Promise((_, reject)=>{
411
+ timeout = setTimeout(()=>{
412
+ timeoutError = new StepTimeoutError(timeoutMs, step.node);
413
+ abortController.abort(timeoutError);
414
+ reject(timeoutError);
415
+ }, timeoutMs);
416
+ })
417
+ ]);
418
+ try {
419
+ const output = validateNodeOutput(await timedExecution, step.node);
420
+ return {
421
+ ...createStepResultBase(step, startedAt, phase, stepIndex),
422
+ status: 'success',
423
+ continuedAfterError: false,
424
+ ...void 0 === output ? {} : {
425
+ output
426
+ }
427
+ };
428
+ } catch (error) {
429
+ return {
430
+ ...createStepResultBase(step, startedAt, phase, stepIndex),
431
+ status: 'failed',
432
+ continuedAfterError: step.meta.continueOnError,
433
+ error: normalizeNodeExecutionError(timeoutError ?? error, step.node)
434
+ };
435
+ } finally{
436
+ if (void 0 !== timeout) clearTimeout(timeout);
437
+ }
438
+ }
439
+ async function executeStep(step, node, target, context) {
440
+ const phase = 'case' === target.scope ? target.case.phase : target.document.phase;
441
+ const stepIndex = 'case' === target.scope ? target.case.stepIndex : target.document.stepIndex;
442
+ return executeNode(step, (signal)=>{
443
+ const common = {
444
+ input: step.input,
445
+ $: step.meta,
446
+ signal,
447
+ context
448
+ };
449
+ return 'case' === target.scope ? node.execute({
450
+ ...common,
451
+ scope: 'case',
452
+ case: target.case
453
+ }) : node.execute({
454
+ ...common,
455
+ scope: 'document',
456
+ document: target.document
457
+ });
458
+ }, phase, stepIndex);
459
+ }
460
+ function createDocumentRuntime(document, options) {
461
+ const documentRunId = options.createDocumentRunId?.() ?? (0, external_node_crypto_namespaceObject.randomUUID)();
462
+ const startedAt = new Date();
463
+ const beforeAll = [];
464
+ const afterAll = [];
465
+ const completedNodes = [];
466
+ const nodes = {
467
+ beforeAll: document.lifecycle.beforeAll.map((step)=>options.resolveNode(step.node)),
468
+ afterAll: document.lifecycle.afterAll.map((step)=>options.resolveNode(step.node))
469
+ };
470
+ const info = {
471
+ documentId: document.documentId,
472
+ documentRunId,
473
+ projectId: document.projectId,
474
+ sourcePath: document.sourcePath,
475
+ cases: document.cases.map((caseDefinition)=>caseDefinition.definition),
476
+ lifecycle: document.lifecycle,
477
+ env: Object.freeze({
478
+ ...process.env
479
+ })
480
+ };
481
+ const teardownStack = [];
482
+ let acceptingTeardowns = void 0 !== options.setupDocument;
483
+ let context;
484
+ let setupError;
485
+ let started = false;
486
+ let finishedResult;
487
+ const createResult = (teardownErrors = [])=>{
488
+ const endedAt = new Date();
489
+ return {
490
+ documentId: document.documentId,
491
+ documentRunId,
492
+ projectId: document.projectId,
493
+ sourcePath: document.sourcePath,
494
+ status: setupError || completedNodes.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
495
+ startedAt: startedAt.toISOString(),
496
+ endedAt: endedAt.toISOString(),
497
+ durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),
498
+ beforeAll: [
499
+ ...beforeAll
500
+ ],
501
+ afterAll: [
502
+ ...afterAll
503
+ ],
504
+ ...setupError ? {
505
+ setupError
506
+ } : {},
507
+ ...teardownErrors.length > 0 ? {
508
+ teardownErrors
509
+ } : {}
510
+ };
511
+ };
512
+ const runPhase = async (phase, results)=>{
513
+ for (const [stepIndex, step] of document.lifecycle[phase].entries()){
514
+ const documentContext = {
515
+ documentId: document.documentId,
516
+ documentRunId,
517
+ projectId: document.projectId,
518
+ sourcePath: document.sourcePath,
519
+ phase,
520
+ stepIndex,
521
+ completedNodes: Object.freeze([
522
+ ...completedNodes
523
+ ])
524
+ };
525
+ const stepInfo = {
526
+ scope: 'document',
527
+ node: step.node,
528
+ stepCount: document.lifecycle[phase].length,
529
+ document: documentContext
530
+ };
531
+ await options.onStepStart?.(stepInfo);
532
+ const result = await executeStep(step, nodes[phase][stepIndex], {
533
+ scope: 'document',
534
+ document: documentContext
535
+ }, context);
536
+ await options.onStepResult?.(stepInfo, result);
537
+ results.push(result);
538
+ completedNodes.push(result);
539
+ if ('failed' === result.status && !result.continuedAfterError) break;
540
+ }
541
+ };
542
+ const onTeardown = (teardown)=>{
543
+ if (!acceptingTeardowns) throw new WorkflowLifecycleError('onTeardown() can only be called while setupDocument is running.', {
544
+ documentId: document.documentId,
545
+ documentRunId
546
+ });
547
+ if ('function' != typeof teardown) throw new WorkflowLifecycleError('onTeardown() requires a teardown function.', {
548
+ documentId: document.documentId,
549
+ documentRunId
550
+ });
551
+ teardownStack.push({
552
+ registrationIndex: teardownStack.length,
553
+ teardown
554
+ });
555
+ };
556
+ return {
557
+ get context () {
558
+ return context;
559
+ },
560
+ get canRunCases () {
561
+ return started && !setupError && !beforeAll.some((step)=>'failed' === step.status);
562
+ },
563
+ async start () {
564
+ if (started) throw new WorkflowLifecycleError('Workflow document runtime has already started.', {
565
+ documentId: document.documentId,
566
+ documentRunId
567
+ });
568
+ started = true;
569
+ if (options.setupDocument) try {
570
+ context = await options.setupDocument({
571
+ ...info,
572
+ onTeardown
573
+ });
574
+ } catch (error) {
575
+ setupError = new WorkflowDocumentSetupError(error, {
576
+ documentId: document.documentId,
577
+ documentRunId
578
+ });
579
+ } finally{
580
+ acceptingTeardowns = false;
581
+ }
582
+ if (!setupError) await runPhase('beforeAll', beforeAll);
583
+ return createResult();
584
+ },
585
+ async finish () {
586
+ if (finishedResult) return finishedResult;
587
+ if (!started) throw new WorkflowLifecycleError('Workflow document runtime must start before it can finish.', {
588
+ documentId: document.documentId,
589
+ documentRunId
590
+ });
591
+ if (!setupError) await runPhase('afterAll', afterAll);
592
+ const statusBeforeTeardown = createResult().status;
593
+ const teardownErrors = [];
594
+ for (const { registrationIndex, teardown } of teardownStack.reverse())try {
595
+ await teardown({
596
+ ...info,
597
+ beforeAll: Object.freeze([
598
+ ...beforeAll
599
+ ]),
600
+ afterAll: Object.freeze([
601
+ ...afterAll
602
+ ]),
603
+ status: statusBeforeTeardown,
604
+ ...setupError ? {
605
+ setupError
606
+ } : {}
607
+ });
608
+ } catch (error) {
609
+ teardownErrors.push(new WorkflowDocumentTeardownError(error, {
610
+ documentId: document.documentId,
611
+ documentRunId,
612
+ registrationIndex
613
+ }));
614
+ }
615
+ finishedResult = createResult(teardownErrors);
616
+ await options.onResult?.(finishedResult);
617
+ return finishedResult;
618
+ }
619
+ };
620
+ }
621
+ async function runCollectedCase(collectedCase, options) {
622
+ const phases = {
623
+ beforeEach: options.beforeEach ?? [],
624
+ steps: collectedCase.definition.steps,
625
+ afterEach: options.afterEach ?? []
626
+ };
627
+ const nodes = {
628
+ beforeEach: phases.beforeEach.map((step)=>options.resolveNode(step.node)),
629
+ steps: phases.steps.map((step)=>options.resolveNode(step.node)),
630
+ afterEach: phases.afterEach.map((step)=>options.resolveNode(step.node))
631
+ };
632
+ const runId = options.createRunId?.() ?? (0, external_node_crypto_namespaceObject.randomUUID)();
633
+ const startedAt = new Date();
634
+ const beforeEach = [];
635
+ const steps = [];
636
+ const afterEach = [];
637
+ const completedNodes = [];
638
+ const runPhase = async (phase, results)=>{
639
+ for (const [stepIndex, step] of phases[phase].entries()){
640
+ const caseContext = {
641
+ caseId: collectedCase.caseId,
642
+ runId,
643
+ name: collectedCase.definition.name,
644
+ sourcePath: collectedCase.sourcePath,
645
+ caseIndex: collectedCase.caseIndex,
646
+ phase,
647
+ stepIndex,
648
+ completedSteps: Object.freeze([
649
+ ...steps
650
+ ]),
651
+ completedNodes: Object.freeze([
652
+ ...completedNodes
653
+ ])
654
+ };
655
+ const stepInfo = {
656
+ scope: 'case',
657
+ node: step.node,
658
+ stepCount: phases[phase].length,
659
+ case: caseContext
660
+ };
661
+ await options.onStepStart?.(stepInfo);
662
+ const result = await executeStep(step, nodes[phase][stepIndex], {
663
+ scope: 'case',
664
+ case: caseContext
665
+ }, options.context);
666
+ await options.onStepResult?.(stepInfo, result);
667
+ results.push(result);
668
+ completedNodes.push(result);
669
+ if ('failed' === result.status && !result.continuedAfterError) break;
670
+ }
671
+ };
672
+ await runPhase('beforeEach', beforeEach);
673
+ if (!beforeEach.some((step)=>'failed' === step.status)) await runPhase('steps', steps);
674
+ await runPhase('afterEach', afterEach);
675
+ const endedAt = new Date();
676
+ const result = {
677
+ caseId: collectedCase.caseId,
678
+ runId,
679
+ name: collectedCase.definition.name,
680
+ sourcePath: collectedCase.sourcePath,
681
+ caseIndex: collectedCase.caseIndex,
682
+ status: completedNodes.some((step)=>'failed' === step.status) ? 'failed' : 'success',
683
+ beforeEach,
684
+ steps,
685
+ afterEach,
686
+ startedAt: startedAt.toISOString(),
687
+ endedAt: endedAt.toISOString(),
688
+ durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime())
689
+ };
690
+ await options.onResult?.(result);
691
+ return result;
692
+ }
693
+ const asNotRun = (collectedCase, reason)=>({
694
+ caseId: collectedCase.caseId,
695
+ name: collectedCase.definition.name,
696
+ sourcePath: collectedCase.sourcePath,
697
+ caseIndex: collectedCase.caseIndex,
698
+ status: 'not-run',
699
+ notRunReason: reason
700
+ });
701
+ async function runWorkflowDocument(document, options) {
702
+ const createDocumentRunId = options.createDocumentRunId;
703
+ const createCaseRunId = options.createCaseRunId;
704
+ const runtime = createDocumentRuntime(document, {
705
+ resolveNode: options.resolveNode,
706
+ setupDocument: options.setupDocument,
707
+ onStepStart: options.onStepStart,
708
+ onStepResult: options.onStepResult,
709
+ onResult: options.onDocumentResult,
710
+ createDocumentRunId: createDocumentRunId ? ()=>createDocumentRunId(document) : void 0
711
+ });
712
+ const cases = [];
713
+ let runtimeStarted = false;
714
+ let documentResult;
715
+ let executionError;
716
+ try {
717
+ runtimeStarted = true;
718
+ await runtime.start();
719
+ if (runtime.canRunCases) for (const collectedCase of document.cases){
720
+ if (options.shouldStop?.()) {
721
+ cases.push(asNotRun(collectedCase, 'interrupted'));
722
+ continue;
723
+ }
724
+ await options.onCaseStart?.(collectedCase);
725
+ const run = await runCollectedCase(collectedCase, {
726
+ resolveNode: options.resolveNode,
727
+ beforeEach: document.lifecycle.beforeEach,
728
+ afterEach: document.lifecycle.afterEach,
729
+ context: runtime.context,
730
+ onStepStart: options.onStepStart,
731
+ onStepResult: options.onStepResult,
732
+ onResult: options.onCaseResult,
733
+ createRunId: createCaseRunId ? ()=>createCaseRunId(collectedCase) : void 0
734
+ });
735
+ cases.push({
736
+ caseId: collectedCase.caseId,
737
+ name: collectedCase.definition.name,
738
+ sourcePath: collectedCase.sourcePath,
739
+ caseIndex: collectedCase.caseIndex,
740
+ status: run.status,
741
+ run
742
+ });
743
+ }
744
+ else cases.push(...document.cases.map((collectedCase)=>asNotRun(collectedCase, 'document-start-failed')));
745
+ } catch (error) {
746
+ executionError = error;
747
+ } finally{
748
+ if (runtimeStarted) try {
749
+ documentResult = await runtime.finish();
750
+ } catch (error) {
751
+ executionError ??= error;
752
+ }
753
+ }
754
+ if (executionError) throw executionError;
755
+ if (!documentResult) throw new Error(`Workflow document "${document.sourcePath}" did not produce a result.`);
756
+ return {
757
+ document: documentResult,
758
+ cases
759
+ };
760
+ }
761
+ const external_js_yaml_namespaceObject = require("js-yaml");
762
+ const collect_isMapping = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
763
+ const rejectUnknownKeys = (value, allowed, label)=>{
764
+ const allowedSet = new Set(allowed);
765
+ const unknown = Object.keys(value).filter((key)=>!allowedSet.has(key));
766
+ if (unknown.length) throw new WorkflowParseError(`${label} has unsupported field "${unknown[0]}".`, {
767
+ field: unknown[0]
768
+ });
769
+ };
770
+ const createCaseId = (projectId, sourcePath, caseIndex)=>(0, external_node_crypto_namespaceObject.createHash)('sha256').update(JSON.stringify([
771
+ projectId,
772
+ sourcePath,
773
+ caseIndex
774
+ ])).digest('hex');
775
+ const createWorkflowDocumentId = (projectId, sourcePath)=>(0, external_node_crypto_namespaceObject.createHash)('sha256').update(JSON.stringify([
776
+ projectId,
777
+ sourcePath
778
+ ])).digest('hex');
779
+ function collectWorkflowDocument(source, options) {
780
+ let parsed;
781
+ try {
782
+ parsed = (0, external_js_yaml_namespaceObject.load)((0, external_node_fs_namespaceObject.readFileSync)(source.absolutePath, 'utf8'), {
783
+ schema: external_js_yaml_namespaceObject.JSON_SCHEMA
784
+ });
785
+ } catch (error) {
786
+ throw new WorkflowParseError(`Failed to parse workflow document "${source.sourcePath}".`, {
787
+ sourcePath: source.sourcePath
788
+ }, error);
789
+ }
790
+ if (!collect_isMapping(parsed)) throw new WorkflowParseError('Workflow document must be a mapping.');
791
+ rejectUnknownKeys(parsed, [
792
+ 'beforeAll',
793
+ 'beforeEach',
794
+ 'cases',
795
+ 'afterEach',
796
+ 'afterAll'
797
+ ], 'Workflow document');
798
+ if (!Array.isArray(parsed.cases) || 0 === parsed.cases.length) throw new WorkflowParseError('Workflow document must contain a non-empty cases array.');
799
+ const sourcePath = source.sourcePath.split(external_node_path_namespaceObject.sep).join('/');
800
+ const normalizeLifecycle = (phase)=>{
801
+ const value = parsed[phase];
802
+ if (void 0 === value) return [];
803
+ if (!Array.isArray(value)) throw new WorkflowParseError(`Workflow document ${phase} must be an array.`, {
804
+ phase
805
+ });
806
+ return normalizeSteps(value).map((normalized, stepIndex)=>{
807
+ const resolved = options.resolveNode(normalized.node);
808
+ if (!resolved) throw new WorkflowParseError(`Workflow document ${phase} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
809
+ phase,
810
+ stepIndex,
811
+ node: normalized.node
812
+ });
813
+ return normalized;
814
+ });
815
+ };
816
+ const lifecycle = {
817
+ beforeAll: normalizeLifecycle('beforeAll'),
818
+ beforeEach: normalizeLifecycle('beforeEach'),
819
+ afterEach: normalizeLifecycle('afterEach'),
820
+ afterAll: normalizeLifecycle('afterAll')
821
+ };
822
+ const ids = new Set();
823
+ const cases = parsed.cases.map((definition, caseIndex)=>{
824
+ if (!collect_isMapping(definition)) throw new WorkflowParseError(`Case ${caseIndex + 1} must be a mapping.`, {
825
+ caseIndex
826
+ });
827
+ rejectUnknownKeys(definition, [
828
+ 'name',
829
+ 'steps'
830
+ ], `Case ${caseIndex + 1}`);
831
+ if ('string' != typeof definition.name || 0 === definition.name.trim().length) throw new WorkflowParseError(`Case ${caseIndex + 1} name must be a non-empty string.`, {
832
+ caseIndex
833
+ });
834
+ if (!Array.isArray(definition.steps) || 0 === definition.steps.length) throw new WorkflowParseError(`Case ${caseIndex + 1} steps must be a non-empty array.`, {
835
+ caseIndex
836
+ });
837
+ const steps = normalizeSteps(definition.steps).map((normalized, stepIndex)=>{
838
+ if (!options.resolveNode(normalized.node)) throw new WorkflowParseError(`Case ${caseIndex + 1} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
839
+ caseIndex,
840
+ stepIndex,
841
+ node: normalized.node
842
+ });
843
+ return normalized;
844
+ });
845
+ const caseId = createCaseId(source.projectId, sourcePath, caseIndex);
846
+ if (ids.has(caseId)) throw new WorkflowParseError(`Case id collision: ${caseId}.`, {
847
+ caseId
848
+ });
849
+ ids.add(caseId);
850
+ return {
851
+ caseId,
852
+ projectId: source.projectId,
853
+ sourcePath,
854
+ caseIndex,
855
+ definition: {
856
+ name: definition.name,
857
+ steps
858
+ }
859
+ };
860
+ });
861
+ return {
862
+ documentId: createWorkflowDocumentId(source.projectId, sourcePath),
863
+ projectId: source.projectId,
864
+ sourcePath,
865
+ lifecycle,
866
+ cases
867
+ };
868
+ }
869
+ const writeJson = (path, value)=>{
870
+ (0, external_node_fs_namespaceObject.mkdirSync)((0, external_node_path_namespaceObject.dirname)(path), {
871
+ recursive: true
872
+ });
873
+ (0, external_node_fs_namespaceObject.writeFileSync)(path, JSON.stringify(value, null, 2));
874
+ };
875
+ const toPosix = (value)=>value.split(external_node_path_namespaceObject.sep).join('/');
876
+ const caseRunResultPath = (result)=>toPosix((0, external_node_path_namespaceObject.join)('runs', result.caseId, `${result.runId}.json`));
877
+ const workflowDocumentRunResultPath = (result)=>toPosix((0, external_node_path_namespaceObject.join)('documents', result.documentId, `${result.documentRunId}.json`));
878
+ const collectionErrorPath = (sourcePath)=>{
879
+ const id = (0, external_node_crypto_namespaceObject.createHash)('sha256').update(sourcePath).digest('hex');
880
+ return toPosix((0, external_node_path_namespaceObject.join)('collection-errors', `${id}.json`));
881
+ };
882
+ const writeWorkflowDocumentRunResult = (resultDir, result)=>{
883
+ writeJson((0, external_node_path_namespaceObject.join)(resultDir, workflowDocumentRunResultPath(result)), result);
884
+ };
885
+ const writeCaseRunResult = (resultDir, result)=>{
886
+ writeJson((0, external_node_path_namespaceObject.join)(resultDir, caseRunResultPath(result)), result);
887
+ };
888
+ const writeCollectionError = (resultDir, collectionError)=>{
889
+ writeJson((0, external_node_path_namespaceObject.join)(resultDir, collectionErrorPath(collectionError.sourcePath)), {
890
+ kind: 'collection-error',
891
+ ...collectionError
892
+ });
893
+ };
894
+ const writeWorkflowProjectRunResult = (resultDir, options)=>{
895
+ const { result } = options;
896
+ writeJson((0, external_node_path_namespaceObject.join)(resultDir, 'project.json'), {
897
+ version: 3,
898
+ projectId: options.projectId,
899
+ projectRoot: options.projectRoot,
900
+ ...options.configPath ? {
901
+ configPath: options.configPath
902
+ } : {},
903
+ fileSelection: {
904
+ include: [
905
+ ...options.fileSelection.include
906
+ ],
907
+ ...options.fileSelection.exclude ? {
908
+ exclude: [
909
+ ...options.fileSelection.exclude
910
+ ]
911
+ } : {}
912
+ },
913
+ sources: options.sources,
914
+ status: result.status,
915
+ exitCode: result.exitCode,
916
+ resultDir: result.resultDir,
917
+ summary: result.summary,
918
+ cases: result.cases.map((caseResult)=>({
919
+ caseId: caseResult.caseId,
920
+ name: caseResult.name,
921
+ sourcePath: caseResult.sourcePath,
922
+ caseIndex: caseResult.caseIndex,
923
+ status: caseResult.status,
924
+ ...caseResult.run ? {
925
+ resultFile: caseRunResultPath(caseResult.run)
926
+ } : {},
927
+ ...caseResult.notRunReason ? {
928
+ notRunReason: caseResult.notRunReason
929
+ } : {}
930
+ })),
931
+ documents: result.documents.map((document)=>({
932
+ documentId: document.documentId,
933
+ sourcePath: document.sourcePath,
934
+ status: document.status,
935
+ resultFile: workflowDocumentRunResultPath(document)
936
+ })),
937
+ collectionErrors: result.collectionErrors.map((error)=>({
938
+ sourcePath: error.sourcePath,
939
+ errorFile: collectionErrorPath(error.sourcePath)
940
+ }))
941
+ });
942
+ };
943
+ const CONFIG_NAMES = [
944
+ 'midscene.workflow.config.cjs',
945
+ 'midscene.workflow.config.js'
946
+ ];
947
+ const ALWAYS_IGNORED_PATTERNS = [
948
+ '.git/**',
949
+ '.midscene/**',
950
+ 'node_modules/**',
951
+ '**/.git/**',
952
+ '**/.midscene/**',
953
+ '**/node_modules/**'
954
+ ];
955
+ const DEFAULT_WORKFLOW_FILE_SELECTION = {
956
+ include: [
957
+ '**/*.{yaml,yml}'
958
+ ]
959
+ };
960
+ const workflow_runner_toPosix = (value)=>value.split(external_node_path_namespaceObject.sep).join('/');
961
+ const discoverWorkflowFiles = (projectRoot, selection = DEFAULT_WORKFLOW_FILE_SELECTION)=>{
962
+ const root = (0, external_node_path_namespaceObject.resolve)(projectRoot);
963
+ const normalized = validateWorkflowFileSelection(selection);
964
+ if (!normalized) throw new TypeError('Workflow file selection is required.');
965
+ const files = (0, external_tinyglobby_namespaceObject.globSync)(normalized.include, {
966
+ absolute: true,
967
+ caseSensitiveMatch: false,
968
+ cwd: root,
969
+ dot: true,
970
+ expandDirectories: false,
971
+ followSymbolicLinks: false,
972
+ ignore: [
973
+ ...ALWAYS_IGNORED_PATTERNS,
974
+ ...normalized.exclude ?? []
975
+ ],
976
+ onlyFiles: true
977
+ }).filter((file)=>/\.ya?ml$/i.test(file));
978
+ return [
979
+ ...new Set(files.map((file)=>(0, external_node_path_namespaceObject.resolve)(file)))
980
+ ].sort((a, b)=>{
981
+ const relativeA = workflow_runner_toPosix((0, external_node_path_namespaceObject.relative)(root, a));
982
+ const relativeB = workflow_runner_toPosix((0, external_node_path_namespaceObject.relative)(root, b));
983
+ return relativeA < relativeB ? -1 : relativeA > relativeB ? 1 : 0;
984
+ });
985
+ };
986
+ const discoverWorkflowConfig = (projectRoot)=>CONFIG_NAMES.map((name)=>(0, external_node_path_namespaceObject.join)((0, external_node_path_namespaceObject.resolve)(projectRoot), name)).find(external_node_fs_namespaceObject.existsSync);
987
+ const defaultResultDir = (projectRoot)=>(0, external_node_path_namespaceObject.join)(projectRoot, '.midscene', 'workflow-results', `${Date.now()}-${process.pid}`);
988
+ const assertDirectory = (path, label)=>{
989
+ if (!(0, external_node_fs_namespaceObject.existsSync)(path) || !(0, external_node_fs_namespaceObject.statSync)(path).isDirectory()) throw new Error(`${label} does not exist or is not a directory: ${path}`);
990
+ };
991
+ const asCollectionError = (sourcePath, error)=>({
992
+ sourcePath,
993
+ error: error instanceof WorkflowError ? error : new WorkflowParseError(`Failed to collect workflow document "${sourcePath}": ${error instanceof Error ? error.message : String(error)}`, {
994
+ sourcePath
995
+ }, error)
996
+ });
997
+ const workflow_runner_asNotRun = (collectedCase, reason)=>({
998
+ caseId: collectedCase.caseId,
999
+ name: collectedCase.definition.name,
1000
+ sourcePath: collectedCase.sourcePath,
1001
+ caseIndex: collectedCase.caseIndex,
1002
+ status: 'not-run',
1003
+ notRunReason: reason
1004
+ });
1005
+ const summarize = (cases, documents, collectionErrors)=>({
1006
+ total: cases.length,
1007
+ passed: cases.filter((caseResult)=>'success' === caseResult.status).length,
1008
+ failed: cases.filter((caseResult)=>'failed' === caseResult.status).length,
1009
+ notRun: cases.filter((caseResult)=>'not-run' === caseResult.status).length,
1010
+ collectionErrors: collectionErrors.length,
1011
+ documentFailures: documents.filter((document)=>'failed' === document.status).length
1012
+ });
1013
+ const stepPosition = (info)=>'case' === info.scope ? info.case : info.document;
1014
+ const workflow_runner_formatStep = (info)=>{
1015
+ const position = stepPosition(info);
1016
+ const phase = 'steps' === position.phase ? 'step' : position.phase;
1017
+ return `${phase} ${position.stepIndex + 1}/${info.stepCount}: ${info.node}`;
1018
+ };
1019
+ const formatStepResult = (info, result)=>{
1020
+ const indent = 'case' === info.scope ? ' ' : ' ';
1021
+ const symbol = 'success' === result.status ? '✓' : '✗';
1022
+ const error = result.error ? ` — ${result.error.message}` : '';
1023
+ const continuation = result.continuedAfterError ? '; continuing' : '';
1024
+ return `${indent}${symbol} ${workflow_runner_formatStep(info)} (${result.durationMs} ms)${error}${continuation}`;
1025
+ };
1026
+ const formatNotRunReason = (reason)=>'document-start-failed' === reason ? 'document start failed' : 'run interrupted';
1027
+ async function runWorkflowProject(options = {}) {
1028
+ const cwd = (0, external_node_path_namespaceObject.resolve)(options.cwd ?? process.cwd());
1029
+ assertDirectory(cwd, 'Workflow working directory');
1030
+ const cliProjectRoot = options.projectRoot ? (0, external_node_path_namespaceObject.resolve)(cwd, options.projectRoot) : void 0;
1031
+ if (cliProjectRoot) assertDirectory(cliProjectRoot, 'Workflow project directory');
1032
+ const configSearchRoot = cliProjectRoot ?? cwd;
1033
+ const configPath = options.configPath ? (0, external_node_path_namespaceObject.resolve)(configSearchRoot, options.configPath) : discoverWorkflowConfig(configSearchRoot);
1034
+ if (options.configPath && (!configPath || !(0, external_node_fs_namespaceObject.existsSync)(configPath))) throw new Error(`Workflow config does not exist: ${configPath}`);
1035
+ const project = loadWorkflowProjectSync(configPath);
1036
+ const projectRoot = cliProjectRoot ? cliProjectRoot : project.root ? (0, external_node_path_namespaceObject.resolve)(configPath ? (0, external_node_path_namespaceObject.dirname)(configPath) : configSearchRoot, project.root) : cwd;
1037
+ assertDirectory(projectRoot, 'Workflow project root');
1038
+ const fileSelection = project.files ?? DEFAULT_WORKFLOW_FILE_SELECTION;
1039
+ const files = discoverWorkflowFiles(projectRoot, fileSelection);
1040
+ if (0 === files.length) throw new Error(`No workflow YAML files found in ${projectRoot}.`);
1041
+ const resultDir = options.resultDir ? (0, external_node_path_namespaceObject.resolve)(cwd, options.resultDir) : defaultResultDir(projectRoot);
1042
+ (0, external_node_fs_namespaceObject.mkdirSync)(resultDir, {
1043
+ recursive: true
1044
+ });
1045
+ const projectId = (0, external_node_path_namespaceObject.basename)(projectRoot);
1046
+ const sources = files.map((absolutePath)=>({
1047
+ projectId,
1048
+ sourcePath: workflow_runner_toPosix((0, external_node_path_namespaceObject.relative)(projectRoot, absolutePath)),
1049
+ absolutePath
1050
+ }));
1051
+ const collectedDocuments = [];
1052
+ const collectionErrors = [];
1053
+ const collectedCaseIds = new Set();
1054
+ const progress = options.onProgress ?? (()=>{});
1055
+ for (const source of sources)try {
1056
+ const document = collectWorkflowDocument(source, {
1057
+ resolveNode: project.resolveNode
1058
+ });
1059
+ const collided = document.cases.find((collectedCase)=>collectedCaseIds.has(collectedCase.caseId));
1060
+ if (collided) throw new WorkflowParseError(`Case id collision: ${collided.caseId}.`, {
1061
+ caseId: collided.caseId,
1062
+ sourcePath: source.sourcePath
1063
+ });
1064
+ for (const collectedCase of document.cases)collectedCaseIds.add(collectedCase.caseId);
1065
+ collectedDocuments.push({
1066
+ document
1067
+ });
1068
+ } catch (error) {
1069
+ const collectionError = asCollectionError(source.sourcePath, error);
1070
+ collectionErrors.push(collectionError);
1071
+ writeCollectionError(resultDir, collectionError);
1072
+ }
1073
+ const collectedCaseCount = collectedDocuments.reduce((total, { document })=>total + document.cases.length, 0);
1074
+ progress(`midscene-workflow: collected ${collectedDocuments.length} documents, ${collectedCaseCount} cases, ${collectionErrors.length} collection errors`);
1075
+ for (const collectionError of collectionErrors)progress(`✗ collection: ${collectionError.sourcePath} — ${collectionError.error.message}`);
1076
+ const cases = [];
1077
+ const documents = [];
1078
+ let interrupted = false;
1079
+ const handleSignal = ()=>{
1080
+ interrupted = true;
1081
+ };
1082
+ process.on('SIGINT', handleSignal);
1083
+ process.on('SIGTERM', handleSignal);
1084
+ try {
1085
+ for (const [documentIndex, { document }] of collectedDocuments.entries()){
1086
+ if (interrupted) {
1087
+ progress(`[document ${documentIndex + 1}/${collectedDocuments.length}] ${document.sourcePath}`);
1088
+ const notRunCases = document.cases.map((collectedCase)=>workflow_runner_asNotRun(collectedCase, 'interrupted'));
1089
+ for (const outcome of notRunCases)progress(` - case ${outcome.caseIndex + 1}/${document.cases.length}: ${outcome.name} (not run: run interrupted)`);
1090
+ progress(`- document ${documentIndex + 1}/${collectedDocuments.length}: ${document.sourcePath} (not run: run interrupted)`);
1091
+ cases.push(...notRunCases);
1092
+ continue;
1093
+ }
1094
+ progress(`[document ${documentIndex + 1}/${collectedDocuments.length}] ${document.sourcePath}`);
1095
+ const execution = await runWorkflowDocument(document, {
1096
+ resolveNode: project.nodes.require.bind(project.nodes),
1097
+ setupDocument: project.setupDocument,
1098
+ shouldStop: ()=>interrupted,
1099
+ onCaseStart: (collectedCase)=>{
1100
+ progress(` [case ${collectedCase.caseIndex + 1}/${document.cases.length}] ${collectedCase.definition.name}`);
1101
+ },
1102
+ onStepStart: (info)=>{
1103
+ const indent = 'case' === info.scope ? ' ' : ' ';
1104
+ progress(`${indent}→ ${workflow_runner_formatStep(info)}`);
1105
+ },
1106
+ onStepResult: (info, result)=>{
1107
+ progress(formatStepResult(info, result));
1108
+ },
1109
+ onCaseResult: (run)=>{
1110
+ writeCaseRunResult(resultDir, run);
1111
+ const symbol = 'success' === run.status ? '✓' : '✗';
1112
+ progress(` ${symbol} case ${run.caseIndex + 1}/${document.cases.length}: ${run.name} (${run.durationMs} ms)`);
1113
+ },
1114
+ onDocumentResult: (documentResult)=>writeWorkflowDocumentRunResult(resultDir, documentResult)
1115
+ });
1116
+ for (const outcome of execution.cases)if ('not-run' === outcome.status && outcome.notRunReason) progress(` - case ${outcome.caseIndex + 1}/${document.cases.length}: ${outcome.name} (not run: ${formatNotRunReason(outcome.notRunReason)})`);
1117
+ const documentSymbol = 'success' === execution.document.status ? '✓' : '✗';
1118
+ const documentError = execution.document.setupError ?? execution.document.teardownErrors?.[0];
1119
+ progress(`${documentSymbol} document ${documentIndex + 1}/${collectedDocuments.length}: ${document.sourcePath} (${execution.document.durationMs} ms)${documentError ? ` — ${documentError.message}` : ''}`);
1120
+ cases.push(...execution.cases);
1121
+ documents.push(execution.document);
1122
+ }
1123
+ } finally{
1124
+ process.off('SIGINT', handleSignal);
1125
+ process.off('SIGTERM', handleSignal);
1126
+ }
1127
+ const summary = summarize(cases, documents, collectionErrors);
1128
+ const failed = interrupted || summary.failed > 0 || summary.notRun > 0 || summary.collectionErrors > 0 || summary.documentFailures > 0;
1129
+ const result = {
1130
+ status: failed ? 'failed' : 'success',
1131
+ exitCode: failed ? 1 : 0,
1132
+ resultDir,
1133
+ summary,
1134
+ cases,
1135
+ documents,
1136
+ collectionErrors
1137
+ };
1138
+ writeWorkflowProjectRunResult(resultDir, {
1139
+ projectId,
1140
+ projectRoot,
1141
+ ...configPath ? {
1142
+ configPath
1143
+ } : {},
1144
+ fileSelection,
1145
+ sources,
1146
+ result
1147
+ });
1148
+ return result;
1149
+ }
1150
+ exports.DEFAULT_WORKFLOW_FILE_SELECTION = __webpack_exports__.DEFAULT_WORKFLOW_FILE_SELECTION;
1151
+ exports.defineWorkflowProject = __webpack_exports__.defineWorkflowProject;
1152
+ exports.discoverWorkflowConfig = __webpack_exports__.discoverWorkflowConfig;
1153
+ exports.discoverWorkflowFiles = __webpack_exports__.discoverWorkflowFiles;
1154
+ exports.loadWorkflowProjectSync = __webpack_exports__.loadWorkflowProjectSync;
1155
+ exports.runWorkflowProject = __webpack_exports__.runWorkflowProject;
1156
+ exports.validateWorkflowFileSelection = __webpack_exports__.validateWorkflowFileSelection;
1157
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
1158
+ "DEFAULT_WORKFLOW_FILE_SELECTION",
1159
+ "defineWorkflowProject",
1160
+ "discoverWorkflowConfig",
1161
+ "discoverWorkflowFiles",
1162
+ "loadWorkflowProjectSync",
1163
+ "runWorkflowProject",
1164
+ "validateWorkflowFileSelection"
1165
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
1166
+ Object.defineProperty(exports, '__esModule', {
1167
+ value: true
1168
+ });
1169
+
1170
+ //# sourceMappingURL=index.js.map