flowstride 1.0.20-rc.2 → 1.0.20
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 +88 -145
- package/README.md +46 -221
- package/dist/adapters/web/playwright.d.ts +4 -1
- package/dist/adapters/web/playwright.js +75 -6
- package/dist/ci/config.js +6 -5
- package/dist/ci/runtime.js +27 -14
- package/dist/cli/commands/run.js +2 -1
- package/dist/cli/index.js +35 -50
- 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/sdk.js +3 -2
- 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.js +13 -22
- package/dist/runner/flow_worker.d.ts +1 -0
- package/dist/runner/flow_worker.js +68 -47
- package/dist/runner/persistent_vault.js +2 -1
- package/dist/runner/plugin_manager.js +3 -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 +51 -0
- package/dist/sessions/manager.js +3 -1
- package/dist/types/index.d.ts +9 -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/dist/utils/replayRequest.d.ts +8 -0
- package/dist/utils/replayRequest.js +77 -0
- package/package.json +23 -14
- package/src/ui/dist/assets/index-B7MuBTWm.js +460 -0
- package/src/ui/dist/index.html +1 -1
- package/RELEASE_NOTES.md +0 -40
- package/src/ui/dist/assets/index-DIC22ZK_.js +0 -638
|
@@ -37,6 +37,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.startManualWorkspaceServer = startManualWorkspaceServer;
|
|
40
|
+
const projectPaths_1 = require("../../utils/projectPaths");
|
|
41
|
+
const security_1 = require("../../manual/security");
|
|
40
42
|
const http = __importStar(require("http"));
|
|
41
43
|
const path = __importStar(require("path"));
|
|
42
44
|
const fs = __importStar(require("fs"));
|
|
@@ -49,12 +51,12 @@ async function startManualWorkspaceServer() {
|
|
|
49
51
|
console.error(chalk_1.default.red(`\n❌ Error: UI build bundle not found at ${uiDistPath}. Please build the UI first.`));
|
|
50
52
|
process.exit(1);
|
|
51
53
|
}
|
|
52
|
-
const manualFlowsDir = path.join(process.cwd(), "manual_flows");
|
|
53
|
-
const templatesDir = path.join(manualFlowsDir, "templates");
|
|
54
|
-
const runsDir = path.join(manualFlowsDir, "runs");
|
|
55
|
-
const mediaDir = path.join(manualFlowsDir, "media");
|
|
56
|
-
const defectMediaDir = path.join(manualFlowsDir, "defect_media");
|
|
57
|
-
const workspaceStateFile = path.join(manualFlowsDir, ".workspace_state.json");
|
|
54
|
+
const manualFlowsDir = (0, projectPaths_1.projectPath)(process.cwd(), path.join(process.cwd(), "manual_flows"));
|
|
55
|
+
const templatesDir = (0, projectPaths_1.projectPath)(process.cwd(), path.join(manualFlowsDir, "templates"));
|
|
56
|
+
const runsDir = (0, projectPaths_1.projectPath)(process.cwd(), path.join(manualFlowsDir, "runs"));
|
|
57
|
+
const mediaDir = (0, projectPaths_1.projectPath)(process.cwd(), path.join(manualFlowsDir, "media"));
|
|
58
|
+
const defectMediaDir = (0, projectPaths_1.projectPath)(process.cwd(), path.join(manualFlowsDir, "defect_media"));
|
|
59
|
+
const workspaceStateFile = (0, projectPaths_1.projectPath)(process.cwd(), path.join(manualFlowsDir, ".workspace_state.json"));
|
|
58
60
|
if (!fs.existsSync(templatesDir))
|
|
59
61
|
fs.mkdirSync(templatesDir, { recursive: true });
|
|
60
62
|
if (!fs.existsSync(runsDir))
|
|
@@ -92,7 +94,7 @@ async function startManualWorkspaceServer() {
|
|
|
92
94
|
return res.end(JSON.stringify({ error: "Invalid or missing file extension." }));
|
|
93
95
|
}
|
|
94
96
|
const safeFilename = `media_${Date.now()}_${Math.random().toString(36).substring(2, 9)}${ext.toLowerCase()}`;
|
|
95
|
-
const filePath = path.join(mediaDir, safeFilename);
|
|
97
|
+
const filePath = (0, projectPaths_1.projectPath)(process.cwd(), path.join(mediaDir, safeFilename));
|
|
96
98
|
const MAX_SIZE = 104857600;
|
|
97
99
|
let uploadedSize = 0;
|
|
98
100
|
let isAborted = false;
|
|
@@ -141,7 +143,7 @@ async function startManualWorkspaceServer() {
|
|
|
141
143
|
}
|
|
142
144
|
const rawFilename = targetPath.replace("/media/", "");
|
|
143
145
|
const safeFilename = path.basename(rawFilename);
|
|
144
|
-
const filePath = path.join(mediaDir, safeFilename);
|
|
146
|
+
const filePath = (0, projectPaths_1.projectPath)(process.cwd(), path.join(mediaDir, safeFilename));
|
|
145
147
|
if (!fs.existsSync(filePath)) {
|
|
146
148
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
147
149
|
return res.end(JSON.stringify({ error: "Media file not found in vault" }));
|
|
@@ -166,7 +168,7 @@ async function startManualWorkspaceServer() {
|
|
|
166
168
|
if (req.method === "GET" && pathname.startsWith("/media/")) {
|
|
167
169
|
const filename = pathname.replace("/media/", "");
|
|
168
170
|
const safeFilename = path.basename(filename);
|
|
169
|
-
const filePath = path.join(mediaDir, safeFilename);
|
|
171
|
+
const filePath = (0, projectPaths_1.projectPath)(process.cwd(), path.join(mediaDir, safeFilename));
|
|
170
172
|
if (!fs.existsSync(filePath)) {
|
|
171
173
|
res.writeHead(404);
|
|
172
174
|
return res.end("Media not found");
|
|
@@ -213,7 +215,7 @@ async function startManualWorkspaceServer() {
|
|
|
213
215
|
return res.end(JSON.stringify({ error: "Invalid or missing file extension." }));
|
|
214
216
|
}
|
|
215
217
|
const safeFilename = `defect_${Date.now()}_${Math.random().toString(36).substring(2, 9)}${ext.toLowerCase()}`;
|
|
216
|
-
const filePath = path.join(defectMediaDir, safeFilename);
|
|
218
|
+
const filePath = (0, projectPaths_1.projectPath)(process.cwd(), path.join(defectMediaDir, safeFilename));
|
|
217
219
|
const MAX_SIZE = 41943040; // Capped at 40MB
|
|
218
220
|
let uploadedSize = 0;
|
|
219
221
|
let isAborted = false;
|
|
@@ -263,7 +265,7 @@ async function startManualWorkspaceServer() {
|
|
|
263
265
|
}
|
|
264
266
|
const rawFilename = targetPath.replace("/defect_media/", "");
|
|
265
267
|
const safeFilename = path.basename(rawFilename);
|
|
266
|
-
const filePath = path.join(defectMediaDir, safeFilename);
|
|
268
|
+
const filePath = (0, projectPaths_1.projectPath)(process.cwd(), path.join(defectMediaDir, safeFilename));
|
|
267
269
|
if (fs.existsSync(filePath)) {
|
|
268
270
|
fs.unlinkSync(filePath);
|
|
269
271
|
console.log(chalk_1.default.yellow(`\n[Local Cache Cleared]: Asset securely removed from manual_flows/defect_media/${safeFilename}`));
|
|
@@ -286,7 +288,7 @@ async function startManualWorkspaceServer() {
|
|
|
286
288
|
if (req.method === "GET" && pathname.startsWith("/defect_media/")) {
|
|
287
289
|
const filename = pathname.replace("/defect_media/", "");
|
|
288
290
|
const safeFilename = path.basename(filename);
|
|
289
|
-
const filePath = path.join(defectMediaDir, safeFilename);
|
|
291
|
+
const filePath = (0, projectPaths_1.projectPath)(process.cwd(), path.join(defectMediaDir, safeFilename));
|
|
290
292
|
if (!fs.existsSync(filePath)) {
|
|
291
293
|
res.writeHead(404);
|
|
292
294
|
return res.end("Defect media not found");
|
|
@@ -326,13 +328,17 @@ async function startManualWorkspaceServer() {
|
|
|
326
328
|
// CORE SYSTEM ROUTES
|
|
327
329
|
// ============================================================================
|
|
328
330
|
if (req.method === "POST" && pathname === "/api/save-design") {
|
|
329
|
-
let
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
|
|
331
|
+
let payload;
|
|
332
|
+
try {
|
|
333
|
+
payload = await (0, security_1.readManualPayload)(req);
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
res.writeHead(error instanceof security_1.ManualInputError ? error.status : 400, { 'Content-Type': 'application/json' });
|
|
337
|
+
res.end(JSON.stringify({ error: error instanceof security_1.ManualInputError ? error.message : 'Invalid manual payload' }));
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const savePayload = async () => {
|
|
334
341
|
try {
|
|
335
|
-
const payload = JSON.parse(body);
|
|
336
342
|
const rawFilename = payload.filename || `New Flow.json`;
|
|
337
343
|
const canvasData = payload.canvasData;
|
|
338
344
|
if (!canvasData) {
|
|
@@ -343,7 +349,7 @@ async function startManualWorkspaceServer() {
|
|
|
343
349
|
if (!safeFilename.endsWith(".json")) {
|
|
344
350
|
safeFilename += ".json";
|
|
345
351
|
}
|
|
346
|
-
const filePath = path.join(templatesDir, safeFilename);
|
|
352
|
+
const filePath = (0, projectPaths_1.projectPath)(process.cwd(), path.join(templatesDir, safeFilename));
|
|
347
353
|
fs.writeFileSync(filePath, JSON.stringify(canvasData, null, 2), "utf-8");
|
|
348
354
|
console.log(chalk_1.default.green(`\n[Saved]: Template securely vaulted to manual_flows/templates/${safeFilename}`));
|
|
349
355
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -360,7 +366,8 @@ async function startManualWorkspaceServer() {
|
|
|
360
366
|
error: "Internal server error while saving template.",
|
|
361
367
|
}));
|
|
362
368
|
}
|
|
363
|
-
}
|
|
369
|
+
};
|
|
370
|
+
await savePayload();
|
|
364
371
|
return;
|
|
365
372
|
}
|
|
366
373
|
if (req.method === "GET" && pathname === "/api/templates") {
|
|
@@ -398,7 +405,7 @@ async function startManualWorkspaceServer() {
|
|
|
398
405
|
error: "Forbidden: Only JSON configuration files can be loaded",
|
|
399
406
|
}));
|
|
400
407
|
}
|
|
401
|
-
const filePath = path.join(templatesDir, safeFilename);
|
|
408
|
+
const filePath = (0, projectPaths_1.projectPath)(process.cwd(), path.join(templatesDir, safeFilename));
|
|
402
409
|
if (!fs.existsSync(filePath)) {
|
|
403
410
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
404
411
|
return res.end(JSON.stringify({ error: "Template file not found" }));
|
|
@@ -430,7 +437,7 @@ async function startManualWorkspaceServer() {
|
|
|
430
437
|
error: "Forbidden: Only JSON configuration files can be deleted",
|
|
431
438
|
}));
|
|
432
439
|
}
|
|
433
|
-
const filePath = path.join(templatesDir, safeFilename);
|
|
440
|
+
const filePath = (0, projectPaths_1.projectPath)(process.cwd(), path.join(templatesDir, safeFilename));
|
|
434
441
|
if (!fs.existsSync(filePath)) {
|
|
435
442
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
436
443
|
return res.end(JSON.stringify({ error: "Template file not found" }));
|
|
@@ -453,13 +460,17 @@ async function startManualWorkspaceServer() {
|
|
|
453
460
|
return;
|
|
454
461
|
}
|
|
455
462
|
if (req.method === "POST" && pathname === "/api/save-run") {
|
|
456
|
-
let
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
|
|
463
|
+
let payload;
|
|
464
|
+
try {
|
|
465
|
+
payload = await (0, security_1.readManualPayload)(req);
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
res.writeHead(error instanceof security_1.ManualInputError ? error.status : 400, { 'Content-Type': 'application/json' });
|
|
469
|
+
res.end(JSON.stringify({ error: error instanceof security_1.ManualInputError ? error.message : 'Invalid manual payload' }));
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const savePayload = async () => {
|
|
461
473
|
try {
|
|
462
|
-
const payload = JSON.parse(body);
|
|
463
474
|
const rawFilename = payload.filename || `Execution_Run_${Date.now()}.json`;
|
|
464
475
|
const canvasData = payload.canvasData;
|
|
465
476
|
if (!canvasData) {
|
|
@@ -469,7 +480,7 @@ async function startManualWorkspaceServer() {
|
|
|
469
480
|
const now = new Date();
|
|
470
481
|
const dayFolder = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
471
482
|
const timeFolder = `${String(now.getHours()).padStart(2, "0")}-${String(now.getMinutes()).padStart(2, "0")}-${String(now.getSeconds()).padStart(2, "0")}`;
|
|
472
|
-
const runTargetDir = path.join(runsDir, dayFolder, timeFolder);
|
|
483
|
+
const runTargetDir = (0, projectPaths_1.projectPath)(process.cwd(), path.join(runsDir, dayFolder, timeFolder));
|
|
473
484
|
if (!fs.existsSync(runTargetDir)) {
|
|
474
485
|
fs.mkdirSync(runTargetDir, { recursive: true });
|
|
475
486
|
}
|
|
@@ -477,7 +488,7 @@ async function startManualWorkspaceServer() {
|
|
|
477
488
|
if (!safeFilename.endsWith(".json")) {
|
|
478
489
|
safeFilename += ".json";
|
|
479
490
|
}
|
|
480
|
-
const filePath = path.join(runTargetDir, safeFilename);
|
|
491
|
+
const filePath = (0, projectPaths_1.projectPath)(process.cwd(), path.join(runTargetDir, safeFilename));
|
|
481
492
|
fs.writeFileSync(filePath, JSON.stringify(canvasData, null, 2), "utf-8");
|
|
482
493
|
const relativePath = path
|
|
483
494
|
.join(dayFolder, timeFolder, safeFilename)
|
|
@@ -498,7 +509,8 @@ async function startManualWorkspaceServer() {
|
|
|
498
509
|
error: "Internal server error while saving run.",
|
|
499
510
|
}));
|
|
500
511
|
}
|
|
501
|
-
}
|
|
512
|
+
};
|
|
513
|
+
await savePayload();
|
|
502
514
|
return;
|
|
503
515
|
}
|
|
504
516
|
if (req.method === "GET" && pathname === "/api/runs") {
|
|
@@ -589,7 +601,7 @@ async function startManualWorkspaceServer() {
|
|
|
589
601
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
590
602
|
return res.end(JSON.stringify({ error: "Forbidden: Invalid file path" }));
|
|
591
603
|
}
|
|
592
|
-
const filePath = path.join(runsDir, targetFilename);
|
|
604
|
+
const filePath = (0, projectPaths_1.projectPath)(process.cwd(), path.join(runsDir, targetFilename));
|
|
593
605
|
const resolvedPath = path.resolve(filePath);
|
|
594
606
|
const resolvedRunsDir = path.resolve(runsDir);
|
|
595
607
|
if (!resolvedPath.startsWith(resolvedRunsDir) ||
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Resolve .env before authentication, using the same precedence as execution. */
|
|
2
|
+
export declare function loadCloudEnvironment(root: string, profile?: string): string;
|
|
3
|
+
export declare function cloudSessionDescription(apiBase: string): string;
|
|
4
|
+
export declare function tokenExpiryDescription(data: {
|
|
5
|
+
tokenExpiresAt?: string;
|
|
6
|
+
tokenScope?: string;
|
|
7
|
+
}): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
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.loadCloudEnvironment = loadCloudEnvironment;
|
|
7
|
+
exports.cloudSessionDescription = cloudSessionDescription;
|
|
8
|
+
exports.tokenExpiryDescription = tokenExpiryDescription;
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
12
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
13
|
+
const cloud_1 = require("../../utils/cloud");
|
|
14
|
+
/** Resolve .env before authentication, using the same precedence as execution. */
|
|
15
|
+
function loadCloudEnvironment(root, profile) {
|
|
16
|
+
if (profile && !/^[a-zA-Z0-9_-]+$/.test(profile))
|
|
17
|
+
throw new Error('Invalid environment profile name.');
|
|
18
|
+
const files = profile ? [`.env.${profile}`, '.env'] : ['.env'];
|
|
19
|
+
for (const file of files) {
|
|
20
|
+
const filename = node_path_1.default.join(root, file);
|
|
21
|
+
if (node_fs_1.default.existsSync(filename))
|
|
22
|
+
dotenv_1.default.config({ path: filename, quiet: true });
|
|
23
|
+
}
|
|
24
|
+
return (0, cloud_1.cloudApiBase)(process.env);
|
|
25
|
+
}
|
|
26
|
+
function cloudSessionDescription(apiBase) {
|
|
27
|
+
const source = process.env.FLOWSTRIDE_TOKEN ? 'FLOWSTRIDE_TOKEN (environment)'
|
|
28
|
+
: node_fs_1.default.existsSync(node_path_1.default.join(process.cwd(), '.flowstride', 'credentials.json')) ? 'project credentials'
|
|
29
|
+
: node_fs_1.default.existsSync(node_path_1.default.join(node_os_1.default.homedir(), '.flowstride', 'credentials.json')) ? 'global credentials' : 'no saved credentials';
|
|
30
|
+
return `[Cloud]: ${apiBase}; credential source: ${source}`;
|
|
31
|
+
}
|
|
32
|
+
function tokenExpiryDescription(data) {
|
|
33
|
+
const expiry = data.tokenExpiresAt ? new Date(data.tokenExpiresAt) : null;
|
|
34
|
+
const label = expiry && Number.isFinite(expiry.getTime()) ? expiry.toISOString() : 'unavailable from this Cloud server';
|
|
35
|
+
const scope = ['execution', 'admin', 'readonly'].includes(data.tokenScope || '') ? `; scope: ${data.tokenScope}` : '';
|
|
36
|
+
return `[Cloud]: Token expiry (UTC): ${label}${scope}. Logging in with the same key does not renew it.`;
|
|
37
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
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.validateProArchive = validateProArchive;
|
|
7
|
+
exports.installProEngine = installProEngine;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
11
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
12
|
+
const node_zlib_1 = require("node:zlib");
|
|
13
|
+
const node_child_process_1 = require("node:child_process");
|
|
14
|
+
const tar_1 = require("tar");
|
|
15
|
+
const projectPaths_1 = require("../../utils/projectPaths");
|
|
16
|
+
const MAX_ARCHIVE = 32 * 1024 * 1024;
|
|
17
|
+
async function validateProArchive(buffer) {
|
|
18
|
+
if (!buffer.length || buffer.length > MAX_ARCHIVE)
|
|
19
|
+
throw new Error('Invalid Enterprise archive size');
|
|
20
|
+
const unpacked = (0, node_zlib_1.gunzipSync)(buffer, { maxOutputLength: 128 * 1024 * 1024 });
|
|
21
|
+
const files = new Set();
|
|
22
|
+
let metadata = '';
|
|
23
|
+
const invalid = [];
|
|
24
|
+
await new Promise((resolve, reject) => {
|
|
25
|
+
const parser = new tar_1.Parser({ onReadEntry(entry) {
|
|
26
|
+
const name = entry.path.replace(/\/$/, '');
|
|
27
|
+
if (!/^package(?:\/|$)/.test(name) || /\\/.test(name) || name.split('/').some(part => part === '..' || part === '.' || part === 'node_modules' || part === '.npmrc' || part === '.env' || part.startsWith('.env.')) || !['File', 'Directory'].includes(entry.type))
|
|
28
|
+
invalid.push(name);
|
|
29
|
+
if (files.has(name))
|
|
30
|
+
invalid.push(name);
|
|
31
|
+
files.add(name);
|
|
32
|
+
if (entry.path === 'package/package.json')
|
|
33
|
+
entry.on('data', chunk => { metadata += chunk.toString('utf8'); if (metadata.length > 65536)
|
|
34
|
+
invalid.push('oversized package metadata'); });
|
|
35
|
+
entry.resume();
|
|
36
|
+
}, strict: true });
|
|
37
|
+
parser.on('error', reject);
|
|
38
|
+
parser.on('end', resolve);
|
|
39
|
+
parser.end(unpacked);
|
|
40
|
+
});
|
|
41
|
+
if (invalid.length)
|
|
42
|
+
throw new Error('Enterprise archive contains unsafe paths or entries');
|
|
43
|
+
const pkg = JSON.parse(metadata);
|
|
44
|
+
if (pkg.name !== '@flowstride/pro' || typeof pkg.version !== 'string' || pkg.bin?.flowstride !== 'dist/cli/index.js')
|
|
45
|
+
throw new Error('Enterprise package identity is invalid');
|
|
46
|
+
for (const file of ['dist/cli/index.js', 'dist/runner/flow_worker.js', 'dist/runner/flow_orchestrator.js'])
|
|
47
|
+
if (!files.has(`package/${file}`))
|
|
48
|
+
throw new Error(`Enterprise archive is incomplete: ${file}`);
|
|
49
|
+
return pkg.version;
|
|
50
|
+
}
|
|
51
|
+
async function installProEngine(apiBase, token, root = process.cwd()) {
|
|
52
|
+
const response = await fetch(`${apiBase}/api/workspace/download-pro`, { headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(60000) });
|
|
53
|
+
if (!response.ok)
|
|
54
|
+
throw new Error(`Enterprise download failed (HTTP ${response.status})`);
|
|
55
|
+
const lengthHeader = response.headers.get('content-length');
|
|
56
|
+
const length = lengthHeader === null ? undefined : Number(lengthHeader);
|
|
57
|
+
if (length !== undefined && (!Number.isSafeInteger(length) || length <= 0 || length > MAX_ARCHIVE))
|
|
58
|
+
throw new Error('Invalid Enterprise download size');
|
|
59
|
+
if (!response.body)
|
|
60
|
+
throw new Error('Empty Enterprise download');
|
|
61
|
+
const reader = response.body.getReader();
|
|
62
|
+
const chunks = [];
|
|
63
|
+
let size = 0;
|
|
64
|
+
try {
|
|
65
|
+
while (true) {
|
|
66
|
+
const { done, value } = await reader.read();
|
|
67
|
+
if (done)
|
|
68
|
+
break;
|
|
69
|
+
size += value.length;
|
|
70
|
+
if (size > MAX_ARCHIVE)
|
|
71
|
+
throw new Error('Enterprise download exceeds 32 MiB');
|
|
72
|
+
chunks.push(value);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
await reader.cancel().catch(() => { });
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
reader.releaseLock();
|
|
81
|
+
}
|
|
82
|
+
if (length !== undefined && size !== length)
|
|
83
|
+
throw new Error('Enterprise download is truncated');
|
|
84
|
+
const archive = Buffer.concat(chunks);
|
|
85
|
+
const expected = process.env.FLOWSTRIDE_PRO_SHA256 || response.headers.get('x-flowstride-sha256');
|
|
86
|
+
const actual = node_crypto_1.default.createHash('sha256').update(archive).digest('hex');
|
|
87
|
+
if (expected && (!/^[a-f\d]{64}$/i.test(expected) || actual !== expected.toLowerCase()))
|
|
88
|
+
throw new Error('Enterprise archive checksum mismatch');
|
|
89
|
+
if (!expected)
|
|
90
|
+
console.warn('[Installer]: This Cloud server did not provide an archive checksum. HTTPS and package validation are being used; update Cloud to enable checksum verification.');
|
|
91
|
+
const version = await validateProArchive(archive);
|
|
92
|
+
const engine = (0, projectPaths_1.projectPath)(root, 'node_modules/@flowstride/pro');
|
|
93
|
+
const bins = ['flowstride', 'flowstride.cmd', 'flowstride.ps1'].map(name => node_path_1.default.resolve(root, 'node_modules/.bin', name));
|
|
94
|
+
(0, projectPaths_1.projectPath)(root, 'node_modules/.bin'); // Reject directory symlinks escaping the project; binary symlinks are preserved below.
|
|
95
|
+
const scratch = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'flowstride-pro-install-'));
|
|
96
|
+
const tarball = node_path_1.default.join(scratch, 'engine.tgz');
|
|
97
|
+
const backup = node_path_1.default.join(scratch, 'previous-pro');
|
|
98
|
+
const binState = bins.map(file => { try {
|
|
99
|
+
const st = node_fs_1.default.lstatSync(file);
|
|
100
|
+
return { file, link: st.isSymbolicLink() ? node_fs_1.default.readlinkSync(file) : undefined, data: st.isSymbolicLink() ? undefined : node_fs_1.default.readFileSync(file), mode: st.mode };
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (error.code !== 'ENOENT')
|
|
104
|
+
throw error;
|
|
105
|
+
return { file };
|
|
106
|
+
} });
|
|
107
|
+
let hadEngine = false;
|
|
108
|
+
let mutationStarted = false;
|
|
109
|
+
try {
|
|
110
|
+
node_fs_1.default.writeFileSync(tarball, archive, { mode: 0o600 });
|
|
111
|
+
if (node_fs_1.default.existsSync(engine)) {
|
|
112
|
+
node_fs_1.default.cpSync(engine, backup, { recursive: true, dereference: false });
|
|
113
|
+
hadEngine = true;
|
|
114
|
+
}
|
|
115
|
+
mutationStarted = true;
|
|
116
|
+
(0, node_child_process_1.execFileSync)(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['install', tarball, '--no-save', '--ignore-scripts', '--package-lock=false', '--no-audit', '--no-fund'], { cwd: root, stdio: 'pipe', timeout: 120000, maxBuffer: 1024 * 1024 });
|
|
117
|
+
if (JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(engine, 'package.json'), 'utf8')).version !== version || !node_fs_1.default.existsSync(node_path_1.default.join(engine, 'dist/runner/flow_worker.js')))
|
|
118
|
+
throw new Error('Enterprise installation verification failed');
|
|
119
|
+
return { version, sha256: actual };
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
if (!mutationStarted)
|
|
123
|
+
throw new Error('Enterprise backup failed; the existing installation was left untouched.');
|
|
124
|
+
node_fs_1.default.rmSync(engine, { recursive: true, force: true });
|
|
125
|
+
if (hadEngine) {
|
|
126
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(engine), { recursive: true });
|
|
127
|
+
node_fs_1.default.cpSync(backup, engine, { recursive: true, dereference: false });
|
|
128
|
+
}
|
|
129
|
+
for (const state of binState) {
|
|
130
|
+
node_fs_1.default.rmSync(state.file, { force: true });
|
|
131
|
+
if (state.link !== undefined)
|
|
132
|
+
node_fs_1.default.symlinkSync(state.link, state.file);
|
|
133
|
+
else if (state.data !== undefined)
|
|
134
|
+
node_fs_1.default.writeFileSync(state.file, state.data, { mode: state.mode });
|
|
135
|
+
}
|
|
136
|
+
throw new Error('Enterprise installation failed. The previous engine and CLI launchers were restored; the OSS fallback remains available.');
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
node_fs_1.default.rmSync(scratch, { recursive: true, force: true });
|
|
140
|
+
}
|
|
141
|
+
}
|
package/dist/game/sdk.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.GameSDK = void 0;
|
|
7
|
+
const projectPaths_1 = require("../utils/projectPaths");
|
|
7
8
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
10
|
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
@@ -127,7 +128,7 @@ class GameSDK {
|
|
|
127
128
|
throw new Error('Checkpoint name must use letters, digits, underscores or hyphens.');
|
|
128
129
|
const directory = this.config.artifactsDir || node_path_1.default.resolve('flowstride-artifacts');
|
|
129
130
|
node_fs_1.default.mkdirSync(directory, { recursive: true });
|
|
130
|
-
const prefix =
|
|
131
|
+
const prefix = (0, projectPaths_1.artifactPrefix)(this.config.testFile);
|
|
131
132
|
const file = node_path_1.default.join(directory, prefix + name + '.png');
|
|
132
133
|
if (typeof options === 'object' && options.canvas)
|
|
133
134
|
await this.page().locator(options.canvas).screenshot({ path: file });
|
|
@@ -167,7 +168,7 @@ class GameSDK {
|
|
|
167
168
|
async expectVisual(options) {
|
|
168
169
|
let before;
|
|
169
170
|
if (options.baseline)
|
|
170
|
-
before = node_fs_1.default.readFileSync(
|
|
171
|
+
before = node_fs_1.default.readFileSync((0, projectPaths_1.projectPath)(this.config.projectRoot || process.cwd(), options.baseline));
|
|
171
172
|
else
|
|
172
173
|
before = this.snapshots.get(options.name);
|
|
173
174
|
if (!before)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GameSDK = exports.registerThreeGame = exports.registerGame = void 0;
|
|
4
|
+
var bridge_1 = require("./game/bridge");
|
|
5
|
+
Object.defineProperty(exports, "registerGame", { enumerable: true, get: function () { return bridge_1.registerGame; } });
|
|
6
|
+
var three_bridge_1 = require("./game/three-bridge");
|
|
7
|
+
Object.defineProperty(exports, "registerThreeGame", { enumerable: true, get: function () { return three_bridge_1.registerThreeGame; } });
|
|
8
|
+
var sdk_1 = require("./game/sdk");
|
|
9
|
+
Object.defineProperty(exports, "GameSDK", { enumerable: true, get: function () { return sdk_1.GameSDK; } });
|
|
@@ -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>;
|