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.
Files changed (43) hide show
  1. package/NATIVE_CI.md +88 -145
  2. package/README.md +46 -221
  3. package/dist/adapters/web/playwright.d.ts +4 -1
  4. package/dist/adapters/web/playwright.js +75 -6
  5. package/dist/ci/config.js +6 -5
  6. package/dist/ci/runtime.js +27 -14
  7. package/dist/cli/commands/run.js +2 -1
  8. package/dist/cli/index.js +35 -50
  9. package/dist/cli/manual_server/workspace_server.js +44 -32
  10. package/dist/cli/utils/cloudSession.d.ts +7 -0
  11. package/dist/cli/utils/cloudSession.js +37 -0
  12. package/dist/cli/utils/proInstaller.d.ts +5 -0
  13. package/dist/cli/utils/proInstaller.js +141 -0
  14. package/dist/game/sdk.js +3 -2
  15. package/dist/index.d.ts +5 -0
  16. package/dist/index.js +9 -0
  17. package/dist/manual/security.d.ts +183 -0
  18. package/dist/manual/security.js +106 -0
  19. package/dist/runner/data_generators.d.ts +1 -0
  20. package/dist/runner/data_generators.js +3 -0
  21. package/dist/runner/dom_stripper.js +3 -0
  22. package/dist/runner/flow_orchestrator.js +13 -22
  23. package/dist/runner/flow_worker.d.ts +1 -0
  24. package/dist/runner/flow_worker.js +68 -47
  25. package/dist/runner/persistent_vault.js +2 -1
  26. package/dist/runner/plugin_manager.js +3 -1
  27. package/dist/security/localDashboard.d.ts +2 -0
  28. package/dist/security/localDashboard.js +22 -0
  29. package/dist/security/redaction.d.ts +4 -0
  30. package/dist/security/redaction.js +51 -0
  31. package/dist/sessions/manager.js +3 -1
  32. package/dist/types/index.d.ts +9 -0
  33. package/dist/utils/cloud.d.ts +6 -0
  34. package/dist/utils/cloud.js +33 -0
  35. package/dist/utils/projectPaths.d.ts +11 -0
  36. package/dist/utils/projectPaths.js +90 -0
  37. package/dist/utils/replayRequest.d.ts +8 -0
  38. package/dist/utils/replayRequest.js +77 -0
  39. package/package.json +23 -14
  40. package/src/ui/dist/assets/index-B7MuBTWm.js +460 -0
  41. package/src/ui/dist/index.html +1 -1
  42. package/RELEASE_NOTES.md +0 -40
  43. 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 body = "";
330
- req.on("data", (chunk) => {
331
- body += chunk.toString();
332
- });
333
- req.on("end", () => {
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 body = "";
457
- req.on("data", (chunk) => {
458
- body += chunk.toString();
459
- });
460
- req.on("end", async () => {
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,5 @@
1
+ export declare function validateProArchive(buffer: Buffer): Promise<string>;
2
+ export declare function installProEngine(apiBase: string, token: string, root?: string): Promise<{
3
+ version: string;
4
+ sha256: string;
5
+ }>;
@@ -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 = this.config.testFile ? this.config.testFile.replace(/[^a-zA-Z0-9_.-]/g, '_') + '-' : '';
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(node_path_1.default.resolve(options.baseline));
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)
@@ -0,0 +1,5 @@
1
+ export { registerGame } from './game/bridge';
2
+ export { registerThreeGame } from './game/three-bridge';
3
+ export { GameSDK } from './game/sdk';
4
+ export type { NativeRunResult } from './ci/runtime';
5
+ export type { FlowstrideConfig } from './types';
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>;