flowstride 1.0.3 → 1.0.7

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.
@@ -73,11 +73,11 @@ async function runAction(targetPath, options) {
73
73
  process.env.GITHUB_ACTIONS ||
74
74
  process.env.GITLAB_CI ||
75
75
  process.env.CIRCLECI ||
76
- process.env.JENKINS_URL);
76
+ process.env.JENKINS_URL ||
77
+ process.env.FLOWSTRIDE_API_KEY);
77
78
  if (isCI) {
78
79
  console.log("\n[CI/CD]: Environment detected. Engaging Smart Automation Behaviors...");
79
80
  }
80
- // The CLI (index.ts) now strictly guarantees targetPath is the absolute path to ./flows
81
81
  const resolvedPath = targetPath;
82
82
  if (!fs.existsSync(resolvedPath)) {
83
83
  console.error(`\n[Error]: Cannot find strict target path "${resolvedPath}".`);
@@ -135,12 +135,13 @@ async function runAction(targetPath, options) {
135
135
  let ActiveFlowWorker = flow_worker_1.FlowWorker;
136
136
  let ActiveFlowOrchestrator = flow_orchestrator_1.FlowOrchestrator;
137
137
  let engineMode = "Open Source";
138
- // Check the local node_modules for the Pro engine
139
138
  const proWorkerPath = path.join(process.cwd(), "node_modules", "@flowstride", "pro", "dist", "runner", "flow_worker.js");
140
139
  const proOrchPath = path.join(process.cwd(), "node_modules", "@flowstride", "pro", "dist", "runner", "flow_orchestrator.js");
141
- if (fs.existsSync(proWorkerPath) && fs.existsSync(proOrchPath)) {
140
+ // We only attempt to load the Pro Engine if the DRM Bouncer (index.ts) did NOT force OS Mode
141
+ if (!options.forceOS &&
142
+ fs.existsSync(proWorkerPath) &&
143
+ fs.existsSync(proOrchPath)) {
142
144
  try {
143
- // Dynamically load the Pro classes using native absolute URL resolution
144
145
  const proWorkerModule = await import((0, url_1.pathToFileURL)(proWorkerPath).href);
145
146
  const proOrchModule = await import((0, url_1.pathToFileURL)(proOrchPath).href);
146
147
  ActiveFlowWorker = proWorkerModule.FlowWorker;
@@ -153,7 +154,6 @@ async function runAction(targetPath, options) {
153
154
  }
154
155
  console.log(`\n[Engine]: Booting Flowstride in ${engineMode} mode...`);
155
156
  // ==========================================
156
- // Instantiate the chosen engine (OS or Pro)
157
157
  const worker = new ActiveFlowWorker(config, web, api, store, sessions, pluginManager);
158
158
  const orchestrator = new ActiveFlowOrchestrator(worker, workerCount, config);
159
159
  try {
package/dist/cli/index.js CHANGED
@@ -44,15 +44,16 @@ const init_1 = require("./commands/init");
44
44
  const fs = __importStar(require("fs"));
45
45
  const path = __importStar(require("path"));
46
46
  const os = __importStar(require("os"));
47
- const child_process_1 = require("child_process");
47
+ const tar = __importStar(require("tar"));
48
48
  const prompts_1 = require("@inquirer/prompts");
49
49
  const chalk_1 = __importDefault(require("chalk"));
50
50
  const ora_1 = __importDefault(require("ora"));
51
51
  const program = new commander_1.Command();
52
+ const API_BASE = process.env.FLOWSTRIDE_API_URL || "https://cloud.flowstride.io";
52
53
  program
53
54
  .name("flowstride")
54
55
  .description("Flowstride: The Enterprise Flow-First Automation CLI")
55
- .version("1.0.3");
56
+ .version("1.0.7");
56
57
  program
57
58
  .command("init")
58
59
  .description("Initialise a new Flowstride workspace in the current directory")
@@ -63,20 +64,41 @@ program
63
64
  .command("login")
64
65
  .description("Authenticate this machine with Flowstride Enterprise Cloud")
65
66
  .action(async () => {
67
+ const localPackageJsonPath = path.join(process.cwd(), "package.json");
68
+ if (!fs.existsSync(localPackageJsonPath)) {
69
+ console.error(chalk_1.default.red("✖ Error: Please navigate to a valid Flowstride project directory before logging in."));
70
+ process.exit(1);
71
+ }
72
+ const pkg = JSON.parse(fs.readFileSync(localPackageJsonPath, "utf8"));
73
+ const deps = {
74
+ ...(pkg.dependencies || {}),
75
+ ...(pkg.devDependencies || {}),
76
+ };
77
+ if (!deps["flowstride"] && !deps["flowstride-os"]) {
78
+ console.error(chalk_1.default.red("✖ Error: Please navigate to a valid Flowstride project directory before logging in."));
79
+ process.exit(1);
80
+ }
66
81
  console.log(chalk_1.default.bold.blue("\n🌐 Flowstride Enterprise Cloud Sync\n"));
67
- const userEmail = await (0, prompts_1.input)({ message: "Enter your Flowstride email:" });
68
- const userPass = await (0, prompts_1.password)({ message: "Enter your password:" });
69
- // Silently grab the computer's hostname without bothering the user
82
+ let authHeader = "";
83
+ if (process.env.FLOWSTRIDE_API_KEY) {
84
+ console.log(chalk_1.default.yellow("🤖 CI/CD Environment Detected. Authenticating via API Key..."));
85
+ authHeader = `Bearer ${process.env.FLOWSTRIDE_API_KEY}`;
86
+ }
87
+ else {
88
+ const userEmail = await (0, prompts_1.input)({
89
+ message: "Enter your Flowstride email:",
90
+ });
91
+ const userPass = await (0, prompts_1.password)({ message: "Enter your password:" });
92
+ authHeader = `Basic ${Buffer.from(`${userEmail}:${userPass}`).toString("base64")}`;
93
+ }
70
94
  const deviceName = os.hostname();
71
95
  const spinner = (0, ora_1.default)("Authenticating with Cloud...").start();
72
96
  try {
73
- // 1. Authenticate with Next.js Server
74
- const authString = Buffer.from(`${userEmail}:${userPass}`).toString("base64");
75
- const response = await fetch("https://cloud.flowstride.io/api/auth/cli-sync", {
97
+ const response = await fetch(`${API_BASE}/api/auth/cli-sync`, {
76
98
  method: "POST",
77
99
  headers: {
78
100
  "Content-Type": "application/json",
79
- Authorization: `Basic ${authString}`,
101
+ Authorization: authHeader,
80
102
  },
81
103
  body: JSON.stringify({ deviceName }),
82
104
  });
@@ -86,72 +108,70 @@ program
86
108
  process.exit(1);
87
109
  }
88
110
  spinner.succeed(chalk_1.default.green("Authentication Successful!"));
89
- // 2. Save Machine Token Locally
90
- const configDir = path.join(os.homedir(), ".flowstride");
111
+ const configDir = path.join(process.cwd(), ".flowstride", "login");
91
112
  if (!fs.existsSync(configDir)) {
92
113
  fs.mkdirSync(configDir, { recursive: true });
93
114
  }
94
- const credentialsPath = path.join(configDir, "credentials.json");
115
+ const credentialsPath = path.join(configDir, "login.json");
95
116
  fs.writeFileSync(credentialsPath, JSON.stringify({
96
117
  machineToken: data.machineToken,
97
118
  workspaceId: data.workspaceId,
98
119
  deviceName: deviceName,
99
120
  syncedAt: new Date().toISOString(),
100
121
  }, null, 2));
101
- console.log(chalk_1.default.gray(`\nCredentials saved to ${credentialsPath}`));
122
+ console.log(chalk_1.default.gray(`\nCredentials securely scoped to ${credentialsPath}`));
102
123
  console.log(chalk_1.default.bold.green(`\n✅ Machine synchronized with Flowstride Enterprise.`));
103
- // 3. The Auto-Installer Logic
104
- if (data.githubToken) {
124
+ if (data.isProEligible) {
105
125
  console.log(chalk_1.default.yellow(`\n⚙️ System Check: You have access to Enterprise features (AI Healing, Parallel Workers).`));
106
- const localPackageJsonPath = path.join(process.cwd(), "package.json");
107
- const nodeModulesPath = path.join(process.cwd(), "node_modules", "@heroamogs", "pro");
108
- if (fs.existsSync(localPackageJsonPath) &&
109
- !fs.existsSync(nodeModulesPath)) {
110
- const installPro = await (0, prompts_1.confirm)({
111
- message: "The Pro Engine is not installed in this project. Would you like Flowstride to automatically install it now?",
112
- default: true,
113
- });
126
+ const nodeModulesPath = path.join(process.cwd(), "node_modules", "@flowstride", "pro");
127
+ if (!fs.existsSync(nodeModulesPath)) {
128
+ let installPro = true;
129
+ if (!process.env.FLOWSTRIDE_API_KEY && !process.env.CI) {
130
+ installPro = await (0, prompts_1.confirm)({
131
+ message: "The Pro Engine is not installed in this project. Would you like Flowstride to automatically install it now?",
132
+ default: true,
133
+ });
134
+ }
114
135
  if (installPro) {
115
- const installSpinner = (0, ora_1.default)("Configuring secure registry and installing @heroamogs/pro...").start();
116
- const npmrcPath = path.join(process.cwd(), ".npmrc");
117
- let originalNpmrc = "";
136
+ const installSpinner = (0, ora_1.default)("Downloading Enterprise Engine via secure proxy...").start();
137
+ const tarballPath = path.join(process.cwd(), "flowstride-pro.tgz");
118
138
  try {
119
- // A. Backup existing .npmrc if it exists
120
- if (fs.existsSync(npmrcPath)) {
121
- originalNpmrc = fs.readFileSync(npmrcPath, "utf8");
139
+ const tarballResponse = await fetch(`${API_BASE}/api/workspace/download-pro`, {
140
+ method: "GET",
141
+ headers: { Authorization: `Bearer ${data.machineToken}` },
142
+ });
143
+ if (!tarballResponse.ok) {
144
+ throw new Error(`Secure proxy rejected request: ${tarballResponse.statusText}`);
145
+ }
146
+ const buffer = await tarballResponse.arrayBuffer();
147
+ fs.writeFileSync(tarballPath, Buffer.from(buffer));
148
+ installSpinner.text = "Silently extracting Enterprise Engine...";
149
+ installSpinner.color = "cyan";
150
+ if (!fs.existsSync(nodeModulesPath)) {
151
+ fs.mkdirSync(nodeModulesPath, { recursive: true });
122
152
  }
123
- // B. Write the temporary GitHub configuration
124
- const tempNpmrcContent = `
125
- @heroamogs:registry=https://npm.pkg.github.com
126
- //npm.pkg.github.com/:_authToken=${data.githubToken}
127
- `;
128
- fs.writeFileSync(npmrcPath, (originalNpmrc ? originalNpmrc + "\n" : "") + tempNpmrcContent);
129
- installSpinner.stop(); // Stop spinner so we can see the exact npm logs
130
- console.log(chalk_1.default.cyan("Starting npm install..."));
131
- // C. Run the install WITH INHERIT to expose the exact error
132
- (0, child_process_1.execSync)("npm install @heroamogs/pro --save-dev", {
133
- stdio: "inherit",
134
- cwd: process.cwd(),
153
+ await tar.x({
154
+ file: tarballPath,
155
+ cwd: nodeModulesPath,
156
+ strip: 1,
135
157
  });
136
- console.log(chalk_1.default.green("\n✅ Enterprise Engine installed successfully!"));
158
+ installSpinner.succeed(chalk_1.default.green("Enterprise Engine installed successfully!"));
137
159
  }
138
160
  catch (installError) {
139
- console.log(chalk_1.default.red("\n✖ Failed to install the Enterprise Engine automatically."));
140
- console.error(chalk_1.default.gray("You may need to install it manually."));
161
+ installSpinner.stop();
162
+ console.log(chalk_1.default.red("\n✖ Failed to install the Enterprise Engine. Please check your internet connection or verify your project configuration."));
163
+ if (fs.existsSync(nodeModulesPath)) {
164
+ fs.rmSync(nodeModulesPath, { recursive: true, force: true });
165
+ }
141
166
  }
142
167
  finally {
143
- // D. Secure Cleanup: Remove the token from .npmrc
144
- if (originalNpmrc) {
145
- fs.writeFileSync(npmrcPath, originalNpmrc); // Restore original
146
- }
147
- else {
148
- if (fs.existsSync(npmrcPath))
149
- fs.unlinkSync(npmrcPath); // Delete if we created it
168
+ if (fs.existsSync(tarballPath)) {
169
+ fs.unlinkSync(tarballPath);
150
170
  }
151
171
  }
152
172
  }
153
173
  else {
154
- console.log(chalk_1.default.gray(`\nSkipped installation. You can run 'npm install @heroamogs/pro --save-dev' manually later.`));
174
+ console.log(chalk_1.default.gray(`\nSkipped installation.`));
155
175
  }
156
176
  }
157
177
  }
@@ -161,6 +181,51 @@ program
161
181
  process.exit(1);
162
182
  }
163
183
  });
184
+ program
185
+ .command("status")
186
+ .description("Display the current Flowstride CLI authentication and engine tier")
187
+ .action(async () => {
188
+ let token = process.env.FLOWSTRIDE_API_KEY || null;
189
+ const credentialsPath = path.join(process.cwd(), ".flowstride", "login", "login.json");
190
+ if (!token && fs.existsSync(credentialsPath)) {
191
+ try {
192
+ const credentials = JSON.parse(fs.readFileSync(credentialsPath, "utf8"));
193
+ token = credentials.machineToken;
194
+ }
195
+ catch (err) { }
196
+ }
197
+ if (!token) {
198
+ console.log(chalk_1.default.white("⚪ [OS]: (Local Mode / Not Logged In)"));
199
+ return;
200
+ }
201
+ try {
202
+ const response = await fetch(`${API_BASE}/api/auth/verify-cli`, {
203
+ method: "GET",
204
+ headers: { Authorization: `Bearer ${token}` },
205
+ });
206
+ if (response.status === 401) {
207
+ console.log(chalk_1.default.red("🔴 AUTH EXPIRED: Your device session was revoked or expired."));
208
+ if (fs.existsSync(credentialsPath))
209
+ fs.unlinkSync(credentialsPath);
210
+ }
211
+ else if (response.ok) {
212
+ const data = await response.json();
213
+ const tier = data.tier.toUpperCase();
214
+ if (tier === "PERSONAL") {
215
+ console.log(chalk_1.default.yellow(`🟡 [OS]: [${data.workspaceName}] (Tier: PERSONAL)`));
216
+ }
217
+ else {
218
+ console.log(chalk_1.default.green(`🟢 [PRO]: [${data.workspaceName}] (Tier: ${tier})`));
219
+ }
220
+ }
221
+ else {
222
+ console.log(chalk_1.default.yellow("🛜 OFFLINE: Unable to reach Flowstride Cloud. Enterprise Sync paused."));
223
+ }
224
+ }
225
+ catch (err) {
226
+ console.log(chalk_1.default.yellow("🛜 OFFLINE: Unable to reach Flowstride Cloud. Enterprise Sync paused."));
227
+ }
228
+ });
164
229
  program
165
230
  .command("run")
166
231
  .description("Execute all .flow automation journeys in the ./flows directory")
@@ -169,20 +234,49 @@ program
169
234
  .option("--headless <boolean>", "Run browser in headless mode", (val) => val === "true", true)
170
235
  .option("--workers <number>", "Number of parallel workers to spawn (Pro feature)")
171
236
  .action(async (options) => {
172
- const credentialsPath = path.join(os.homedir(), ".flowstride", "credentials.json");
173
- let isPro = false;
174
- if (fs.existsSync(credentialsPath)) {
237
+ let token = process.env.FLOWSTRIDE_API_KEY || null;
238
+ let forceOS = false;
239
+ const credentialsPath = path.join(process.cwd(), ".flowstride", "login", "login.json");
240
+ if (!token && fs.existsSync(credentialsPath)) {
175
241
  try {
176
242
  const credentials = JSON.parse(fs.readFileSync(credentialsPath, "utf8"));
177
- isPro = true;
178
- console.log(`[PRO]: Machine Synced to Enterprise Cloud`);
179
- }
180
- catch (err) {
181
- console.log(`[AUTH ERROR]: Invalid credentials file. Run 'flowstride login' to reconnect.`);
243
+ token = credentials.machineToken;
182
244
  }
245
+ catch (err) { }
246
+ }
247
+ if (!token) {
248
+ console.log(chalk_1.default.white("⚪ [OS]: (Local Mode / Not Logged In)"));
249
+ forceOS = true;
183
250
  }
184
251
  else {
185
- console.log(`[OS]: Local Mode (Run 'flowstride login' to sync to Enterprise Cloud)`);
252
+ try {
253
+ const response = await fetch(`${API_BASE}/api/auth/verify-cli`, {
254
+ method: "GET",
255
+ headers: { Authorization: `Bearer ${token}` },
256
+ });
257
+ if (response.status === 401) {
258
+ console.log(chalk_1.default.red("🔴 AUTH EXPIRED: Your device session was revoked or expired."));
259
+ if (fs.existsSync(credentialsPath))
260
+ fs.unlinkSync(credentialsPath);
261
+ forceOS = true;
262
+ }
263
+ else if (response.ok) {
264
+ const data = await response.json();
265
+ const tier = data.tier.toUpperCase();
266
+ if (tier === "PERSONAL") {
267
+ console.log(chalk_1.default.yellow(`🟡 [OS]: [${data.workspaceName}] (Tier: PERSONAL)`));
268
+ }
269
+ else {
270
+ console.log(chalk_1.default.green(`🟢 [PRO]: [${data.workspaceName}] (Tier: ${tier})`));
271
+ }
272
+ }
273
+ else {
274
+ forceOS = await handleOfflinePrompt();
275
+ }
276
+ }
277
+ catch (err) {
278
+ forceOS = await handleOfflinePrompt();
279
+ }
186
280
  }
187
281
  if (options.headed) {
188
282
  options.headless = false;
@@ -193,8 +287,25 @@ program
193
287
  console.error(`Flowstride requires all UI tests to be inside a './flows' directory.`);
194
288
  process.exit(1);
195
289
  }
290
+ // Inject our DRM state into options before passing to runner
291
+ options.forceOS = forceOS;
196
292
  await (0, run_1.runAction)(strictUiFlowsDir, options);
197
293
  });
294
+ async function handleOfflinePrompt() {
295
+ console.log(chalk_1.default.yellow("🛜 OFFLINE: Unable to reach Flowstride Cloud. Enterprise Sync paused."));
296
+ if (process.env.CI || process.env.FLOWSTRIDE_API_KEY) {
297
+ console.log(chalk_1.default.yellow("⚠️ CI/CD Environment: Automatically falling back to OS Engine."));
298
+ return true; // Force OS
299
+ }
300
+ const runAnyway = await (0, prompts_1.confirm)({
301
+ message: "⚠️ Execute a local OS run anyway? (Pro features are disabled on OS)",
302
+ default: true,
303
+ });
304
+ if (!runAnyway) {
305
+ process.exit(0);
306
+ }
307
+ return true; // User said yes, Force OS
308
+ }
198
309
  program
199
310
  .command("report")
200
311
  .description("Launch the interactive Debug Dashboard")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowstride",
3
- "version": "1.0.3",
3
+ "version": "1.0.7",
4
4
  "description": "An opensource flowfirst test automation framework",
5
5
  "workspaces": [
6
6
  "packages/flowstride_report"
@@ -41,6 +41,7 @@
41
41
  "playwright": "^1.59.1",
42
42
  "playwright-extra": "^4.3.6",
43
43
  "puppeteer-extra-plugin-stealth": "^2.11.2",
44
+ "tar": "^7.5.16",
44
45
  "ts-node": "^10.9.2",
45
46
  "undici": "^7.25.0",
46
47
  "ws": "^8.20.1"
@@ -245,7 +245,7 @@ Error generating stack: `+o.message+`
245
245
  *
246
246
  * This source code is licensed under the ISC license.
247
247
  * See the LICENSE file in the root directory of this source tree.
248
- */const ih=O("X",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]]),sh=""+new URL("flow_logo-CjiL4Lvy.png",import.meta.url).href,uh=({isTestFlowsOpen:e,setTestFlowsOpen:t})=>{const{activeView:n,setActiveView:r}=Se(),l=[{id:"Flows",icon:Qp,label:"Flows"},{id:"Logs",icon:th,label:"Logs"},{id:"Reports",icon:Pc,label:"Reports"},{id:"TestCase",icon:Lc,label:"TestCase"}];return s.jsxs("nav",{style:{width:"75px",backgroundColor:"#0d1117",borderRight:"1px solid #30363d",display:"flex",flexDirection:"column",alignItems:"center",flexShrink:0,height:"100%",overflow:"hidden"},children:[s.jsx("div",{style:{padding:"25px 0",cursor:"pointer"},onClick:()=>r("Flows"),children:s.jsx("img",{src:sh,alt:"Flowstride",style:{width:"42px",height:"auto"}})}),n==="Flows"&&s.jsxs("div",{onClick:()=>t(!e),style:{width:"100%",display:"flex",flexDirection:"column",alignItems:"center",padding:"12px 0",cursor:"pointer",color:e?"#a78bfa":"#8b949e",borderBottom:"1px solid #21262d",marginBottom:"10px"},children:[s.jsx(Yp,{size:22}),s.jsx("span",{style:{fontSize:"10px",marginTop:"4px",fontWeight:"bold"},children:e?"Hide":"Show"})]}),s.jsx("div",{style:{flex:1,width:"100%"},children:l.map(o=>{const i=n===o.id;return s.jsxs("div",{onClick:()=>r(o.id),style:{width:"100%",display:"flex",flexDirection:"column",alignItems:"center",padding:"18px 0",cursor:"pointer",backgroundColor:i?"rgba(124, 58, 237, 0.08)":"transparent",borderLeft:i?"3px solid #7c3aed":"3px solid transparent",color:i?"#a78bfa":"#8b949e",transition:"color 0.2s ease"},children:[s.jsx(o.icon,{size:22,strokeWidth:i?2.5:2}),s.jsx("span",{style:{fontSize:"10px",marginTop:"6px",fontWeight:i?"600":"400"},children:o.label})]},o.id)})}),s.jsxs("div",{style:{padding:"25px 0",width:"100%",borderTop:"1px solid #30363d",display:"flex",flexDirection:"column",alignItems:"center"},children:[s.jsx("div",{style:{width:"38px",height:"38px",borderRadius:"50%",border:"2px solid #30363d",backgroundColor:"#1f2937",display:"flex",alignItems:"center",justifyContent:"center",color:"#a78bfa",fontWeight:"bold",fontSize:"14px"},children:"OS"}),s.jsx("span",{style:{fontSize:"11px",marginTop:"10px",fontWeight:"bold",color:"#ffffff"},children:"Open Source"}),s.jsx("span",{style:{fontSize:"9px",color:"#8b949e",marginTop:"2px",textAlign:"center",padding:"0 4px"},children:"COMMUNITY EDITION"})]})]})},ah=()=>{const{currentUrls:e,pageTitles:t,browserFrames:n,testScenarios:r,activeStepId:l,setActiveStepId:o,isRunning:i,activeTestFile:u,setActiveTestFile:a}=Se();F.useEffect(()=>{if(i){const c=r.find(p=>p.steps.some(g=>g.status==="running"));c&&c.fileName!==u&&a(c.fileName)}},[r,i,u,a]);let d=i?u:null,m=l;if(l&&l.includes(":::")){const c=l.split(":::");d=c[0],m=c[1]}const y=r.filter(c=>!d||c.fileName===d).flatMap(c=>c.steps).find(c=>c.id===m),h=!i&&!!(y&&y.screenshot),x=e[u]||"about:blank",v=t[u]||"New Tab",w=n[u]||null,_=h?y.screenshot:w,f=Array.from(new Set(r.map(c=>c.fileName)));return s.jsxs("div",{style:{height:"100%",display:"flex",flexDirection:"column",backgroundColor:"#1c2128"},children:[s.jsxs("div",{onWheel:c=>{c.currentTarget.scrollLeft+=c.deltaY},style:{display:"flex",alignItems:"center",padding:"8px 8px 0 8px",gap:"4px",overflowX:"auto",overflowY:"hidden",scrollbarWidth:"none",msOverflowStyle:"none"},children:[s.jsx("style",{children:`
248
+ */const ih=O("X",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]]),sh=""+new URL("flow_logo-CjiL4Lvy.png",import.meta.url).href,uh=({isTestFlowsOpen:e,setTestFlowsOpen:t})=>{const{activeView:n,setActiveView:r}=Se(),l=[{id:"Flows",icon:Qp,label:"Flows"},{id:"Logs",icon:th,label:"Logs"},{id:"Reports",icon:Pc,label:"Reports"},{id:"TestCase",icon:Lc,label:"TestCase"}];return s.jsxs("nav",{style:{width:"75px",backgroundColor:"#0d1117",borderRight:"1px solid #30363d",display:"flex",flexDirection:"column",alignItems:"center",flexShrink:0,height:"100%",overflow:"hidden"},children:[s.jsx("div",{style:{padding:"25px 0",cursor:"pointer"},onClick:()=>r("Flows"),children:s.jsx("img",{src:sh,alt:"Flowstride",style:{width:"42px",height:"auto"}})}),n==="Flows"&&s.jsxs("div",{onClick:()=>t(!e),style:{width:"100%",display:"flex",flexDirection:"column",alignItems:"center",padding:"12px 0",cursor:"pointer",color:e?"#a78bfa":"#8b949e",borderBottom:"1px solid #21262d",marginBottom:"10px"},children:[s.jsx(Yp,{size:22}),s.jsx("span",{style:{fontSize:"10px",marginTop:"4px",fontWeight:"bold"},children:e?"Hide":"Show"})]}),s.jsx("div",{style:{flex:1,width:"100%"},children:l.map(o=>{const i=n===o.id;return s.jsxs("div",{onClick:()=>r(o.id),style:{width:"100%",display:"flex",flexDirection:"column",alignItems:"center",padding:"18px 0",cursor:"pointer",backgroundColor:i?"rgba(124, 58, 237, 0.08)":"transparent",borderLeft:i?"3px solid #7c3aed":"3px solid transparent",color:i?"#a78bfa":"#8b949e",transition:"color 0.2s ease"},children:[s.jsx(o.icon,{size:22,strokeWidth:i?2.5:2}),s.jsx("span",{style:{fontSize:"10px",marginTop:"6px",fontWeight:i?"600":"400"},children:o.label})]},o.id)})}),s.jsxs("a",{href:"https://cloud.flowstride.io/register",target:"_blank",rel:"noopener noreferrer",style:{padding:"25px 0",width:"100%",borderTop:"1px solid #30363d",display:"flex",flexDirection:"column",alignItems:"center",textDecoration:"none"},children:[s.jsx("div",{style:{width:"38px",height:"38px",borderRadius:"50%",border:"2px solid #30363d",backgroundColor:"#1f2937",display:"flex",alignItems:"center",justifyContent:"center",color:"#a78bfa",fontWeight:"bold",fontSize:"14px"},children:"OS"}),s.jsx("span",{style:{fontSize:"11px",marginTop:"10px",fontWeight:"bold",color:"#ffffff"},children:"Open Source"}),s.jsx("span",{style:{fontSize:"9px",color:"#8b949e",marginTop:"2px",textAlign:"center",padding:"0 4px"},children:"COMMUNITY EDITION"})]})]})},ah=()=>{const{currentUrls:e,pageTitles:t,browserFrames:n,testScenarios:r,activeStepId:l,setActiveStepId:o,isRunning:i,activeTestFile:u,setActiveTestFile:a}=Se();F.useEffect(()=>{if(i){const c=r.find(p=>p.steps.some(g=>g.status==="running"));c&&c.fileName!==u&&a(c.fileName)}},[r,i,u,a]);let d=i?u:null,m=l;if(l&&l.includes(":::")){const c=l.split(":::");d=c[0],m=c[1]}const y=r.filter(c=>!d||c.fileName===d).flatMap(c=>c.steps).find(c=>c.id===m),h=!i&&!!(y&&y.screenshot),x=e[u]||"about:blank",v=t[u]||"New Tab",w=n[u]||null,_=h?y.screenshot:w,f=Array.from(new Set(r.map(c=>c.fileName)));return s.jsxs("div",{style:{height:"100%",display:"flex",flexDirection:"column",backgroundColor:"#1c2128"},children:[s.jsxs("div",{onWheel:c=>{c.currentTarget.scrollLeft+=c.deltaY},style:{display:"flex",alignItems:"center",padding:"8px 8px 0 8px",gap:"4px",overflowX:"auto",overflowY:"hidden",scrollbarWidth:"none",msOverflowStyle:"none"},children:[s.jsx("style",{children:`
249
249
  div::-webkit-scrollbar { display: none; }
250
250
  `}),f.length>0?f.map((c,p)=>{const g=u===c;return s.jsxs("div",{onClick:()=>a(c),style:{backgroundColor:g?"var(--fs-bg-base)":"rgba(255,255,255,0.05)",padding:"6px 15px",borderRadius:"8px 8px 0 0",display:"flex",alignItems:"center",gap:"8px",border:g?"1px solid var(--fs-border)":"1px solid transparent",borderBottom:"none",minWidth:"130px",maxWidth:"200px",flexShrink:0,cursor:"pointer",opacity:g?1:.6},children:[s.jsx(no,{size:12,color:g?"var(--fs-accent-purple)":"#8b949e",style:{flexShrink:0}}),s.jsx("span",{style:{fontSize:"10px",color:g?"white":"#8b949e",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis",fontWeight:g?"bold":"normal"},children:c})]},p)}):s.jsxs("div",{style:{backgroundColor:"var(--fs-bg-base)",padding:"6px 15px",borderRadius:"8px 8px 0 0",display:"flex",alignItems:"center",gap:"8px",border:"1px solid var(--fs-border)",borderBottom:"none",minWidth:"140px",flexShrink:0},children:[s.jsx(no,{size:12,color:"var(--fs-accent-purple)"}),s.jsx("span",{style:{fontSize:"10px",color:"white"},children:v})]})]}),s.jsxs("div",{style:{backgroundColor:"var(--fs-bg-base)",borderTop:"1px solid var(--fs-border)",padding:"6px 12px",display:"flex",alignItems:"center",gap:"12px"},children:[s.jsxs("div",{style:{display:"flex",gap:"8px",color:"#8b949e"},children:[s.jsx(Bp,{size:14}),s.jsx(or,{size:14}),s.jsx(Mc,{size:14}),s.jsx(Gp,{size:14})]}),s.jsxs("div",{style:{flex:1,backgroundColor:"#05070a",border:"1px solid var(--fs-border)",borderRadius:"15px",padding:"3px 12px",display:"flex",alignItems:"center",gap:"8px"},children:[s.jsx(Xp,{size:10,color:"#22c55e"}),s.jsx("span",{style:{fontSize:"10px",color:"#8b949e",whiteSpace:"nowrap",overflow:"hidden"},children:x})]}),s.jsxs("div",{style:{display:"flex",gap:"10px",color:"#444",borderLeft:"1px solid #333",paddingLeft:"12px"},children:[s.jsx(Zp,{size:14,color:"var(--fs-accent-purple)"}),s.jsx(lh,{size:14}),s.jsx(nh,{size:14}),s.jsx($p,{size:14})]})]}),s.jsxs("div",{style:{flex:1,margin:"10px",backgroundColor:"white",borderRadius:"4px",boxShadow:"0 4px 12px rgba(0,0,0,0.5)",display:"flex",alignItems:"center",justifyContent:"center",overflow:"hidden",position:"relative"},children:[h&&s.jsxs("div",{style:{position:"absolute",top:"12px",left:"12px",right:"12px",backgroundColor:"rgba(15, 23, 42, 0.95)",border:"1px solid rgba(167, 139, 250, 0.3)",padding:"10px 16px",borderRadius:"6px",display:"flex",justifyContent:"space-between",alignItems:"center",zIndex:30,boxShadow:"0 4px 20px rgba(0,0,0,0.4)",backdropFilter:"blur(4px)"},children:[s.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"10px"},children:[s.jsx("div",{style:{backgroundColor:"rgba(167, 139, 250, 0.1)",padding:"6px",borderRadius:"4px"},children:s.jsx(Hp,{size:14,color:"#a78bfa"})}),s.jsxs("div",{children:[s.jsx("div",{style:{fontSize:"11px",fontWeight:"bold",color:"#a78bfa",textTransform:"uppercase",letterSpacing:"0.05em"},children:"Viewing Historical State"}),s.jsx("div",{style:{fontSize:"12px",color:"#e2e8f0",fontFamily:"monospace",marginTop:"2px"},children:y==null?void 0:y.command})]})]}),s.jsx("button",{onClick:()=>o(null),style:{backgroundColor:"#7c3aed",color:"white",border:"none",padding:"6px 14px",borderRadius:"4px",fontSize:"11px",fontWeight:"bold",cursor:"pointer",transition:"background 0.2s"},onMouseEnter:c=>c.currentTarget.style.backgroundColor="#6d28d9",onMouseLeave:c=>c.currentTarget.style.backgroundColor="#7c3aed",children:"Return to Live"})]}),_?s.jsx("img",{src:`data:image/png;base64,${_}`,style:{width:"100%",height:"100%",objectFit:"contain",opacity:h?.95:1},alt:"Browser frame"}):s.jsxs("div",{style:{textAlign:"center",color:"#ccc"},children:[s.jsx(no,{size:40,style:{marginBottom:"8px",opacity:.1}}),s.jsx("div",{style:{fontSize:"11px",fontWeight:500,opacity:.3},children:"WAITING FOR RENDER..."})]})]})]})},ch=()=>{const{networkLogs:e,activeNetworkTab:t,setNetworkTab:n,setSelectedLogId:r,setActiveView:l}=Se(),o=["REST","GraphQL","Network","WS"],i=e.filter(a=>t==="GraphQL"?a.url.includes("graphql"):t==="REST"?!a.url.includes("graphql")&&!a.url.includes("ws"):!0),u=a=>{r(a),l("Logs")};return s.jsxs("div",{style:{height:"100%",display:"flex",flexDirection:"column",backgroundColor:"#0d1117"},children:[s.jsx("div",{style:{display:"flex",borderBottom:"1px solid var(--fs-border)",backgroundColor:"#05070a"},children:o.map(a=>s.jsx("button",{onClick:()=>n(a),style:{padding:"10px 16px",fontSize:"11px",fontWeight:"600",background:"none",border:"none",color:t===a?"#a78bfa":"#444",borderBottom:t===a?"2px solid #7c3aed":"2px solid transparent",cursor:"pointer",transition:"all 0.2s"},children:a},a))}),s.jsxs("div",{style:{padding:"8px 12px",display:"flex",gap:"10px",alignItems:"center",borderBottom:"1px solid #161b22"},children:[s.jsxs("div",{style:{flex:1,position:"relative"},children:[s.jsx(Gi,{size:12,style:{position:"absolute",left:"8px",top:"50%",transform:"translateY(-50%)",color:"#444"}}),s.jsx("input",{type:"text",placeholder:`Filter ${t}...`,style:{width:"100%",background:"#05070a",border:"1px solid #30363d",borderRadius:"4px",padding:"4px 8px 4px 28px",fontSize:"10px",color:"white"}})]}),s.jsx(bp,{size:14,color:"#444"})]}),s.jsx("div",{style:{flex:1,overflowY:"auto"},children:i.length===0?s.jsxs("div",{style:{padding:"40px",textAlign:"center",color:"#444",fontSize:"11px"},children:["No ",t," traffic detected"]}):i.map((a,d)=>s.jsx("div",{onClick:()=>u(a.id),style:{borderBottom:"1px solid #161b22",cursor:"pointer",transition:"background 0.2s"},onMouseEnter:m=>m.currentTarget.style.backgroundColor="#161b22",onMouseLeave:m=>m.currentTarget.style.backgroundColor="transparent",children:s.jsxs("div",{style:{display:"flex",alignItems:"center",padding:"8px 12px",fontSize:"11px",gap:"10px"},children:[s.jsx("span",{style:{color:a.status<400?"#34d399":"#f87171",fontWeight:"bold",width:"25px"},children:a.status}),s.jsx("span",{style:{fontWeight:"bold",width:"35px",color:"#8b949e"},children:a.method}),s.jsx("span",{style:{flex:1,color:"white",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:a.name||a.url}),s.jsx("span",{style:{color:"#444",fontSize:"10px"},children:a.time})]})},d))})]})},dh=()=>{const{logs:e}=Se(),t=F.useRef(null);return F.useEffect(()=>{t.current&&(t.current.scrollTop=t.current.scrollHeight)},[e]),s.jsxs("div",{ref:t,style:{flex:1,backgroundColor:"#0d1117",padding:"10px 15px",fontFamily:"monospace",fontSize:"12px",overflowY:"auto",color:"#d1d5db",lineHeight:"1.5"},children:[e.length===0&&s.jsx("div",{style:{color:"#4b5563",fontStyle:"italic"},children:"System ready. Awaiting execution..."}),e.map((n,r)=>{const l=(n.type||n.status||"INFO").toUpperCase();let o="#34d399";return(n.status==="error"||n.type==="ERROR")&&(o="#f87171"),n.status==="warning"&&(o="#fbbf24"),n.type==="ACTION"&&(o="#60a5fa"),s.jsxs("div",{style:{marginBottom:"4px",display:"flex",gap:"8px"},children:[s.jsxs("span",{style:{color:"#6e7681",flexShrink:0},children:["[",n.timestamp||"--:--:--","]"]}),s.jsx("span",{style:{color:o,fontWeight:"bold",flexShrink:0,width:"55px"},children:l}),s.jsx("span",{style:{color:"#c9d1d9",wordBreak:"break-all"},children:n.summary})]},n.id||r)})]})},fh=({isRightPanelOpen:e})=>{const{logs:t,pageTitles:n,activeTestFile:r}=Se(),l=t[t.length-1],o=n[r]||"New Tab";return s.jsxs("div",{style:{position:"absolute",top:0,left:0,right:0,bottom:0,display:"flex",flexDirection:"column",backgroundColor:"var(--fs-bg-base)",overflow:"hidden"},children:[s.jsxs("div",{style:{height:"35px",backgroundColor:"#161b22",borderBottom:"1px solid var(--fs-border)",display:"flex",alignItems:"center",padding:"0 15px",fontSize:"11px",color:"#8b949e",flexShrink:0},children:[s.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"8px",flex:1},children:[s.jsx("span",{style:{color:"#a78bfa",fontWeight:"bold"},children:"LOG:"}),s.jsx("span",{style:{color:"#c9d1d9",fontFamily:"monospace",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:(l==null?void 0:l.summary)||"System ready. Awaiting connection..."})]}),s.jsx("div",{style:{opacity:.5,fontStyle:"italic"},children:o})]}),s.jsxs("div",{style:{flex:1,display:"flex",overflow:"hidden"},children:[s.jsx("div",{style:{flex:1,borderRight:"1px solid var(--fs-border)",height:"100%",overflow:"hidden"},children:s.jsx(ah,{})}),s.jsxs("div",{style:{width:e?"400px":"0px",transition:"width 0.2s ease",display:"flex",flexDirection:"column",height:"100%",backgroundColor:"#0d1117",overflow:"hidden",flexShrink:0},children:[s.jsx("div",{style:{flex:1,overflow:"hidden",display:"flex",flexDirection:"column"},children:s.jsx(ch,{})}),s.jsxs("div",{style:{flex:1,borderTop:"1px solid var(--fs-border)",backgroundColor:"#0d1117",overflow:"hidden",display:"flex",flexDirection:"column"},children:[s.jsx("div",{style:{padding:"8px 15px",fontSize:"11px",fontWeight:"bold",color:"#8b949e",borderBottom:"1px solid var(--fs-border)",flexShrink:0},children:"LIVE INFO"}),s.jsx("div",{style:{flex:1,overflow:"hidden",position:"relative"},children:s.jsx(dh,{})})]})]})]})]})},ph=()=>{var h,x;const[e,t]=F.useState(null),{testScenarios:n,logs:r}=Se(),l=n.reduce((v,w)=>v+w.steps.length,0),o=n.reduce((v,w)=>v+w.steps.filter(_=>["pass","passed","success"].includes(_.status)).length,0),i=n.reduce((v,w)=>v+w.steps.filter(_=>_.status==="failed").length,0),u=n.reduce((v,w)=>{const _=w.fileName||"General";return v[_]||(v[_]=[]),v[_].push(w),v},{});let a="Verified.",d=null,m=null,y=null;if(e){const v=e.status==="failed",w=["pass","passed","success"].includes(e.status);if(m=e.screenshot,v){const f=`${e.type} ${e.description}`,c=r.slice().reverse().find(p=>p.type==="ERROR"&&p.fileName===e.fileName&&p.stepContext===f);a=c?c.summary:"Validation Failed.",!m&&((h=c==null?void 0:c.details)!=null&&h.screenshot)&&(m=c.details.screenshot),(x=c==null?void 0:c.details)!=null&&x.boundingBox&&(y=c.details.boundingBox)}else e.status==="running"?a="Executing...":w&&(a="Action verified successfully.");const _=r.slice().reverse().find(f=>(f.type==="REST"||f.type==="GRAPHQL")&&f.fileName===e.fileName);d=_?_.details:null}return s.jsxs("div",{style:{height:"100%",width:"100%",backgroundColor:"#f8fafc",overflowY:"auto",position:"relative",color:"#0f172a"},children:[s.jsxs("div",{style:{maxWidth:"1200px",margin:"0 auto",padding:"40px 20px",marginRight:e?"450px":"auto",transition:"margin-right 0.3s"},children:[s.jsxs("header",{style:{display:"flex",justifyContent:"space-between",alignItems:"flex-end",borderBottom:"1px solid #e2e8f0",paddingBottom:"24px",marginBottom:"40px"},children:[s.jsxs("div",{children:[s.jsx("h1",{style:{fontSize:"30px",fontWeight:"800",margin:0,color:"#1e293b"},children:"Flowstride Reports"}),s.jsx("p",{style:{color:"#64748b",margin:"4px 0 0 0",fontWeight:"500"},children:"Multi-Flow Execution Analysis"})]}),s.jsxs("div",{style:{display:"flex",gap:"16px"},children:[s.jsx(ro,{label:"PASSED",value:o,color:"#10b981",icon:s.jsx(Tc,{size:20})}),s.jsx(ro,{label:"FAILED",value:i,color:"#ef4444",icon:s.jsx(_c,{size:20})}),s.jsx(ro,{label:"TOTAL",value:l,color:"#3b82f6",icon:s.jsx(Pc,{size:20})})]})]}),Object.keys(u).map(v=>s.jsxs("div",{style:{marginBottom:"60px"},children:[s.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"20px",marginBottom:"24px"},children:[s.jsx("div",{style:{height:"2px",flex:1,backgroundColor:"#7c3aed",opacity:.15}}),s.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"10px",color:"#7c3aed",fontWeight:"900",fontSize:"11px",textTransform:"uppercase",letterSpacing:"0.1em",backgroundColor:"rgba(124, 58, 237, 0.05)",padding:"6px 16px",borderRadius:"20px",border:"1px solid rgba(124, 58, 237, 0.1)"},children:[s.jsx(Nc,{size:14})," ",v]}),s.jsx("div",{style:{height:"2px",flex:1,backgroundColor:"#7c3aed",opacity:.15}})]}),s.jsx("div",{style:{backgroundColor:"white",borderRadius:"12px",border:"1px solid #e2e8f0",boxShadow:"0 1px 3px rgba(0,0,0,0.05)",overflow:"hidden"},children:s.jsxs("table",{style:{width:"100%",borderCollapse:"collapse",textAlign:"left"},children:[s.jsx("thead",{style:{backgroundColor:"#f1f5f9",borderBottom:"1px solid #e2e8f0"},children:s.jsx("tr",{children:["S/N","Scenario","Action","Status",""].map((w,_)=>s.jsx("th",{style:{padding:"16px 24px",fontSize:"10px",fontWeight:"800",color:"#64748b",textTransform:"uppercase"},children:w},_))})}),s.jsx("tbody",{children:u[v].flatMap(w=>w.steps.map((_,f)=>s.jsxs("tr",{onClick:()=>t({..._,scenarioName:w.name,fileName:v}),style:{cursor:"pointer",borderBottom:"1px solid #f1f5f9",backgroundColor:(e==null?void 0:e.id)===_.id?"#eff6ff":"transparent"},children:[s.jsx("td",{style:{padding:"18px 24px",fontSize:"12px",color:"#94a3b8",fontFamily:"monospace",fontWeight:"bold"},children:f+1}),s.jsx("td",{style:{padding:"18px 24px",fontWeight:"600",color:"#334155"},children:w.name}),s.jsxs("td",{style:{padding:"18px 24px",fontSize:"14px",color:"#475569"},children:[s.jsx("b",{style:{color:"#7c3aed",marginRight:"8px"},children:_.type})," ",_.description]}),s.jsx("td",{style:{padding:"18px 24px"},children:s.jsx(yh,{status:_.status})}),s.jsx("td",{style:{padding:"18px 24px",textAlign:"right"},children:s.jsx(or,{size:16,color:(e==null?void 0:e.id)===_.id?"#3b82f6":"#cbd5e1",style:{transform:(e==null?void 0:e.id)===_.id?"rotate(90deg)":"none",transition:"0.2s"}})})]},_.id)))})]})})]},v))]}),s.jsx("aside",{style:{position:"fixed",top:0,right:0,height:"100%",width:"450px",backgroundColor:"white",boxShadow:"-10px 0 40px rgba(0,0,0,0.1)",borderLeft:"1px solid #e2e8f0",zIndex:100,transform:e?"translateX(0)":"translateX(100%)",transition:"transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)",padding:"30px",overflowY:"auto"},children:e&&s.jsxs(s.Fragment,{children:[s.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"32px"},children:[s.jsxs("h2",{style:{fontSize:"18px",fontWeight:"700",display:"flex",alignItems:"center",gap:"10px"},children:[s.jsx(oh,{size:18,color:"#3b82f6"})," Step Diagnostics"]}),s.jsx(ih,{onClick:()=>t(null),style:{cursor:"pointer",color:"#94a3b8"}})]}),m&&s.jsx(Rn,{label:`Visual State (${(e.status||"").toUpperCase()})`,children:s.jsx(hh,{screenshot:m,boundingBox:y})}),s.jsx(Rn,{label:"Context",children:s.jsx("div",{style:{fontWeight:"bold",color:"#1e293b"},children:e.scenarioName})}),s.jsx(Rn,{label:"Requirement Proof",children:s.jsxs("div",{style:{padding:"16px",backgroundColor:"#f8fafc",borderRadius:"8px",fontSize:"14px",fontStyle:"italic",color:"#475569",border:"1px solid #f1f5f9"},children:['"',e.type," ",e.description,'"']})}),s.jsx(Rn,{label:"Runtime Outcome",children:s.jsx("div",{style:{padding:"16px",borderRadius:"8px",fontSize:"14px",fontWeight:"500",backgroundColor:["pass","success","passed"].includes(e.status)?"#ecfdf5":"#fef2f2",color:["pass","success","passed"].includes(e.status)?"#065f46":"#991b1b",border:`1px solid ${["pass","success","passed"].includes(e.status)?"#d1fae5":"#fee2e2"}`},children:a})}),d&&s.jsx(Rn,{label:"Network Activity",children:s.jsx("div",{style:{backgroundColor:"#0f172a",padding:"16px",borderRadius:"8px",color:"#7dd3fc",fontFamily:"monospace",fontSize:"11px",overflowX:"auto"},children:s.jsx("pre",{children:JSON.stringify({status:d.responseStatus,url:d.url},null,2)})})})]})})]})},hh=({screenshot:e,boundingBox:t})=>{const[n,r]=F.useState({w:0,h:0}),[l,o]=F.useState(!1),i=()=>{const u=window.open();u&&(u.document.write(`
251
251
  <html>
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/png" href="./favicon.png" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Flowstride</title>
8
- <script type="module" crossorigin src="./assets/index-Brl3nrIZ.js"></script>
8
+ <script type="module" crossorigin src="./assets/index-BrbAbAha.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="./assets/index-DXTn_ThB.css">
10
10
  </head>
11
11
  <body>