flowstride 1.0.19 → 1.0.20-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NATIVE_CI.md +179 -0
- package/dist/adapters/web/playwright.d.ts +1 -0
- package/dist/adapters/web/playwright.js +24 -18
- package/dist/ci/config.d.ts +3 -0
- package/dist/ci/config.js +77 -0
- package/dist/ci/deadline.d.ts +2 -0
- package/dist/ci/deadline.js +16 -0
- package/dist/ci/init.d.ts +1 -0
- package/dist/ci/init.js +21 -0
- package/dist/ci/runtime.d.ts +19 -0
- package/dist/ci/runtime.js +203 -0
- package/dist/cli/commands/run.js +17 -35
- package/dist/cli/index.js +78 -54
- package/dist/cli/manual_server/workspace_server.js +44 -32
- package/dist/cli/utils/cloudSession.d.ts +7 -0
- package/dist/cli/utils/cloudSession.js +37 -0
- package/dist/cli/utils/proInstaller.d.ts +5 -0
- package/dist/cli/utils/proInstaller.js +141 -0
- package/dist/game/bridge.d.ts +13 -0
- package/dist/game/bridge.js +15 -0
- package/dist/game/sdk.d.ts +27 -0
- package/dist/game/sdk.js +256 -0
- package/dist/game/three-bridge.d.ts +18 -0
- package/dist/game/three-bridge.js +59 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +9 -0
- package/dist/manual/security.d.ts +183 -0
- package/dist/manual/security.js +106 -0
- package/dist/runner/data_generators.d.ts +1 -0
- package/dist/runner/data_generators.js +3 -0
- package/dist/runner/dom_stripper.js +3 -0
- package/dist/runner/flow_orchestrator.d.ts +5 -1
- package/dist/runner/flow_orchestrator.js +34 -19
- package/dist/runner/flow_worker.d.ts +1 -0
- package/dist/runner/flow_worker.js +56 -16
- package/dist/runner/persistent_vault.js +2 -1
- package/dist/runner/plugin_manager.d.ts +1 -0
- package/dist/runner/plugin_manager.js +35 -1
- package/dist/security/localDashboard.d.ts +2 -0
- package/dist/security/localDashboard.js +22 -0
- package/dist/security/redaction.d.ts +4 -0
- package/dist/security/redaction.js +49 -0
- package/dist/types/index.d.ts +43 -0
- package/dist/utils/cloud.d.ts +6 -0
- package/dist/utils/cloud.js +33 -0
- package/dist/utils/projectPaths.d.ts +11 -0
- package/dist/utils/projectPaths.js +90 -0
- package/examples/native-game/flows/canvas.flow +13 -0
- package/examples/native-game/flowstride.config.json +12 -0
- package/examples/native-game/server.mjs +12 -0
- package/package.json +27 -11
- package/src/ui/dist/assets/index-DVwp0K-J.js +462 -0
- package/src/ui/dist/index.html +1 -1
- package/src/ui/dist/assets/index-DIC22ZK_.js +0 -638
package/dist/cli/commands/run.js
CHANGED
|
@@ -34,6 +34,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.runAction = runAction;
|
|
37
|
+
const projectPaths_1 = require("../../utils/projectPaths");
|
|
38
|
+
const config_1 = require("../../ci/config");
|
|
39
|
+
const runtime_1 = require("../../ci/runtime");
|
|
37
40
|
const fs = __importStar(require("fs"));
|
|
38
41
|
const path = __importStar(require("path"));
|
|
39
42
|
const dotenv = __importStar(require("dotenv"));
|
|
@@ -51,7 +54,7 @@ const api_proxy_1 = require("../../sdk/api_proxy");
|
|
|
51
54
|
async function runAction(targetPath, options) {
|
|
52
55
|
const root = process.cwd();
|
|
53
56
|
const profileEnvPath = options.env
|
|
54
|
-
?
|
|
57
|
+
? (0, projectPaths_1.projectPath)(root, `.env.${options.env}`)
|
|
55
58
|
: null;
|
|
56
59
|
const defaultEnvPath = path.resolve(root, ".env");
|
|
57
60
|
if (profileEnvPath) {
|
|
@@ -69,12 +72,8 @@ async function runAction(targetPath, options) {
|
|
|
69
72
|
console.log(`[Environment]: Loaded default .env file`);
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
process.env.GITLAB_CI ||
|
|
75
|
-
process.env.CIRCLECI ||
|
|
76
|
-
process.env.JENKINS_URL ||
|
|
77
|
-
process.env.FLOWSTRIDE_API_KEY);
|
|
75
|
+
const config = (0, config_1.loadNativeConfig)(root, options);
|
|
76
|
+
const isCI = !!config.ci;
|
|
78
77
|
if (isCI) {
|
|
79
78
|
console.log("\n[CI/CD]: Environment detected. Engaging Smart Automation Behaviors...");
|
|
80
79
|
}
|
|
@@ -84,39 +83,13 @@ async function runAction(targetPath, options) {
|
|
|
84
83
|
process.exit(1);
|
|
85
84
|
}
|
|
86
85
|
console.log(`\n[Flowstride]: Starting execution suite at "${resolvedPath}"...`);
|
|
87
|
-
const configPath = path.resolve(root, "flowstride.config.json");
|
|
88
|
-
let localConfig = {};
|
|
89
|
-
if (fs.existsSync(configPath)) {
|
|
90
|
-
try {
|
|
91
|
-
const fileContent = fs.readFileSync(configPath, "utf-8");
|
|
92
|
-
localConfig = JSON.parse(fileContent);
|
|
93
|
-
console.log(`[Config]: Loaded project configuration from flowstride.config.json`);
|
|
94
|
-
}
|
|
95
|
-
catch (e) {
|
|
96
|
-
console.warn(`[Warning]: Could not parse flowstride.config.json: ${e.message}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
const config = {
|
|
100
|
-
headless: isCI
|
|
101
|
-
? true
|
|
102
|
-
: options.headed
|
|
103
|
-
? false
|
|
104
|
-
: (localConfig.headless ?? true),
|
|
105
|
-
timeout: localConfig.timeout ?? 30000,
|
|
106
|
-
bail: options.bail ?? localConfig.bail ?? false,
|
|
107
|
-
baseUrl: process.env.BASE_URL ?? localConfig.baseUrl,
|
|
108
|
-
defaultDialogBehavior: isCI
|
|
109
|
-
? "dismiss"
|
|
110
|
-
: (localConfig.defaultDialogBehavior ?? "dismiss"),
|
|
111
|
-
pluginsDir: localConfig.pluginsDir,
|
|
112
|
-
};
|
|
113
86
|
if (isCI) {
|
|
114
87
|
console.log(` > Headless Mode: ${config.headless ? "Forced ON" : "OFF"}`);
|
|
115
88
|
console.log(` > Dialog Protection: ${(config.defaultDialogBehavior || "dismiss").toUpperCase()}`);
|
|
116
89
|
console.log(` > Artifact Auto-Staging: ENABLED\n`);
|
|
117
90
|
}
|
|
118
|
-
let workerCount = options.workers ?
|
|
119
|
-
if (
|
|
91
|
+
let workerCount = options.workers ? Number(options.workers) : 1;
|
|
92
|
+
if (!Number.isSafeInteger(workerCount) || workerCount < 1) {
|
|
120
93
|
console.error(`\n[Error]: Invalid worker count "${options.workers}". Must be an integer greater than 0.`);
|
|
121
94
|
process.exit(1);
|
|
122
95
|
}
|
|
@@ -126,6 +99,8 @@ async function runAction(targetPath, options) {
|
|
|
126
99
|
console.log(`[Warning]: Auto-capping to ${maxSafeWorkers} workers to prevent system thrashing and crashes.\n`);
|
|
127
100
|
workerCount = maxSafeWorkers;
|
|
128
101
|
}
|
|
102
|
+
if (config.manifest?.maxWorkers !== undefined && config.manifest.maxWorkers !== 'unlimited')
|
|
103
|
+
workerCount = Math.min(workerCount, config.manifest.maxWorkers);
|
|
129
104
|
const web = new playwright_1.WebAdapter(config);
|
|
130
105
|
const api = new undici_1.ApiAdapter(config);
|
|
131
106
|
const store = new temporary_store_1.TemporaryStore();
|
|
@@ -144,6 +119,8 @@ async function runAction(targetPath, options) {
|
|
|
144
119
|
try {
|
|
145
120
|
const proWorkerModule = await import((0, url_1.pathToFileURL)(proWorkerPath).href);
|
|
146
121
|
const proOrchModule = await import((0, url_1.pathToFileURL)(proOrchPath).href);
|
|
122
|
+
if (config.ci && proOrchModule.FlowOrchestrator.nativeCIProtocol !== 1)
|
|
123
|
+
throw new Error('Installed Pro engine does not support native CI protocol 1; install the matching Pro candidate.');
|
|
147
124
|
ActiveFlowWorker = proWorkerModule.FlowWorker;
|
|
148
125
|
ActiveFlowOrchestrator = proOrchModule.FlowOrchestrator;
|
|
149
126
|
engineMode = "Enterprise (Pro)";
|
|
@@ -155,6 +132,11 @@ async function runAction(targetPath, options) {
|
|
|
155
132
|
console.log(`\n[Engine]: Booting Flowstride in ${engineMode} mode...`);
|
|
156
133
|
const worker = new ActiveFlowWorker(config, web, api, store, sessions, pluginManager);
|
|
157
134
|
const orchestrator = new ActiveFlowOrchestrator(worker, workerCount, config);
|
|
135
|
+
if (isCI) {
|
|
136
|
+
const result = await (0, runtime_1.runNativeCI)(worker, orchestrator, resolvedPath, config, engineMode);
|
|
137
|
+
console.log(`[Flowstride]: ${result.status}; reports: ${config.artifactsDir}`);
|
|
138
|
+
process.exit(result.status === 'passed' ? 0 : result.status === 'interrupted' ? 130 : 1);
|
|
139
|
+
}
|
|
158
140
|
try {
|
|
159
141
|
await orchestrator.start(resolvedPath);
|
|
160
142
|
console.log("\n[Flowstride]: Execution suite completed successfully.");
|
package/dist/cli/index.js
CHANGED
|
@@ -37,29 +37,38 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
38
|
};
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const cloud_1 = require("../utils/cloud");
|
|
41
|
+
const cloudSession_1 = require("./utils/cloudSession");
|
|
42
|
+
const config_1 = require("../ci/config");
|
|
43
|
+
const init_1 = require("../ci/init");
|
|
40
44
|
const commander_1 = require("commander");
|
|
41
45
|
const run_1 = require("./commands/run");
|
|
42
46
|
const report_1 = require("./commands/report");
|
|
43
|
-
const
|
|
47
|
+
const init_2 = require("./commands/init");
|
|
44
48
|
const open_1 = require("./commands/open");
|
|
45
49
|
const fs = __importStar(require("fs"));
|
|
46
50
|
const path = __importStar(require("path"));
|
|
47
51
|
const os = __importStar(require("os"));
|
|
48
|
-
const
|
|
52
|
+
const proInstaller_1 = require("./utils/proInstaller");
|
|
49
53
|
const prompts_1 = require("@inquirer/prompts");
|
|
50
54
|
const chalk_1 = __importDefault(require("chalk"));
|
|
51
55
|
const ora_1 = __importDefault(require("ora"));
|
|
52
56
|
const program = new commander_1.Command();
|
|
53
|
-
|
|
57
|
+
program.hook('preAction', (_program, command) => {
|
|
58
|
+
if (['login', 'status', 'run', 'update'].includes(command.name())) {
|
|
59
|
+
API_BASE = (0, cloudSession_1.loadCloudEnvironment)(process.cwd(), command.opts().env);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
let API_BASE = (0, cloud_1.cloudApiBase)(process.env);
|
|
54
63
|
program
|
|
55
64
|
.name("flowstride")
|
|
56
65
|
.description("Flowstride: The Enterprise Flow-First Automation CLI")
|
|
57
|
-
.version("1.0.
|
|
66
|
+
.version("1.0.20-rc.6");
|
|
58
67
|
program
|
|
59
68
|
.command("init")
|
|
60
69
|
.description("Initialise a new Flowstride workspace in the current directory")
|
|
61
70
|
.action(async () => {
|
|
62
|
-
await (0,
|
|
71
|
+
await (0, init_2.initAction)();
|
|
63
72
|
});
|
|
64
73
|
program
|
|
65
74
|
.command("login")
|
|
@@ -99,6 +108,7 @@ program
|
|
|
99
108
|
console.error(chalk_1.default.red("✖ Error: A valid Developer Token is required."));
|
|
100
109
|
process.exit(1);
|
|
101
110
|
}
|
|
111
|
+
console.log(`[Cloud]: ${API_BASE}; login credential source: ${process.env.FLOWSTRIDE_TOKEN ? 'FLOWSTRIDE_TOKEN (environment)' : options.token ? '--token option' : 'interactive prompt'}`);
|
|
102
112
|
const deviceName = os.hostname();
|
|
103
113
|
const spinner = (0, ora_1.default)("Authenticating with Cloud...").start();
|
|
104
114
|
try {
|
|
@@ -112,10 +122,11 @@ program
|
|
|
112
122
|
});
|
|
113
123
|
const data = await response.json();
|
|
114
124
|
if (!response.ok) {
|
|
115
|
-
spinner.fail(chalk_1.default.red(`Authentication Failed: ${
|
|
125
|
+
spinner.fail(chalk_1.default.red(`Authentication Failed: ${(0, cloud_1.cloudFailure)({ response: { status: response.status, data } }, [token.trim()]).message}`));
|
|
116
126
|
process.exit(1);
|
|
117
127
|
}
|
|
118
128
|
spinner.succeed(chalk_1.default.green("Authentication Successful!"));
|
|
129
|
+
console.log((0, cloudSession_1.tokenExpiryDescription)(data));
|
|
119
130
|
const configDir = options.global
|
|
120
131
|
? path.join(os.homedir(), ".flowstride")
|
|
121
132
|
: path.join(process.cwd(), ".flowstride");
|
|
@@ -144,42 +155,14 @@ program
|
|
|
144
155
|
}
|
|
145
156
|
if (installPro) {
|
|
146
157
|
const installSpinner = (0, ora_1.default)("Downloading Enterprise Engine via secure proxy...").start();
|
|
147
|
-
const tarballPath = path.join(process.cwd(), "flowstride-pro.tgz");
|
|
148
158
|
try {
|
|
149
|
-
|
|
150
|
-
method: "GET",
|
|
151
|
-
headers: { Authorization: `Bearer ${token.trim()}` },
|
|
152
|
-
});
|
|
153
|
-
if (!tarballResponse.ok) {
|
|
154
|
-
throw new Error(`Secure proxy rejected request: ${tarballResponse.statusText}`);
|
|
155
|
-
}
|
|
156
|
-
const buffer = await tarballResponse.arrayBuffer();
|
|
157
|
-
fs.writeFileSync(tarballPath, Buffer.from(buffer));
|
|
158
|
-
installSpinner.text = "Wiring up Enterprise dependencies...";
|
|
159
|
-
installSpinner.color = "cyan";
|
|
160
|
-
// Let npm natively handle the extraction and binary symlinking
|
|
161
|
-
(0, child_process_1.execSync)(`npm install "${tarballPath}" --no-save`, {
|
|
162
|
-
cwd: process.cwd(),
|
|
163
|
-
stdio: "ignore",
|
|
164
|
-
});
|
|
159
|
+
await (0, proInstaller_1.installProEngine)(API_BASE, token.trim());
|
|
165
160
|
installSpinner.succeed(chalk_1.default.green("Enterprise Engine installed successfully!"));
|
|
166
161
|
}
|
|
167
|
-
catch (
|
|
168
|
-
installSpinner.
|
|
169
|
-
console.
|
|
170
|
-
|
|
171
|
-
if (fs.existsSync(nodeModulesPath)) {
|
|
172
|
-
fs.rmSync(nodeModulesPath, { recursive: true, force: true });
|
|
173
|
-
}
|
|
174
|
-
console.log(chalk_1.default.yellow("⚠️ Reverting login: Enterprise Engine is required to sync."));
|
|
175
|
-
if (fs.existsSync(credentialsPath))
|
|
176
|
-
fs.unlinkSync(credentialsPath);
|
|
177
|
-
process.exit(1);
|
|
178
|
-
}
|
|
179
|
-
finally {
|
|
180
|
-
if (fs.existsSync(tarballPath)) {
|
|
181
|
-
fs.unlinkSync(tarballPath);
|
|
182
|
-
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
installSpinner.fail(error instanceof Error ? error.message : "Enterprise installation failed");
|
|
164
|
+
console.log(chalk_1.default.yellow("Credentials remain available. You can retry installation; runs can fall back to the Open Source engine."));
|
|
165
|
+
process.exitCode = 1;
|
|
183
166
|
}
|
|
184
167
|
}
|
|
185
168
|
else {
|
|
@@ -194,7 +177,7 @@ program
|
|
|
194
177
|
console.log(chalk_1.default.bold.green(`\n✅ Machine synchronized with Flowstride Enterprise.`));
|
|
195
178
|
}
|
|
196
179
|
catch (error) {
|
|
197
|
-
spinner.fail(chalk_1.default.red(
|
|
180
|
+
spinner.fail(chalk_1.default.red((0, cloud_1.cloudFailure)(error, [token.trim()]).message));
|
|
198
181
|
process.exit(1);
|
|
199
182
|
}
|
|
200
183
|
});
|
|
@@ -202,7 +185,8 @@ program
|
|
|
202
185
|
.command("status")
|
|
203
186
|
.description("Display the current Flowstride CLI authentication and engine tier")
|
|
204
187
|
.action(async () => {
|
|
205
|
-
|
|
188
|
+
console.log((0, cloudSession_1.cloudSessionDescription)(API_BASE));
|
|
189
|
+
let token = process.env.FLOWSTRIDE_TOKEN?.trim() || null;
|
|
206
190
|
const localCredentialsPath = path.join(process.cwd(), ".flowstride", "credentials.json");
|
|
207
191
|
const globalCredentialsPath = path.join(os.homedir(), ".flowstride", "credentials.json");
|
|
208
192
|
let activeCredentialsPath = localCredentialsPath; // Default for cleanup
|
|
@@ -211,7 +195,7 @@ program
|
|
|
211
195
|
activeCredentialsPath = localCredentialsPath;
|
|
212
196
|
try {
|
|
213
197
|
const credentials = JSON.parse(fs.readFileSync(localCredentialsPath, "utf8"));
|
|
214
|
-
token = credentials.machineToken;
|
|
198
|
+
token = credentials.machineToken?.trim() || null;
|
|
215
199
|
}
|
|
216
200
|
catch (err) { }
|
|
217
201
|
}
|
|
@@ -219,7 +203,7 @@ program
|
|
|
219
203
|
activeCredentialsPath = globalCredentialsPath;
|
|
220
204
|
try {
|
|
221
205
|
const credentials = JSON.parse(fs.readFileSync(globalCredentialsPath, "utf8"));
|
|
222
|
-
token = credentials.machineToken;
|
|
206
|
+
token = credentials.machineToken?.trim() || null;
|
|
223
207
|
}
|
|
224
208
|
catch (err) { }
|
|
225
209
|
}
|
|
@@ -241,9 +225,10 @@ program
|
|
|
241
225
|
method: "GET",
|
|
242
226
|
headers: { Authorization: `Bearer ${token}` },
|
|
243
227
|
});
|
|
244
|
-
if (response.status === 401) {
|
|
245
|
-
|
|
246
|
-
|
|
228
|
+
if (response.status === 401 || response.status === 403) {
|
|
229
|
+
const failureData = await response.json().catch(() => ({}));
|
|
230
|
+
console.log(chalk_1.default.red((0, cloud_1.cloudFailure)({ response: { status: response.status, data: failureData } }, [token]).message));
|
|
231
|
+
if (response.status === 401 && !process.env.FLOWSTRIDE_TOKEN && fs.existsSync(activeCredentialsPath))
|
|
247
232
|
fs.unlinkSync(activeCredentialsPath);
|
|
248
233
|
}
|
|
249
234
|
else if (response.ok) {
|
|
@@ -271,12 +256,19 @@ program
|
|
|
271
256
|
.option("--headed", "Run browser in headed mode")
|
|
272
257
|
.option("--headless", "Run browser in headless mode")
|
|
273
258
|
.option("--workers <number>", "Number of parallel workers to spawn (Pro feature)")
|
|
259
|
+
.option('--ci', 'Run without a dashboard and write CI reports')
|
|
260
|
+
.option('--device <profile>', 'desktop, phone, or landscape')
|
|
261
|
+
.option('--config <file>', 'Path to the Flowstride configuration')
|
|
262
|
+
.option('--env <name>', 'Environment profile')
|
|
274
263
|
.action(async (targetPath, options) => {
|
|
275
264
|
if (typeof targetPath === "object") {
|
|
276
265
|
options = targetPath;
|
|
277
266
|
targetPath = undefined;
|
|
278
267
|
}
|
|
279
|
-
|
|
268
|
+
if (options.ci)
|
|
269
|
+
process.env.FLOWSTRIDE_NATIVE_CI = '1';
|
|
270
|
+
console.log((0, cloudSession_1.cloudSessionDescription)(API_BASE));
|
|
271
|
+
let token = process.env.FLOWSTRIDE_TOKEN?.trim() || null;
|
|
280
272
|
let forceOS = false;
|
|
281
273
|
const localCredentialsPath = path.join(process.cwd(), ".flowstride", "credentials.json");
|
|
282
274
|
const globalCredentialsPath = path.join(os.homedir(), ".flowstride", "credentials.json");
|
|
@@ -286,7 +278,7 @@ program
|
|
|
286
278
|
activeCredentialsPath = localCredentialsPath;
|
|
287
279
|
try {
|
|
288
280
|
const credentials = JSON.parse(fs.readFileSync(localCredentialsPath, "utf8"));
|
|
289
|
-
token = credentials.machineToken;
|
|
281
|
+
token = credentials.machineToken?.trim() || null;
|
|
290
282
|
}
|
|
291
283
|
catch (err) { }
|
|
292
284
|
}
|
|
@@ -294,7 +286,7 @@ program
|
|
|
294
286
|
activeCredentialsPath = globalCredentialsPath;
|
|
295
287
|
try {
|
|
296
288
|
const credentials = JSON.parse(fs.readFileSync(globalCredentialsPath, "utf8"));
|
|
297
|
-
token = credentials.machineToken;
|
|
289
|
+
token = credentials.machineToken?.trim() || null;
|
|
298
290
|
}
|
|
299
291
|
catch (err) { }
|
|
300
292
|
}
|
|
@@ -317,15 +309,29 @@ program
|
|
|
317
309
|
method: "GET",
|
|
318
310
|
headers: { Authorization: `Bearer ${token}` },
|
|
319
311
|
});
|
|
320
|
-
if (response.status === 401) {
|
|
321
|
-
|
|
322
|
-
|
|
312
|
+
if (response.status === 401 || response.status === 403) {
|
|
313
|
+
const failureData = await response.json().catch(() => ({}));
|
|
314
|
+
console.log(chalk_1.default.red((0, cloud_1.cloudFailure)({ response: { status: response.status, data: failureData } }, [token]).message));
|
|
315
|
+
if (response.status === 401 && !process.env.FLOWSTRIDE_TOKEN && fs.existsSync(activeCredentialsPath))
|
|
323
316
|
fs.unlinkSync(activeCredentialsPath);
|
|
324
317
|
forceOS = true;
|
|
325
318
|
}
|
|
326
319
|
else if (response.ok) {
|
|
327
320
|
const data = await response.json();
|
|
328
321
|
const tier = data.tier.toUpperCase();
|
|
322
|
+
if ((0, config_1.loadNativeConfig)(process.cwd(), options).ci && fs.existsSync(nodeModulesPath)) {
|
|
323
|
+
const workspaceResponse = await fetch(`${API_BASE}/api/workspace/status`, {
|
|
324
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
325
|
+
});
|
|
326
|
+
if (!workspaceResponse.ok)
|
|
327
|
+
forceOS = await handleOfflinePrompt();
|
|
328
|
+
else {
|
|
329
|
+
const workspaceData = await workspaceResponse.json();
|
|
330
|
+
options.manifest = workspaceData.manifest;
|
|
331
|
+
options.workspaceId = workspaceData.workspace?._id;
|
|
332
|
+
options.isCloudSyncActive = true;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
329
335
|
if (tier === "PERSONAL") {
|
|
330
336
|
console.log(chalk_1.default.yellow(`🟡 [OS]: [${data.workspaceName}] (Tier: PERSONAL)`));
|
|
331
337
|
}
|
|
@@ -344,7 +350,7 @@ program
|
|
|
344
350
|
if (options.headed) {
|
|
345
351
|
options.headless = false;
|
|
346
352
|
}
|
|
347
|
-
const resolvedTargetPath = targetPath
|
|
353
|
+
const resolvedTargetPath = targetPath || (0, config_1.loadNativeConfig)(process.cwd(), options).testDir;
|
|
348
354
|
const strictUiFlowsDir = path.resolve(process.cwd(), resolvedTargetPath);
|
|
349
355
|
if (!fs.existsSync(strictUiFlowsDir)) {
|
|
350
356
|
console.error(`\n[ERROR]: Could not find ${strictUiFlowsDir}.`);
|
|
@@ -352,11 +358,17 @@ program
|
|
|
352
358
|
process.exit(1);
|
|
353
359
|
}
|
|
354
360
|
options.forceOS = forceOS;
|
|
361
|
+
if (!forceOS && token) {
|
|
362
|
+
options.machineToken = token;
|
|
363
|
+
const file = fs.existsSync(localCredentialsPath) ? localCredentialsPath : globalCredentialsPath;
|
|
364
|
+
if (!options.workspaceId && fs.existsSync(file))
|
|
365
|
+
options.workspaceId = JSON.parse(fs.readFileSync(file, 'utf8')).workspaceId;
|
|
366
|
+
}
|
|
355
367
|
await (0, run_1.runAction)(strictUiFlowsDir, options);
|
|
356
368
|
});
|
|
357
369
|
async function handleOfflinePrompt() {
|
|
358
370
|
console.log(chalk_1.default.yellow("🛜 OFFLINE: Unable to reach Flowstride Cloud. Enterprise Sync paused."));
|
|
359
|
-
if (process.env.CI || process.env.FLOWSTRIDE_TOKEN) {
|
|
371
|
+
if (process.env.CI || process.env.FLOWSTRIDE_NATIVE_CI || process.env.FLOWSTRIDE_TOKEN) {
|
|
360
372
|
console.log(chalk_1.default.yellow("⚠️ CI/CD Environment: Automatically falling back to OS Engine."));
|
|
361
373
|
return true;
|
|
362
374
|
}
|
|
@@ -381,4 +393,16 @@ program
|
|
|
381
393
|
.action(async () => {
|
|
382
394
|
await (0, open_1.openAction)();
|
|
383
395
|
});
|
|
396
|
+
program.command('install')
|
|
397
|
+
.description('Install Chromium matching this Flowstride engine')
|
|
398
|
+
.option('--with-deps', 'Install operating-system browser dependencies')
|
|
399
|
+
.action((options) => {
|
|
400
|
+
const { execFileSync } = require('node:child_process');
|
|
401
|
+
execFileSync(process.execPath, [path.join(path.dirname(require.resolve('playwright/package.json')), 'cli.js'), 'install', ...(options.withDeps ? ['--with-deps'] : []), 'chromium'], { stdio: 'inherit', env: { ...process.env, PLAYWRIGHT_SKIP_BROWSER_GC: '1' } });
|
|
402
|
+
});
|
|
403
|
+
program.command('ci')
|
|
404
|
+
.description('Set up native Flowstride CI')
|
|
405
|
+
.command('init <provider>')
|
|
406
|
+
.description('Generate a GitHub Actions workflow without overwriting existing files')
|
|
407
|
+
.action((provider) => (0, init_1.initCI)(provider));
|
|
384
408
|
program.parse(process.argv);
|
|
@@ -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
|
+
}
|