flowstride 1.0.14 → 1.0.16
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/README.md +2 -0
- package/dist/cli/commands/run.js +5 -6
- package/dist/cli/index.js +96 -60
- package/dist/parser/parser.js +22 -3
- package/dist/runner/flow_orchestrator.js +35 -16
- package/dist/runner/flow_worker.js +23 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Flowstride OS combines the human-readable structure of Behavior-Driven Development (BDD) with the raw execution power of Playwright. It allows QA engineers and developers to write robust, maintainable UI and API tests using an intuitive domain-specific language (DSL), while providing a rich, interactive React dashboard to monitor, debug, and replay test runs in real time.
|
|
6
6
|
|
|
7
|
+
[Documentation](https://docs.flowstride.io/)
|
|
8
|
+
|
|
7
9
|
---
|
|
8
10
|
|
|
9
11
|
## Why Flowstride?
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -97,7 +97,11 @@ async function runAction(targetPath, options) {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
const config = {
|
|
100
|
-
headless: isCI
|
|
100
|
+
headless: isCI
|
|
101
|
+
? true
|
|
102
|
+
: options.headed
|
|
103
|
+
? false
|
|
104
|
+
: (localConfig.headless ?? true),
|
|
101
105
|
timeout: localConfig.timeout ?? 30000,
|
|
102
106
|
bail: options.bail ?? localConfig.bail ?? false,
|
|
103
107
|
baseUrl: process.env.BASE_URL ?? localConfig.baseUrl,
|
|
@@ -129,15 +133,11 @@ async function runAction(targetPath, options) {
|
|
|
129
133
|
const pluginManager = new plugin_manager_1.PluginManager(config, web, store, apiProxy);
|
|
130
134
|
const vault = new persistent_vault_1.PersistentVault(root);
|
|
131
135
|
const sessions = new manager_1.SessionManager(web, api, vault);
|
|
132
|
-
// ==========================================
|
|
133
|
-
// DYNAMIC ENGINE ROUTER (The Plugin Handoff)
|
|
134
|
-
// ==========================================
|
|
135
136
|
let ActiveFlowWorker = flow_worker_1.FlowWorker;
|
|
136
137
|
let ActiveFlowOrchestrator = flow_orchestrator_1.FlowOrchestrator;
|
|
137
138
|
let engineMode = "Open Source";
|
|
138
139
|
const proWorkerPath = path.join(process.cwd(), "node_modules", "@flowstride", "pro", "dist", "runner", "flow_worker.js");
|
|
139
140
|
const proOrchPath = path.join(process.cwd(), "node_modules", "@flowstride", "pro", "dist", "runner", "flow_orchestrator.js");
|
|
140
|
-
// We only attempt to load the Pro Engine if the DRM Bouncer (index.ts) did NOT force OS Mode
|
|
141
141
|
if (!options.forceOS &&
|
|
142
142
|
fs.existsSync(proWorkerPath) &&
|
|
143
143
|
fs.existsSync(proOrchPath)) {
|
|
@@ -153,7 +153,6 @@ async function runAction(targetPath, options) {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
console.log(`\n[Engine]: Booting Flowstride in ${engineMode} mode...`);
|
|
156
|
-
// ==========================================
|
|
157
156
|
const worker = new ActiveFlowWorker(config, web, api, store, sessions, pluginManager);
|
|
158
157
|
const orchestrator = new ActiveFlowOrchestrator(worker, workerCount, config);
|
|
159
158
|
try {
|
package/dist/cli/index.js
CHANGED
|
@@ -54,7 +54,7 @@ const API_BASE = process.env.FLOWSTRIDE_API_URL || "https://cloud.flowstride.io"
|
|
|
54
54
|
program
|
|
55
55
|
.name("flowstride")
|
|
56
56
|
.description("Flowstride: The Enterprise Flow-First Automation CLI")
|
|
57
|
-
.version("1.0.
|
|
57
|
+
.version("1.0.16");
|
|
58
58
|
program
|
|
59
59
|
.command("init")
|
|
60
60
|
.description("Initialise a new Flowstride workspace in the current directory")
|
|
@@ -64,33 +64,40 @@ program
|
|
|
64
64
|
program
|
|
65
65
|
.command("login")
|
|
66
66
|
.description("Authenticate this machine with Flowstride Enterprise Cloud")
|
|
67
|
-
.
|
|
67
|
+
.option("-t, --token <token>", "Enterprise Developer Token")
|
|
68
|
+
.option("-g, --global", "Save the token globally for all projects on this machine", false)
|
|
69
|
+
.action(async (options) => {
|
|
68
70
|
const localPackageJsonPath = path.join(process.cwd(), "package.json");
|
|
69
|
-
if
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
// Enforce valid project directory ONLY if doing a local login
|
|
72
|
+
if (!options.global) {
|
|
73
|
+
if (!fs.existsSync(localPackageJsonPath)) {
|
|
74
|
+
console.error(chalk_1.default.red("✖ Error: Please navigate to a valid Flowstride project directory before logging in locally, or use --global."));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
const pkg = JSON.parse(fs.readFileSync(localPackageJsonPath, "utf8"));
|
|
78
|
+
const deps = {
|
|
79
|
+
...(pkg.dependencies || {}),
|
|
80
|
+
...(pkg.devDependencies || {}),
|
|
81
|
+
};
|
|
82
|
+
if (!deps["flowstride"] && !deps["flowstride-os"]) {
|
|
83
|
+
console.error(chalk_1.default.red("✖ Error: Please navigate to a valid Flowstride project directory before logging in locally, or use --global."));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
81
86
|
}
|
|
82
87
|
console.log(chalk_1.default.bold.blue("\n🌐 Flowstride Enterprise Cloud Sync\n"));
|
|
83
|
-
let
|
|
84
|
-
if (process.env.
|
|
85
|
-
console.log(chalk_1.default.yellow("🤖 CI/CD Environment Detected. Authenticating via
|
|
86
|
-
authHeader = `Bearer ${process.env.FLOWSTRIDE_API_KEY}`;
|
|
88
|
+
let token = process.env.FLOWSTRIDE_TOKEN || options.token;
|
|
89
|
+
if (process.env.FLOWSTRIDE_TOKEN) {
|
|
90
|
+
console.log(chalk_1.default.yellow("🤖 CI/CD Environment Detected. Authenticating via FLOWSTRIDE_TOKEN..."));
|
|
87
91
|
}
|
|
88
|
-
else {
|
|
89
|
-
|
|
90
|
-
message: "
|
|
92
|
+
else if (!token) {
|
|
93
|
+
token = await (0, prompts_1.password)({
|
|
94
|
+
message: "Paste your Enterprise Developer Token:",
|
|
95
|
+
mask: "*",
|
|
91
96
|
});
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
}
|
|
98
|
+
if (!token || token.trim().length === 0) {
|
|
99
|
+
console.error(chalk_1.default.red("✖ Error: A valid Developer Token is required."));
|
|
100
|
+
process.exit(1);
|
|
94
101
|
}
|
|
95
102
|
const deviceName = os.hostname();
|
|
96
103
|
const spinner = (0, ora_1.default)("Authenticating with Cloud...").start();
|
|
@@ -99,34 +106,37 @@ program
|
|
|
99
106
|
method: "POST",
|
|
100
107
|
headers: {
|
|
101
108
|
"Content-Type": "application/json",
|
|
102
|
-
Authorization:
|
|
109
|
+
Authorization: `Bearer ${token.trim()}`,
|
|
103
110
|
},
|
|
104
111
|
body: JSON.stringify({ deviceName }),
|
|
105
112
|
});
|
|
106
113
|
const data = await response.json();
|
|
107
114
|
if (!response.ok) {
|
|
108
|
-
spinner.fail(chalk_1.default.red(`Authentication Failed: ${data.error || "
|
|
115
|
+
spinner.fail(chalk_1.default.red(`Authentication Failed: ${data.error || "Invalid token or network error"}`));
|
|
109
116
|
process.exit(1);
|
|
110
117
|
}
|
|
111
118
|
spinner.succeed(chalk_1.default.green("Authentication Successful!"));
|
|
112
|
-
const configDir =
|
|
119
|
+
const configDir = options.global
|
|
120
|
+
? path.join(os.homedir(), ".flowstride")
|
|
121
|
+
: path.join(process.cwd(), ".flowstride");
|
|
113
122
|
if (!fs.existsSync(configDir)) {
|
|
114
123
|
fs.mkdirSync(configDir, { recursive: true });
|
|
115
124
|
}
|
|
116
|
-
const credentialsPath = path.join(configDir, "
|
|
125
|
+
const credentialsPath = path.join(configDir, "credentials.json");
|
|
117
126
|
fs.writeFileSync(credentialsPath, JSON.stringify({
|
|
118
|
-
machineToken: data.machineToken,
|
|
127
|
+
machineToken: data.machineToken || token.trim(),
|
|
119
128
|
workspaceId: data.workspaceId,
|
|
120
129
|
deviceName: deviceName,
|
|
121
130
|
syncedAt: new Date().toISOString(),
|
|
122
131
|
}, null, 2));
|
|
123
|
-
|
|
132
|
+
const scopeText = options.global ? "GLOBALLY" : "LOCALLY to this project";
|
|
133
|
+
console.log(chalk_1.default.gray(`\nCredentials securely scoped ${scopeText} at: ${credentialsPath}`));
|
|
124
134
|
if (data.isProEligible) {
|
|
125
135
|
console.log(chalk_1.default.yellow(`\n⚙️ System Check: You have access to Enterprise features (AI Healing, Parallel Workers).`));
|
|
126
136
|
const nodeModulesPath = path.join(process.cwd(), "node_modules", "@flowstride", "pro");
|
|
127
137
|
if (!fs.existsSync(nodeModulesPath)) {
|
|
128
138
|
let installPro = true;
|
|
129
|
-
if (!process.env.
|
|
139
|
+
if (!process.env.FLOWSTRIDE_TOKEN && !process.env.CI) {
|
|
130
140
|
installPro = await (0, prompts_1.confirm)({
|
|
131
141
|
message: "The Pro Engine is not installed in this project. Would you like Flowstride to automatically install it now?",
|
|
132
142
|
default: true,
|
|
@@ -138,7 +148,7 @@ program
|
|
|
138
148
|
try {
|
|
139
149
|
const tarballResponse = await fetch(`${API_BASE}/api/workspace/download-pro`, {
|
|
140
150
|
method: "GET",
|
|
141
|
-
headers: { Authorization: `Bearer ${
|
|
151
|
+
headers: { Authorization: `Bearer ${token.trim()}` },
|
|
142
152
|
});
|
|
143
153
|
if (!tarballResponse.ok) {
|
|
144
154
|
throw new Error(`Secure proxy rejected request: ${tarballResponse.statusText}`);
|
|
@@ -200,21 +210,34 @@ program
|
|
|
200
210
|
.command("status")
|
|
201
211
|
.description("Display the current Flowstride CLI authentication and engine tier")
|
|
202
212
|
.action(async () => {
|
|
203
|
-
let token = process.env.
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
213
|
+
let token = process.env.FLOWSTRIDE_TOKEN || null;
|
|
214
|
+
const localCredentialsPath = path.join(process.cwd(), ".flowstride", "credentials.json");
|
|
215
|
+
const globalCredentialsPath = path.join(os.homedir(), ".flowstride", "credentials.json");
|
|
216
|
+
let activeCredentialsPath = localCredentialsPath; // Default for cleanup
|
|
217
|
+
if (!token) {
|
|
218
|
+
if (fs.existsSync(localCredentialsPath)) {
|
|
219
|
+
activeCredentialsPath = localCredentialsPath;
|
|
220
|
+
try {
|
|
221
|
+
const credentials = JSON.parse(fs.readFileSync(localCredentialsPath, "utf8"));
|
|
222
|
+
token = credentials.machineToken;
|
|
223
|
+
}
|
|
224
|
+
catch (err) { }
|
|
225
|
+
}
|
|
226
|
+
else if (fs.existsSync(globalCredentialsPath)) {
|
|
227
|
+
activeCredentialsPath = globalCredentialsPath;
|
|
228
|
+
try {
|
|
229
|
+
const credentials = JSON.parse(fs.readFileSync(globalCredentialsPath, "utf8"));
|
|
230
|
+
token = credentials.machineToken;
|
|
231
|
+
}
|
|
232
|
+
catch (err) { }
|
|
210
233
|
}
|
|
211
|
-
catch (err) { }
|
|
212
234
|
}
|
|
213
|
-
|
|
235
|
+
const nodeModulesPath = path.join(process.cwd(), "node_modules", "@flowstride", "pro");
|
|
236
|
+
if (!process.env.FLOWSTRIDE_TOKEN &&
|
|
214
237
|
token &&
|
|
215
238
|
!fs.existsSync(nodeModulesPath)) {
|
|
216
|
-
if (fs.existsSync(
|
|
217
|
-
fs.unlinkSync(
|
|
239
|
+
if (fs.existsSync(activeCredentialsPath))
|
|
240
|
+
fs.unlinkSync(activeCredentialsPath);
|
|
218
241
|
token = null;
|
|
219
242
|
}
|
|
220
243
|
if (!token) {
|
|
@@ -228,8 +251,8 @@ program
|
|
|
228
251
|
});
|
|
229
252
|
if (response.status === 401) {
|
|
230
253
|
console.log(chalk_1.default.red("🔴 AUTH EXPIRED: Your device session was revoked or expired."));
|
|
231
|
-
if (fs.existsSync(
|
|
232
|
-
fs.unlinkSync(
|
|
254
|
+
if (fs.existsSync(activeCredentialsPath))
|
|
255
|
+
fs.unlinkSync(activeCredentialsPath);
|
|
233
256
|
}
|
|
234
257
|
else if (response.ok) {
|
|
235
258
|
const data = await response.json();
|
|
@@ -253,30 +276,43 @@ program
|
|
|
253
276
|
.command("run [targetPath]")
|
|
254
277
|
.description("Execute all .flow automation journeys in the ./flows directory")
|
|
255
278
|
.option("-b, --bail", "Stop execution on first failure", false)
|
|
256
|
-
.option("--headed", "Run browser in headed mode"
|
|
257
|
-
.option("--headless
|
|
279
|
+
.option("--headed", "Run browser in headed mode")
|
|
280
|
+
.option("--headless", "Run browser in headless mode")
|
|
258
281
|
.option("--workers <number>", "Number of parallel workers to spawn (Pro feature)")
|
|
259
282
|
.action(async (targetPath, options) => {
|
|
260
283
|
if (typeof targetPath === "object") {
|
|
261
284
|
options = targetPath;
|
|
262
285
|
targetPath = undefined;
|
|
263
286
|
}
|
|
264
|
-
let token = process.env.
|
|
287
|
+
let token = process.env.FLOWSTRIDE_TOKEN || null;
|
|
265
288
|
let forceOS = false;
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
289
|
+
const localCredentialsPath = path.join(process.cwd(), ".flowstride", "credentials.json");
|
|
290
|
+
const globalCredentialsPath = path.join(os.homedir(), ".flowstride", "credentials.json");
|
|
291
|
+
let activeCredentialsPath = localCredentialsPath;
|
|
292
|
+
if (!token) {
|
|
293
|
+
if (fs.existsSync(localCredentialsPath)) {
|
|
294
|
+
activeCredentialsPath = localCredentialsPath;
|
|
295
|
+
try {
|
|
296
|
+
const credentials = JSON.parse(fs.readFileSync(localCredentialsPath, "utf8"));
|
|
297
|
+
token = credentials.machineToken;
|
|
298
|
+
}
|
|
299
|
+
catch (err) { }
|
|
300
|
+
}
|
|
301
|
+
else if (fs.existsSync(globalCredentialsPath)) {
|
|
302
|
+
activeCredentialsPath = globalCredentialsPath;
|
|
303
|
+
try {
|
|
304
|
+
const credentials = JSON.parse(fs.readFileSync(globalCredentialsPath, "utf8"));
|
|
305
|
+
token = credentials.machineToken;
|
|
306
|
+
}
|
|
307
|
+
catch (err) { }
|
|
272
308
|
}
|
|
273
|
-
catch (err) { }
|
|
274
309
|
}
|
|
275
|
-
|
|
310
|
+
const nodeModulesPath = path.join(process.cwd(), "node_modules", "@flowstride", "pro");
|
|
311
|
+
if (!process.env.FLOWSTRIDE_TOKEN &&
|
|
276
312
|
token &&
|
|
277
313
|
!fs.existsSync(nodeModulesPath)) {
|
|
278
|
-
if (fs.existsSync(
|
|
279
|
-
fs.unlinkSync(
|
|
314
|
+
if (fs.existsSync(activeCredentialsPath))
|
|
315
|
+
fs.unlinkSync(activeCredentialsPath);
|
|
280
316
|
token = null;
|
|
281
317
|
}
|
|
282
318
|
if (!token) {
|
|
@@ -291,8 +327,8 @@ program
|
|
|
291
327
|
});
|
|
292
328
|
if (response.status === 401) {
|
|
293
329
|
console.log(chalk_1.default.red("🔴 AUTH EXPIRED: Your device session was revoked or expired."));
|
|
294
|
-
if (fs.existsSync(
|
|
295
|
-
fs.unlinkSync(
|
|
330
|
+
if (fs.existsSync(activeCredentialsPath))
|
|
331
|
+
fs.unlinkSync(activeCredentialsPath);
|
|
296
332
|
forceOS = true;
|
|
297
333
|
}
|
|
298
334
|
else if (response.ok) {
|
|
@@ -328,7 +364,7 @@ program
|
|
|
328
364
|
});
|
|
329
365
|
async function handleOfflinePrompt() {
|
|
330
366
|
console.log(chalk_1.default.yellow("🛜 OFFLINE: Unable to reach Flowstride Cloud. Enterprise Sync paused."));
|
|
331
|
-
if (process.env.CI || process.env.
|
|
367
|
+
if (process.env.CI || process.env.FLOWSTRIDE_TOKEN) {
|
|
332
368
|
console.log(chalk_1.default.yellow("⚠️ CI/CD Environment: Automatically falling back to OS Engine."));
|
|
333
369
|
return true;
|
|
334
370
|
}
|
package/dist/parser/parser.js
CHANGED
|
@@ -615,9 +615,28 @@ class Parser {
|
|
|
615
615
|
throw new Error(`Syntax Error on line ${targetToken.line}: Invalid keyword '${target}'. Flowstride uses strict camelCase. Did you mean 'reqHeader'?`);
|
|
616
616
|
}
|
|
617
617
|
if (target === "reqHeader") {
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
618
|
+
const firstToken = this.consume(lexer_1.TokenType.STRING, "Header key or JSON block expected");
|
|
619
|
+
if (!this.isAtEnd() && this.peek().type === lexer_1.TokenType.STRING) {
|
|
620
|
+
const hKey = firstToken.value;
|
|
621
|
+
const hVal = this.advance().value;
|
|
622
|
+
payload.headers.push({ key: hKey, value: hVal });
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
try {
|
|
626
|
+
const headerObj = JSON.parse(firstToken.value);
|
|
627
|
+
if (typeof headerObj !== "object" ||
|
|
628
|
+
headerObj === null ||
|
|
629
|
+
Array.isArray(headerObj)) {
|
|
630
|
+
throw new Error("Parsed JSON is not a Key-Value object.");
|
|
631
|
+
}
|
|
632
|
+
for (const [k, v] of Object.entries(headerObj)) {
|
|
633
|
+
payload.headers.push({ key: k, value: String(v) });
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
catch (e) {
|
|
637
|
+
throw new Error(`Line ${firstToken.line}: Invalid JSON format for reqHeader block. Ensure it is a valid JSON object, or use the legacy format with two strings (Key and Value).`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
621
640
|
}
|
|
622
641
|
else if (target === "reqBody") {
|
|
623
642
|
payload.body = this.consume(lexer_1.TokenType.STRING, "Body content expected").value;
|
|
@@ -276,22 +276,41 @@ class FlowOrchestrator {
|
|
|
276
276
|
}
|
|
277
277
|
});
|
|
278
278
|
});
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
279
|
+
return new Promise((resolve, reject) => {
|
|
280
|
+
this.httpServer.listen(0, async () => {
|
|
281
|
+
try {
|
|
282
|
+
const address = this.httpServer?.address();
|
|
283
|
+
const port = typeof address === "object" && address !== null
|
|
284
|
+
? address.port
|
|
285
|
+
: null;
|
|
286
|
+
if (!port) {
|
|
287
|
+
console.error(chalk_1.default.red("\n[Error]: Could not bind to an available port."));
|
|
288
|
+
return reject(new Error("No port available"));
|
|
289
|
+
}
|
|
290
|
+
const localUrl = `http://localhost:${port}`;
|
|
291
|
+
console.log(chalk_1.default.yellow(`\n[Dashboard]: Local server running at ${localUrl}`));
|
|
292
|
+
const isStrictHeadless = process.argv.includes("--headless") ||
|
|
293
|
+
!!process.env.CI ||
|
|
294
|
+
!!process.env.GITHUB_ACTIONS ||
|
|
295
|
+
!!process.env.FLOWSTRIDE_API_KEY;
|
|
296
|
+
if (!isStrictHeadless) {
|
|
297
|
+
const { default: open } = await import("open");
|
|
298
|
+
await open(localUrl);
|
|
299
|
+
console.log(chalk_1.default.yellow("[Dashboard]: Waiting for UI connection..."));
|
|
300
|
+
while (this.wss.clients.size === 0)
|
|
301
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
302
|
+
console.log(chalk_1.default.green("[Dashboard]: Connected to UI!"));
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
console.log(chalk_1.default.gray("[Dashboard]: Headless mode active. Bypassing UI auto-launch..."));
|
|
306
|
+
}
|
|
307
|
+
await this.executeTestRun(targetPath);
|
|
308
|
+
resolve(undefined);
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
reject(err);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
295
314
|
});
|
|
296
315
|
}
|
|
297
316
|
catch (e) {
|
|
@@ -131,6 +131,9 @@ class FlowWorker extends events_1.EventEmitter {
|
|
|
131
131
|
return context;
|
|
132
132
|
}
|
|
133
133
|
resolveTextVariables(text) {
|
|
134
|
+
if (typeof text !== "string") {
|
|
135
|
+
return text;
|
|
136
|
+
}
|
|
134
137
|
let resolved = text;
|
|
135
138
|
resolved = resolved.replace(/\{\{env\.([a-zA-Z0-9_]+)\}\}/g, (_, varName) => {
|
|
136
139
|
const val = process.env[varName];
|
|
@@ -259,11 +262,21 @@ class FlowWorker extends events_1.EventEmitter {
|
|
|
259
262
|
catch {
|
|
260
263
|
rawBodyString = typeof body === "string" ? body : null;
|
|
261
264
|
}
|
|
262
|
-
|
|
265
|
+
let formattedHeaders = {};
|
|
266
|
+
if (Array.isArray(headers)) {
|
|
267
|
+
headers.forEach((h) => {
|
|
268
|
+
if (h && h.key)
|
|
269
|
+
formattedHeaders[h.key] = h.value;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
else if (headers && typeof headers === "object") {
|
|
273
|
+
formattedHeaders = headers;
|
|
274
|
+
}
|
|
275
|
+
const curlString = this.web.synthesizeCurl(method, cleanUrl, formattedHeaders, rawBodyString);
|
|
263
276
|
this.emitLog("REST", (state.status || 500) < 400 ? "success" : "error", `${method.toUpperCase()} ${endpointName}`, {
|
|
264
277
|
method: method.toUpperCase(),
|
|
265
278
|
url: cleanUrl,
|
|
266
|
-
requestHeaders:
|
|
279
|
+
requestHeaders: formattedHeaders,
|
|
267
280
|
requestBody: parsedReqBody,
|
|
268
281
|
responseStatus: state.status,
|
|
269
282
|
responseHeaders: state.headers,
|
|
@@ -340,7 +353,14 @@ class FlowWorker extends events_1.EventEmitter {
|
|
|
340
353
|
this.isAborted = true;
|
|
341
354
|
}
|
|
342
355
|
async runBatch(filesToProcess) {
|
|
343
|
-
this.config.headless = true;
|
|
356
|
+
// this.config.headless = true;
|
|
357
|
+
const isCliHeadless = process.argv.includes("--headless");
|
|
358
|
+
if (isCliHeadless) {
|
|
359
|
+
this.config.headless = true;
|
|
360
|
+
}
|
|
361
|
+
else if (this.config.headless === undefined) {
|
|
362
|
+
this.config.headless = false;
|
|
363
|
+
}
|
|
344
364
|
this.activeAudioPath = undefined;
|
|
345
365
|
this.isAborted = false;
|
|
346
366
|
this.isDebugHalted = false;
|