flowstride 1.0.19 → 1.0.20-rc.6
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/NATIVE_CI.md +179 -0
- package/dist/adapters/web/playwright.d.ts +1 -0
- package/dist/adapters/web/playwright.js +24 -18
- package/dist/ci/config.d.ts +3 -0
- package/dist/ci/config.js +77 -0
- package/dist/ci/deadline.d.ts +2 -0
- package/dist/ci/deadline.js +16 -0
- package/dist/ci/init.d.ts +1 -0
- package/dist/ci/init.js +21 -0
- package/dist/ci/runtime.d.ts +19 -0
- package/dist/ci/runtime.js +203 -0
- package/dist/cli/commands/run.js +17 -35
- package/dist/cli/index.js +78 -54
- package/dist/cli/manual_server/workspace_server.js +44 -32
- package/dist/cli/utils/cloudSession.d.ts +7 -0
- package/dist/cli/utils/cloudSession.js +37 -0
- package/dist/cli/utils/proInstaller.d.ts +5 -0
- package/dist/cli/utils/proInstaller.js +141 -0
- package/dist/game/bridge.d.ts +13 -0
- package/dist/game/bridge.js +15 -0
- package/dist/game/sdk.d.ts +27 -0
- package/dist/game/sdk.js +256 -0
- package/dist/game/three-bridge.d.ts +18 -0
- package/dist/game/three-bridge.js +59 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +9 -0
- package/dist/manual/security.d.ts +183 -0
- package/dist/manual/security.js +106 -0
- package/dist/runner/data_generators.d.ts +1 -0
- package/dist/runner/data_generators.js +3 -0
- package/dist/runner/dom_stripper.js +3 -0
- package/dist/runner/flow_orchestrator.d.ts +5 -1
- package/dist/runner/flow_orchestrator.js +34 -19
- package/dist/runner/flow_worker.d.ts +1 -0
- package/dist/runner/flow_worker.js +56 -16
- package/dist/runner/persistent_vault.js +2 -1
- package/dist/runner/plugin_manager.d.ts +1 -0
- package/dist/runner/plugin_manager.js +35 -1
- package/dist/security/localDashboard.d.ts +2 -0
- package/dist/security/localDashboard.js +22 -0
- package/dist/security/redaction.d.ts +4 -0
- package/dist/security/redaction.js +49 -0
- package/dist/types/index.d.ts +43 -0
- package/dist/utils/cloud.d.ts +6 -0
- package/dist/utils/cloud.js +33 -0
- package/dist/utils/projectPaths.d.ts +11 -0
- package/dist/utils/projectPaths.js +90 -0
- package/examples/native-game/flows/canvas.flow +13 -0
- package/examples/native-game/flowstride.config.json +12 -0
- package/examples/native-game/server.mjs +12 -0
- package/package.json +27 -11
- package/src/ui/dist/assets/index-DVwp0K-J.js +462 -0
- package/src/ui/dist/index.html +1 -1
- package/src/ui/dist/assets/index-DIC22ZK_.js +0 -638
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare function sanitizeManualHtml(value: unknown): string;
|
|
3
|
+
export declare const manualRunSchema: z.ZodObject<{
|
|
4
|
+
name: z.ZodString;
|
|
5
|
+
status: z.ZodEnum<{
|
|
6
|
+
PASS: "PASS";
|
|
7
|
+
FAIL: "FAIL";
|
|
8
|
+
INCOMPLETE: "INCOMPLETE";
|
|
9
|
+
"NO STEPS": "NO STEPS";
|
|
10
|
+
}>;
|
|
11
|
+
metrics: z.ZodObject<{
|
|
12
|
+
total: z.ZodNumber;
|
|
13
|
+
passed: z.ZodNumber;
|
|
14
|
+
failed: z.ZodNumber;
|
|
15
|
+
pending: z.ZodNumber;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
canvasData: z.ZodObject<{
|
|
18
|
+
nodes: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
19
|
+
id: z.ZodNumber;
|
|
20
|
+
title: z.ZodOptional<z.ZodString>;
|
|
21
|
+
visual_step: z.ZodOptional<z.ZodString>;
|
|
22
|
+
description: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
23
|
+
steps: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
24
|
+
expected_result: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
25
|
+
references: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
26
|
+
defect_report: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
27
|
+
defect_media: z.ZodOptional<z.ZodString>;
|
|
28
|
+
assigned_to: z.ZodOptional<z.ZodString>;
|
|
29
|
+
priority: z.ZodOptional<z.ZodString>;
|
|
30
|
+
x: z.ZodOptional<z.ZodNumber>;
|
|
31
|
+
y: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
33
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
is_active: z.ZodOptional<z.ZodBoolean>;
|
|
35
|
+
}, z.core.$loose>>>;
|
|
36
|
+
groups: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
37
|
+
id: z.ZodNumber;
|
|
38
|
+
title: z.ZodOptional<z.ZodString>;
|
|
39
|
+
x: z.ZodOptional<z.ZodNumber>;
|
|
40
|
+
y: z.ZodOptional<z.ZodNumber>;
|
|
41
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
42
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
43
|
+
is_active: z.ZodOptional<z.ZodBoolean>;
|
|
44
|
+
}, z.core.$loose>>>;
|
|
45
|
+
texts: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
46
|
+
id: z.ZodNumber;
|
|
47
|
+
content: z.ZodOptional<z.ZodString>;
|
|
48
|
+
x: z.ZodOptional<z.ZodNumber>;
|
|
49
|
+
y: z.ZodOptional<z.ZodNumber>;
|
|
50
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
51
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
52
|
+
is_active: z.ZodOptional<z.ZodBoolean>;
|
|
53
|
+
}, z.core.$loose>>>;
|
|
54
|
+
connections: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
55
|
+
id: z.ZodNumber;
|
|
56
|
+
}, z.core.$loose>>>;
|
|
57
|
+
executionReport: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
58
|
+
status: z.ZodEnum<{
|
|
59
|
+
fail: "fail";
|
|
60
|
+
passed: "passed";
|
|
61
|
+
failed: "failed";
|
|
62
|
+
skipped: "skipped";
|
|
63
|
+
success: "success";
|
|
64
|
+
Pending: "Pending";
|
|
65
|
+
Current: "Current";
|
|
66
|
+
Passed: "Passed";
|
|
67
|
+
Failed: "Failed";
|
|
68
|
+
Blocked: "Blocked";
|
|
69
|
+
Skipped: "Skipped";
|
|
70
|
+
Pass: "Pass";
|
|
71
|
+
Fail: "Fail";
|
|
72
|
+
Success: "Success";
|
|
73
|
+
pass: "pass";
|
|
74
|
+
pending: "pending";
|
|
75
|
+
blocked: "blocked";
|
|
76
|
+
}>;
|
|
77
|
+
group_title: z.ZodOptional<z.ZodString>;
|
|
78
|
+
id: z.ZodNumber;
|
|
79
|
+
title: z.ZodOptional<z.ZodString>;
|
|
80
|
+
visual_step: z.ZodOptional<z.ZodString>;
|
|
81
|
+
description: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
82
|
+
steps: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
83
|
+
expected_result: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
84
|
+
references: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
85
|
+
defect_report: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
86
|
+
defect_media: z.ZodOptional<z.ZodString>;
|
|
87
|
+
assigned_to: z.ZodOptional<z.ZodString>;
|
|
88
|
+
priority: z.ZodOptional<z.ZodString>;
|
|
89
|
+
x: z.ZodOptional<z.ZodNumber>;
|
|
90
|
+
y: z.ZodOptional<z.ZodNumber>;
|
|
91
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
92
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
93
|
+
is_active: z.ZodOptional<z.ZodBoolean>;
|
|
94
|
+
}, z.core.$loose>>>;
|
|
95
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
96
|
+
}, z.core.$loose>;
|
|
97
|
+
}, z.core.$strip>;
|
|
98
|
+
export declare function sanitizeStoredCanvas(value: unknown): Record<string, unknown>;
|
|
99
|
+
export declare const manualCanvasSchema: z.ZodObject<{
|
|
100
|
+
nodes: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
101
|
+
id: z.ZodNumber;
|
|
102
|
+
title: z.ZodOptional<z.ZodString>;
|
|
103
|
+
visual_step: z.ZodOptional<z.ZodString>;
|
|
104
|
+
description: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
105
|
+
steps: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
106
|
+
expected_result: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
107
|
+
references: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
108
|
+
defect_report: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
109
|
+
defect_media: z.ZodOptional<z.ZodString>;
|
|
110
|
+
assigned_to: z.ZodOptional<z.ZodString>;
|
|
111
|
+
priority: z.ZodOptional<z.ZodString>;
|
|
112
|
+
x: z.ZodOptional<z.ZodNumber>;
|
|
113
|
+
y: z.ZodOptional<z.ZodNumber>;
|
|
114
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
115
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
116
|
+
is_active: z.ZodOptional<z.ZodBoolean>;
|
|
117
|
+
}, z.core.$loose>>>;
|
|
118
|
+
groups: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
119
|
+
id: z.ZodNumber;
|
|
120
|
+
title: z.ZodOptional<z.ZodString>;
|
|
121
|
+
x: z.ZodOptional<z.ZodNumber>;
|
|
122
|
+
y: z.ZodOptional<z.ZodNumber>;
|
|
123
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
124
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
125
|
+
is_active: z.ZodOptional<z.ZodBoolean>;
|
|
126
|
+
}, z.core.$loose>>>;
|
|
127
|
+
texts: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
128
|
+
id: z.ZodNumber;
|
|
129
|
+
content: z.ZodOptional<z.ZodString>;
|
|
130
|
+
x: z.ZodOptional<z.ZodNumber>;
|
|
131
|
+
y: z.ZodOptional<z.ZodNumber>;
|
|
132
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
133
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
134
|
+
is_active: z.ZodOptional<z.ZodBoolean>;
|
|
135
|
+
}, z.core.$loose>>>;
|
|
136
|
+
connections: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
137
|
+
id: z.ZodNumber;
|
|
138
|
+
}, z.core.$loose>>>;
|
|
139
|
+
executionReport: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
140
|
+
status: z.ZodEnum<{
|
|
141
|
+
fail: "fail";
|
|
142
|
+
passed: "passed";
|
|
143
|
+
failed: "failed";
|
|
144
|
+
skipped: "skipped";
|
|
145
|
+
success: "success";
|
|
146
|
+
Pending: "Pending";
|
|
147
|
+
Current: "Current";
|
|
148
|
+
Passed: "Passed";
|
|
149
|
+
Failed: "Failed";
|
|
150
|
+
Blocked: "Blocked";
|
|
151
|
+
Skipped: "Skipped";
|
|
152
|
+
Pass: "Pass";
|
|
153
|
+
Fail: "Fail";
|
|
154
|
+
Success: "Success";
|
|
155
|
+
pass: "pass";
|
|
156
|
+
pending: "pending";
|
|
157
|
+
blocked: "blocked";
|
|
158
|
+
}>;
|
|
159
|
+
group_title: z.ZodOptional<z.ZodString>;
|
|
160
|
+
id: z.ZodNumber;
|
|
161
|
+
title: z.ZodOptional<z.ZodString>;
|
|
162
|
+
visual_step: z.ZodOptional<z.ZodString>;
|
|
163
|
+
description: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
164
|
+
steps: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
165
|
+
expected_result: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
166
|
+
references: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
167
|
+
defect_report: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
168
|
+
defect_media: z.ZodOptional<z.ZodString>;
|
|
169
|
+
assigned_to: z.ZodOptional<z.ZodString>;
|
|
170
|
+
priority: z.ZodOptional<z.ZodString>;
|
|
171
|
+
x: z.ZodOptional<z.ZodNumber>;
|
|
172
|
+
y: z.ZodOptional<z.ZodNumber>;
|
|
173
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
174
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
175
|
+
is_active: z.ZodOptional<z.ZodBoolean>;
|
|
176
|
+
}, z.core.$loose>>>;
|
|
177
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
178
|
+
}, z.core.$loose>;
|
|
179
|
+
export declare class ManualInputError extends Error {
|
|
180
|
+
status: number;
|
|
181
|
+
constructor(status: number, message: string);
|
|
182
|
+
}
|
|
183
|
+
export declare function readManualPayload(req: import("http").IncomingMessage): Promise<any>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ManualInputError = exports.manualCanvasSchema = exports.manualRunSchema = void 0;
|
|
7
|
+
exports.sanitizeManualHtml = sanitizeManualHtml;
|
|
8
|
+
exports.sanitizeStoredCanvas = sanitizeStoredCanvas;
|
|
9
|
+
exports.readManualPayload = readManualPayload;
|
|
10
|
+
const sanitize_html_1 = __importDefault(require("sanitize-html"));
|
|
11
|
+
const zod_1 = require("zod");
|
|
12
|
+
const MANUAL_HTML_TAGS = ["b", "i", "em", "strong", "a", "ul", "ol", "li", "br", "div", "p", "font"];
|
|
13
|
+
function sanitizeManualHtml(value) {
|
|
14
|
+
return (0, sanitize_html_1.default)(typeof value === "string" ? value : "", {
|
|
15
|
+
allowedTags: MANUAL_HTML_TAGS,
|
|
16
|
+
allowedAttributes: { a: ["href", "target", "rel"], font: ["size"] },
|
|
17
|
+
allowedSchemes: ["http", "https", "mailto"], allowProtocolRelative: false,
|
|
18
|
+
transformTags: { a: (tagName, attribs) => ({ tagName, attribs: {
|
|
19
|
+
...attribs, target: attribs.target === "_blank" ? "_blank" : "_self", rel: "noopener noreferrer",
|
|
20
|
+
} }) },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const label = zod_1.z.string().max(2000);
|
|
24
|
+
const rich = zod_1.z.string().max(100000).transform(sanitizeManualHtml);
|
|
25
|
+
const numeric = zod_1.z.number().finite();
|
|
26
|
+
const media = zod_1.z.string().max(8192).refine(value => !value || /^(https?:\/\/|\/(?!\/))[^\s\\]*$/i.test(value), "Invalid media URL");
|
|
27
|
+
const nodeFields = {
|
|
28
|
+
id: zod_1.z.number().int().nonnegative(), title: label.optional(), visual_step: label.optional(),
|
|
29
|
+
description: rich.optional(), steps: rich.optional(), expected_result: rich.optional(), references: rich.optional(),
|
|
30
|
+
defect_report: rich.optional(), defect_media: media.optional(), assigned_to: label.optional(), priority: label.optional(),
|
|
31
|
+
x: numeric.optional(), y: numeric.optional(), width: numeric.optional(), height: numeric.optional(), is_active: zod_1.z.boolean().optional(),
|
|
32
|
+
};
|
|
33
|
+
const node = zod_1.z.looseObject(nodeFields);
|
|
34
|
+
const step = zod_1.z.looseObject({ ...nodeFields, status: zod_1.z.enum(["Pending", "Current", "Passed", "Failed", "Blocked", "Skipped", "Pass", "Fail", "Success", "passed", "failed", "pass", "fail", "success", "pending", "skipped", "blocked"]), group_title: label.optional() });
|
|
35
|
+
const group = zod_1.z.looseObject({ id: zod_1.z.number().int().nonnegative(), title: label.optional(), x: numeric.optional(), y: numeric.optional(), width: numeric.optional(), height: numeric.optional(), is_active: zod_1.z.boolean().optional() });
|
|
36
|
+
const text = zod_1.z.looseObject({ id: zod_1.z.number().int().nonnegative(), content: label.optional(), x: numeric.optional(), y: numeric.optional(), width: numeric.optional(), height: numeric.optional(), is_active: zod_1.z.boolean().optional() });
|
|
37
|
+
const connection = zod_1.z.looseObject({ id: zod_1.z.number().int().nonnegative() });
|
|
38
|
+
exports.manualRunSchema = zod_1.z.object({
|
|
39
|
+
name: zod_1.z.string().trim().min(1).max(255),
|
|
40
|
+
status: zod_1.z.enum(["PASS", "FAIL", "INCOMPLETE", "NO STEPS"]),
|
|
41
|
+
metrics: zod_1.z.object({ total: zod_1.z.number().int().nonnegative(), passed: zod_1.z.number().int().nonnegative(), failed: zod_1.z.number().int().nonnegative(), pending: zod_1.z.number().int().nonnegative() }).refine(m => m.total === m.passed + m.failed + m.pending, "Inconsistent metrics"),
|
|
42
|
+
canvasData: zod_1.z.looseObject({ nodes: zod_1.z.array(node).max(10000).default([]), groups: zod_1.z.array(group).max(10000).default([]), texts: zod_1.z.array(text).max(10000).default([]), connections: zod_1.z.array(connection).max(20000).default([]), executionReport: zod_1.z.array(step).max(10000).default([]), metadata: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional() }),
|
|
43
|
+
});
|
|
44
|
+
// Sanitize older records without trusting their original ingestion path.
|
|
45
|
+
function sanitizeStoredCanvas(value) {
|
|
46
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
47
|
+
return {};
|
|
48
|
+
const canvas = { ...value };
|
|
49
|
+
for (const key of ["nodes", "executionReport"]) {
|
|
50
|
+
if (!Array.isArray(canvas[key])) {
|
|
51
|
+
canvas[key] = [];
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
canvas[key] = canvas[key].filter(item => item && typeof item === "object").map(item => {
|
|
55
|
+
const result = { ...item };
|
|
56
|
+
for (const field of ["description", "steps", "expected_result", "references", "defect_report"]) {
|
|
57
|
+
if (field in result)
|
|
58
|
+
result[field] = sanitizeManualHtml(result[field]);
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return canvas;
|
|
64
|
+
}
|
|
65
|
+
exports.manualCanvasSchema = exports.manualRunSchema.shape.canvasData;
|
|
66
|
+
class ManualInputError extends Error {
|
|
67
|
+
status;
|
|
68
|
+
constructor(status, message) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.status = status;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.ManualInputError = ManualInputError;
|
|
74
|
+
async function readManualPayload(req) {
|
|
75
|
+
const limit = 2 * 1024 * 1024;
|
|
76
|
+
const length = req.headers["content-length"];
|
|
77
|
+
if (length && (!/^\d+$/.test(length) || !Number.isSafeInteger(Number(length))))
|
|
78
|
+
throw new ManualInputError(400, "Invalid request length");
|
|
79
|
+
if (length && Number(length) > limit)
|
|
80
|
+
throw new ManualInputError(413, "Manual payload exceeds 2 MiB");
|
|
81
|
+
const buffer = Buffer.allocUnsafe(length ? Number(length) : limit);
|
|
82
|
+
let bytes = 0;
|
|
83
|
+
for await (const chunk of req.iterator({ destroyOnReturn: false })) {
|
|
84
|
+
bytes += chunk.length;
|
|
85
|
+
if (bytes > limit || (length && bytes > Number(length))) {
|
|
86
|
+
req.resume();
|
|
87
|
+
throw new ManualInputError(413, "Manual payload exceeds allowed size");
|
|
88
|
+
}
|
|
89
|
+
buffer.set(chunk, bytes - chunk.length);
|
|
90
|
+
}
|
|
91
|
+
if (length && bytes !== Number(length))
|
|
92
|
+
throw new ManualInputError(400, "Request length mismatch");
|
|
93
|
+
let payload;
|
|
94
|
+
try {
|
|
95
|
+
payload = JSON.parse(buffer.subarray(0, bytes).toString("utf8"));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
throw new ManualInputError(400, "Invalid manual JSON");
|
|
99
|
+
}
|
|
100
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
101
|
+
throw new ManualInputError(400, "Invalid manual payload");
|
|
102
|
+
const canvas = exports.manualCanvasSchema.safeParse(payload.canvasData);
|
|
103
|
+
if (!canvas.success)
|
|
104
|
+
throw new ManualInputError(400, "Invalid manual canvas");
|
|
105
|
+
return { ...payload, canvasData: canvas.data };
|
|
106
|
+
}
|
|
@@ -3,6 +3,7 @@ export declare class DataGenerators {
|
|
|
3
3
|
private runIdContext;
|
|
4
4
|
private workerIdContext;
|
|
5
5
|
constructor(runId?: string, workerId?: string);
|
|
6
|
+
sensitiveValues(): string[];
|
|
6
7
|
resolve(template: string): string;
|
|
7
8
|
private pickRandom;
|
|
8
9
|
uuid: () => `${string}-${string}-${string}-${string}-${string}`;
|
|
@@ -19,6 +19,9 @@ class DataGenerators {
|
|
|
19
19
|
this.runIdContext = runId;
|
|
20
20
|
this.workerIdContext = workerId;
|
|
21
21
|
}
|
|
22
|
+
sensitiveValues() {
|
|
23
|
+
return [...this.memoryBank.entries()].filter(([key]) => /password|passcode|token|otp|pin|secret/i.test(key)).map(([, value]) => value);
|
|
24
|
+
}
|
|
22
25
|
resolve(template) {
|
|
23
26
|
if (!template || typeof template !== "string")
|
|
24
27
|
return template;
|
|
@@ -44,6 +44,9 @@ class DOMStripper {
|
|
|
44
44
|
el.removeAttribute("src");
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
|
+
clone.querySelectorAll('input').forEach(input => input.removeAttribute('value'));
|
|
48
|
+
clone.querySelectorAll('textarea').forEach(input => { input.textContent = '[REDACTED]'; });
|
|
49
|
+
clone.querySelectorAll('[contenteditable]').forEach(input => { input.textContent = '[REDACTED]'; });
|
|
47
50
|
let rawHtml = clone.outerHTML;
|
|
48
51
|
rawHtml = rawHtml.replace(/[\n\r\t]+/g, " ");
|
|
49
52
|
rawHtml = rawHtml.replace(/\s{2,}/g, " ");
|
|
@@ -4,6 +4,8 @@ export declare class FlowOrchestrator {
|
|
|
4
4
|
private worker;
|
|
5
5
|
private workerCount;
|
|
6
6
|
private config?;
|
|
7
|
+
static readonly nativeCIProtocol = 1;
|
|
8
|
+
readonly nativeEventProtocol = 1;
|
|
7
9
|
private wss;
|
|
8
10
|
private httpServer;
|
|
9
11
|
private globalTestScenarios;
|
|
@@ -13,6 +15,8 @@ export declare class FlowOrchestrator {
|
|
|
13
15
|
constructor(worker: FlowWorker, workerCount?: number, config?: FlowstrideConfig | undefined);
|
|
14
16
|
private forwardToDashboard;
|
|
15
17
|
private executeTestRun;
|
|
16
|
-
start(targetPath: string): Promise<
|
|
18
|
+
start(targetPath: string): Promise<boolean>;
|
|
19
|
+
abort(): void;
|
|
20
|
+
close(): Promise<void>;
|
|
17
21
|
private handleExportPdfRequest;
|
|
18
22
|
}
|
|
@@ -37,6 +37,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.FlowOrchestrator = void 0;
|
|
40
|
+
const localDashboard_1 = require("../security/localDashboard");
|
|
41
|
+
const projectPaths_1 = require("../utils/projectPaths");
|
|
40
42
|
const fs = __importStar(require("fs"));
|
|
41
43
|
const path = __importStar(require("path"));
|
|
42
44
|
const http = __importStar(require("http"));
|
|
@@ -48,6 +50,8 @@ class FlowOrchestrator {
|
|
|
48
50
|
worker;
|
|
49
51
|
workerCount;
|
|
50
52
|
config;
|
|
53
|
+
static nativeCIProtocol = 1;
|
|
54
|
+
nativeEventProtocol = 1;
|
|
51
55
|
wss = null;
|
|
52
56
|
httpServer = null;
|
|
53
57
|
globalTestScenarios = [];
|
|
@@ -64,6 +68,7 @@ class FlowOrchestrator {
|
|
|
64
68
|
});
|
|
65
69
|
}
|
|
66
70
|
forwardToDashboard(type, payload) {
|
|
71
|
+
this.config?.onRunEvent?.({ type, payload });
|
|
67
72
|
if (this.wss) {
|
|
68
73
|
this.wss.clients.forEach((client) => {
|
|
69
74
|
if (client.readyState === 1)
|
|
@@ -73,17 +78,9 @@ class FlowOrchestrator {
|
|
|
73
78
|
}
|
|
74
79
|
async executeTestRun(targetPath) {
|
|
75
80
|
this.forwardToDashboard("SUITE_START", {});
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
filesToProcess = fs
|
|
80
|
-
.readdirSync(targetPath)
|
|
81
|
-
.filter((f) => f.endsWith(".flow"))
|
|
82
|
-
.map((f) => path.join(targetPath, f));
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
filesToProcess = [targetPath];
|
|
86
|
-
}
|
|
81
|
+
const filesToProcess = (0, projectPaths_1.discoverFlows)(targetPath);
|
|
82
|
+
if (this.config)
|
|
83
|
+
this.config.flowRoot = fs.statSync(targetPath).isDirectory() ? targetPath : path.dirname(targetPath);
|
|
87
84
|
this.globalTestScenarios = [];
|
|
88
85
|
this.stepStatusRegistry.clear();
|
|
89
86
|
if (this.workerCount > 1) {
|
|
@@ -103,12 +100,21 @@ class FlowOrchestrator {
|
|
|
103
100
|
this.worker.on("worker_event", listener);
|
|
104
101
|
await this.worker.runBatch(filesToProcess);
|
|
105
102
|
this.worker.off("worker_event", listener);
|
|
106
|
-
this.
|
|
103
|
+
const passed = ![...this.stepStatusRegistry.values()].some(status => ['failed', 'running', 'not_tested'].includes(status));
|
|
104
|
+
this.forwardToDashboard("RUN_COMPLETE", { status: passed ? 'success' : 'failed' });
|
|
105
|
+
return passed;
|
|
107
106
|
}
|
|
108
107
|
async start(targetPath) {
|
|
109
108
|
try {
|
|
109
|
+
if (this.config?.ci)
|
|
110
|
+
return await this.executeTestRun(targetPath);
|
|
110
111
|
this.httpServer = http.createServer((req, res) => {
|
|
111
|
-
|
|
112
|
+
if (!(0, localDashboard_1.isLocalDashboardRequest)(req.headers.host, req.headers.origin)) {
|
|
113
|
+
res.writeHead(403);
|
|
114
|
+
res.end('Forbidden');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
|
|
112
118
|
if (req.url && req.url.startsWith("/videos/")) {
|
|
113
119
|
let urlObj;
|
|
114
120
|
try {
|
|
@@ -221,7 +227,7 @@ class FlowOrchestrator {
|
|
|
221
227
|
console.error(chalk_1.default.red(`\n[Error]: Failed to start local server: ${e.message}`));
|
|
222
228
|
process.exit(1);
|
|
223
229
|
});
|
|
224
|
-
this.wss = new ws_1.WebSocketServer({ server: this.httpServer });
|
|
230
|
+
this.wss = new ws_1.WebSocketServer({ server: this.httpServer, verifyClient: (info) => Boolean(info.origin) && (0, localDashboard_1.isLocalDashboardRequest)(info.req.headers.host, info.origin) });
|
|
225
231
|
this.wss.on("connection", (ws) => {
|
|
226
232
|
if (this.disconnectTimer) {
|
|
227
233
|
clearTimeout(this.disconnectTimer);
|
|
@@ -277,7 +283,7 @@ class FlowOrchestrator {
|
|
|
277
283
|
});
|
|
278
284
|
});
|
|
279
285
|
return new Promise((resolve, reject) => {
|
|
280
|
-
this.httpServer.listen(0, async () => {
|
|
286
|
+
this.httpServer.listen(0, '127.0.0.1', async () => {
|
|
281
287
|
try {
|
|
282
288
|
const address = this.httpServer?.address();
|
|
283
289
|
const port = typeof address === "object" && address !== null
|
|
@@ -304,8 +310,8 @@ class FlowOrchestrator {
|
|
|
304
310
|
else {
|
|
305
311
|
console.log(chalk_1.default.gray("[Dashboard]: Headless mode active. Bypassing UI auto-launch..."));
|
|
306
312
|
}
|
|
307
|
-
await this.executeTestRun(targetPath);
|
|
308
|
-
resolve(
|
|
313
|
+
const result = await this.executeTestRun(targetPath);
|
|
314
|
+
resolve(result);
|
|
309
315
|
}
|
|
310
316
|
catch (err) {
|
|
311
317
|
reject(err);
|
|
@@ -317,11 +323,20 @@ class FlowOrchestrator {
|
|
|
317
323
|
throw e;
|
|
318
324
|
}
|
|
319
325
|
}
|
|
326
|
+
abort() {
|
|
327
|
+
this.worker.abort();
|
|
328
|
+
}
|
|
329
|
+
async close() {
|
|
330
|
+
if (this.disconnectTimer)
|
|
331
|
+
clearTimeout(this.disconnectTimer);
|
|
332
|
+
this.wss?.clients.forEach(client => client.terminate());
|
|
333
|
+
this.wss?.close();
|
|
334
|
+
this.httpServer?.close();
|
|
335
|
+
}
|
|
320
336
|
async handleExportPdfRequest(testScenariosFromUI) {
|
|
321
337
|
try {
|
|
322
|
-
const spinner = (0, ora_1.default)(chalk_1.default.blue("Compiling high
|
|
338
|
+
const spinner = (0, ora_1.default)(chalk_1.default.blue("Compiling high fidelity PDF report...")).start();
|
|
323
339
|
const pdfPath = await this.pdfGenerator.generateTestcasePdf(testScenariosFromUI);
|
|
324
|
-
// Convert the saved PDF to base64 to trigger the native browser download UI
|
|
325
340
|
const pdfBuffer = fs.readFileSync(pdfPath);
|
|
326
341
|
const base64Data = pdfBuffer.toString("base64");
|
|
327
342
|
this.forwardToDashboard("EXPORT_PDF_RES", { base64: base64Data });
|