@veolab/discoverylab 1.4.1 → 1.4.2
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.
|
@@ -8707,11 +8707,11 @@ var require_mime_types = __commonJS({
|
|
|
8707
8707
|
import { Hono } from "hono";
|
|
8708
8708
|
import { cors } from "hono/cors";
|
|
8709
8709
|
import { serve } from "@hono/node-server";
|
|
8710
|
-
import { readFileSync as readFileSync2, existsSync as existsSync5, statSync as
|
|
8710
|
+
import { readFileSync as readFileSync2, existsSync as existsSync5, statSync as statSync3, readdirSync as readdirSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, copyFileSync, cpSync, mkdtempSync, rmSync as rmSync2 } from "fs";
|
|
8711
8711
|
import { exec, execSync as execSync2, spawn } from "child_process";
|
|
8712
8712
|
import { createConnection } from "net";
|
|
8713
8713
|
import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
|
|
8714
|
-
import { join as
|
|
8714
|
+
import { join as join6, dirname as dirname2, basename as basename3 } from "path";
|
|
8715
8715
|
import { fileURLToPath } from "url";
|
|
8716
8716
|
import { eq, desc, and, inArray } from "drizzle-orm";
|
|
8717
8717
|
|
|
@@ -9318,7 +9318,7 @@ function attachPlaywrightNetworkCapture(page, options) {
|
|
|
9318
9318
|
|
|
9319
9319
|
// src/core/export/pipeline.ts
|
|
9320
9320
|
var import_mime_types = __toESM(require_mime_types(), 1);
|
|
9321
|
-
import { existsSync as existsSync3 } from "fs";
|
|
9321
|
+
import { existsSync as existsSync3, statSync as statSync2, readdirSync as readdirSync2 } from "fs";
|
|
9322
9322
|
import { join as join3, basename as basename2, extname as extname2 } from "path";
|
|
9323
9323
|
var adapters = /* @__PURE__ */ new Map();
|
|
9324
9324
|
function registerAdapter(adapter) {
|
|
@@ -9380,16 +9380,15 @@ async function prepareAsset(asset, ctx) {
|
|
|
9380
9380
|
}
|
|
9381
9381
|
function resolveVideoFile(videoPath) {
|
|
9382
9382
|
if (!existsSync3(videoPath)) return null;
|
|
9383
|
-
const
|
|
9384
|
-
const stat = statSync3(videoPath);
|
|
9383
|
+
const stat = statSync2(videoPath);
|
|
9385
9384
|
if (stat.isFile()) return videoPath;
|
|
9386
9385
|
const videoExts = [".mp4", ".mov", ".webm", ".avi", ".mkv"];
|
|
9387
9386
|
const searchDirs = [join3(videoPath, "video"), videoPath];
|
|
9388
9387
|
for (const dir of searchDirs) {
|
|
9389
9388
|
if (!existsSync3(dir)) continue;
|
|
9390
|
-
const dirStat =
|
|
9389
|
+
const dirStat = statSync2(dir);
|
|
9391
9390
|
if (!dirStat.isDirectory()) continue;
|
|
9392
|
-
const files =
|
|
9391
|
+
const files = readdirSync2(dir);
|
|
9393
9392
|
for (const file of files) {
|
|
9394
9393
|
if (videoExts.some((ext) => file.toLowerCase().endsWith(ext))) {
|
|
9395
9394
|
return join3(dir, file);
|
|
@@ -9493,6 +9492,8 @@ async function executeBatchExport(manifest, dataProvider, onProgress) {
|
|
|
9493
9492
|
}
|
|
9494
9493
|
|
|
9495
9494
|
// src/core/export/adapters/notion.ts
|
|
9495
|
+
import { readdirSync as readdirSync3 } from "fs";
|
|
9496
|
+
import { join as join4 } from "path";
|
|
9496
9497
|
var SUPPORTED_TYPES = [
|
|
9497
9498
|
"frames",
|
|
9498
9499
|
// Images upload directly
|
|
@@ -9561,11 +9562,9 @@ var notionAdapter = {
|
|
|
9561
9562
|
if (!asset.filePath) continue;
|
|
9562
9563
|
switch (asset.type) {
|
|
9563
9564
|
case "frames": {
|
|
9564
|
-
const { readdirSync: readdirSync3 } = __require("fs");
|
|
9565
|
-
const { join: join6 } = __require("path");
|
|
9566
9565
|
try {
|
|
9567
9566
|
const files = readdirSync3(asset.filePath);
|
|
9568
|
-
const imageFiles = files.filter((f) => /\.(png|jpg|jpeg|gif|webp)$/i.test(f)).sort().map((f) =>
|
|
9567
|
+
const imageFiles = files.filter((f) => /\.(png|jpg|jpeg|gif|webp)$/i.test(f)).sort().map((f) => join4(asset.filePath, f));
|
|
9569
9568
|
const maxFrames = asset.metadata?.maxFrames || 20;
|
|
9570
9569
|
screenshots.push(...imageFiles.slice(0, maxFrames));
|
|
9571
9570
|
} catch {
|
|
@@ -9630,7 +9629,7 @@ var notionAdapter = {
|
|
|
9630
9629
|
// src/core/testing/playwrightRecorder.ts
|
|
9631
9630
|
import { chromium } from "playwright";
|
|
9632
9631
|
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
9633
|
-
import { join as
|
|
9632
|
+
import { join as join5 } from "path";
|
|
9634
9633
|
import { homedir as homedir2 } from "os";
|
|
9635
9634
|
import { EventEmitter } from "events";
|
|
9636
9635
|
var PlaywrightRecorder = class _PlaywrightRecorder extends EventEmitter {
|
|
@@ -9673,8 +9672,8 @@ var PlaywrightRecorder = class _PlaywrightRecorder extends EventEmitter {
|
|
|
9673
9672
|
throw new Error("Recording already in progress");
|
|
9674
9673
|
}
|
|
9675
9674
|
const sessionId = `rec_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
9676
|
-
const baseDir =
|
|
9677
|
-
const screenshotsDir =
|
|
9675
|
+
const baseDir = join5(homedir2(), ".discoverylab", "recordings", sessionId);
|
|
9676
|
+
const screenshotsDir = join5(baseDir, "screenshots");
|
|
9678
9677
|
mkdirSync3(screenshotsDir, { recursive: true });
|
|
9679
9678
|
this.session = {
|
|
9680
9679
|
id: sessionId,
|
|
@@ -9706,7 +9705,7 @@ var PlaywrightRecorder = class _PlaywrightRecorder extends EventEmitter {
|
|
|
9706
9705
|
this.session.viewportMode = viewportMode;
|
|
9707
9706
|
const contextOptions = {
|
|
9708
9707
|
recordVideo: {
|
|
9709
|
-
dir:
|
|
9708
|
+
dir: join5(baseDir, "video"),
|
|
9710
9709
|
size: captureResolution
|
|
9711
9710
|
}
|
|
9712
9711
|
};
|
|
@@ -9994,7 +9993,7 @@ var PlaywrightRecorder = class _PlaywrightRecorder extends EventEmitter {
|
|
|
9994
9993
|
this.actionCounter++;
|
|
9995
9994
|
const actionId = `action_${this.actionCounter.toString().padStart(3, "0")}`;
|
|
9996
9995
|
const screenshotName = `${actionId}_${actionData.type}.png`;
|
|
9997
|
-
const screenshotPath =
|
|
9996
|
+
const screenshotPath = join5(this.session.screenshotsDir, screenshotName);
|
|
9998
9997
|
try {
|
|
9999
9998
|
await this.page.screenshot({
|
|
10000
9999
|
path: screenshotPath,
|
|
@@ -10069,10 +10068,10 @@ var PlaywrightRecorder = class _PlaywrightRecorder extends EventEmitter {
|
|
|
10069
10068
|
this.session.status = "stopped";
|
|
10070
10069
|
this.session.endedAt = Date.now();
|
|
10071
10070
|
const specCode = this.generateSpecCode();
|
|
10072
|
-
const specPath =
|
|
10071
|
+
const specPath = join5(this.session.screenshotsDir, "..", "test.spec.ts");
|
|
10073
10072
|
writeFileSync3(specPath, specCode);
|
|
10074
10073
|
this.session.specPath = specPath;
|
|
10075
|
-
const metadataPath =
|
|
10074
|
+
const metadataPath = join5(this.session.screenshotsDir, "..", "session.json");
|
|
10076
10075
|
writeFileSync3(metadataPath, JSON.stringify(this.session, null, 2));
|
|
10077
10076
|
try {
|
|
10078
10077
|
await this.context?.close();
|
|
@@ -10159,7 +10158,7 @@ var PlaywrightRecorder = class _PlaywrightRecorder extends EventEmitter {
|
|
|
10159
10158
|
if (!this.page || !this.session) return null;
|
|
10160
10159
|
this.actionCounter++;
|
|
10161
10160
|
const screenshotName = name || `manual_${this.actionCounter.toString().padStart(3, "0")}.png`;
|
|
10162
|
-
const screenshotPath =
|
|
10161
|
+
const screenshotPath = join5(this.session.screenshotsDir, screenshotName);
|
|
10163
10162
|
await this.page.screenshot({ path: screenshotPath });
|
|
10164
10163
|
this.emit("screenshot", screenshotPath, `manual_${this.actionCounter}`);
|
|
10165
10164
|
return screenshotPath;
|
|
@@ -10283,10 +10282,10 @@ var TEST_VAR_KEY_REGEX = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
|
10283
10282
|
var SCRIPT_PLACEHOLDER_REGEX = /\$\{([A-Z][A-Z0-9_]*)\}/g;
|
|
10284
10283
|
var mobileReplayRuns = /* @__PURE__ */ new Map();
|
|
10285
10284
|
function getMobileRecordingDir(recordingId) {
|
|
10286
|
-
return
|
|
10285
|
+
return join6(PROJECTS_DIR, "maestro-recordings", recordingId);
|
|
10287
10286
|
}
|
|
10288
10287
|
function getMobileRecordingSessionPath(recordingId) {
|
|
10289
|
-
return
|
|
10288
|
+
return join6(getMobileRecordingDir(recordingId), "session.json");
|
|
10290
10289
|
}
|
|
10291
10290
|
async function readMobileRecordingSessionData(recordingId) {
|
|
10292
10291
|
const sessionPath = getMobileRecordingSessionPath(recordingId);
|
|
@@ -10314,9 +10313,9 @@ function getFirstRecordingScreenshotPath(session) {
|
|
|
10314
10313
|
if (typeof session?.screenshotsDir !== "string" || !existsSync5(session.screenshotsDir)) {
|
|
10315
10314
|
return void 0;
|
|
10316
10315
|
}
|
|
10317
|
-
const files =
|
|
10316
|
+
const files = readdirSync4(session.screenshotsDir).filter((file) => file.toLowerCase().endsWith(".png")).sort();
|
|
10318
10317
|
if (files.length === 0) return void 0;
|
|
10319
|
-
return
|
|
10318
|
+
return join6(session.screenshotsDir, files[0]);
|
|
10320
10319
|
}
|
|
10321
10320
|
function buildDefaultESVPNetworkProfile(session) {
|
|
10322
10321
|
return buildAppLabNetworkProfile(
|
|
@@ -11382,15 +11381,15 @@ app.get("/setup", async (c) => {
|
|
|
11382
11381
|
app.get("/", (c) => {
|
|
11383
11382
|
const cwd = process.cwd();
|
|
11384
11383
|
const possiblePaths = [
|
|
11385
|
-
|
|
11384
|
+
join6(__dirname, "index.html"),
|
|
11386
11385
|
// Production: bundled (dist/index.html alongside server)
|
|
11387
|
-
|
|
11386
|
+
join6(__dirname, "..", "web", "index.html"),
|
|
11388
11387
|
// Parent/web
|
|
11389
|
-
|
|
11388
|
+
join6(__dirname, "..", "..", "src", "web", "index.html"),
|
|
11390
11389
|
// Two levels up/src/web
|
|
11391
|
-
|
|
11390
|
+
join6(cwd, "src", "web", "index.html"),
|
|
11392
11391
|
// Development: running from project root
|
|
11393
|
-
|
|
11392
|
+
join6(cwd, "dist", "index.html")
|
|
11394
11393
|
// Production: running from project root
|
|
11395
11394
|
];
|
|
11396
11395
|
for (const path of possiblePaths) {
|
|
@@ -11439,14 +11438,14 @@ function resolveVideoPath(videoPath) {
|
|
|
11439
11438
|
if (!videoPath) return null;
|
|
11440
11439
|
try {
|
|
11441
11440
|
if (!existsSync5(videoPath)) return videoPath;
|
|
11442
|
-
if (!
|
|
11443
|
-
const videoDir =
|
|
11444
|
-
if (existsSync5(videoDir) &&
|
|
11445
|
-
const videoFiles =
|
|
11446
|
-
if (videoFiles.length > 0) return
|
|
11447
|
-
}
|
|
11448
|
-
const directFiles =
|
|
11449
|
-
if (directFiles.length > 0) return
|
|
11441
|
+
if (!statSync3(videoPath).isDirectory()) return videoPath;
|
|
11442
|
+
const videoDir = join6(videoPath, "video");
|
|
11443
|
+
if (existsSync5(videoDir) && statSync3(videoDir).isDirectory()) {
|
|
11444
|
+
const videoFiles = readdirSync4(videoDir).filter((f) => /\.(mp4|mov|webm)$/i.test(f));
|
|
11445
|
+
if (videoFiles.length > 0) return join6(videoDir, videoFiles[0]);
|
|
11446
|
+
}
|
|
11447
|
+
const directFiles = readdirSync4(videoPath).filter((f) => /\.(mp4|mov|webm)$/i.test(f));
|
|
11448
|
+
if (directFiles.length > 0) return join6(videoPath, directFiles[0]);
|
|
11450
11449
|
return videoPath;
|
|
11451
11450
|
} catch {
|
|
11452
11451
|
return videoPath;
|
|
@@ -11455,7 +11454,7 @@ function resolveVideoPath(videoPath) {
|
|
|
11455
11454
|
function resolveRecordingBaseDir(videoPath) {
|
|
11456
11455
|
if (!videoPath || !existsSync5(videoPath)) return null;
|
|
11457
11456
|
try {
|
|
11458
|
-
if (
|
|
11457
|
+
if (statSync3(videoPath).isDirectory()) {
|
|
11459
11458
|
return videoPath;
|
|
11460
11459
|
}
|
|
11461
11460
|
const parentDir = dirname2(videoPath);
|
|
@@ -11502,7 +11501,7 @@ async function maybeRepairMobileProjectThumbnail(project) {
|
|
|
11502
11501
|
if (!recordingBaseDir || !existsSync5(recordingBaseDir)) {
|
|
11503
11502
|
return project;
|
|
11504
11503
|
}
|
|
11505
|
-
const sessionPath =
|
|
11504
|
+
const sessionPath = join6(recordingBaseDir, "session.json");
|
|
11506
11505
|
if (!existsSync5(sessionPath)) {
|
|
11507
11506
|
return project;
|
|
11508
11507
|
}
|
|
@@ -11610,7 +11609,7 @@ app.get("/api/projects/:id", async (c) => {
|
|
|
11610
11609
|
let networkCapture = null;
|
|
11611
11610
|
let esvp = null;
|
|
11612
11611
|
if (recordingBaseDir && existsSync6(recordingBaseDir)) {
|
|
11613
|
-
const sessionPath =
|
|
11612
|
+
const sessionPath = join6(recordingBaseDir, "session.json");
|
|
11614
11613
|
if (existsSync6(sessionPath)) {
|
|
11615
11614
|
try {
|
|
11616
11615
|
const sessionData = JSON.parse(readFileSync3(sessionPath, "utf8"));
|
|
@@ -11675,13 +11674,13 @@ app.post("/api/projects", async (c) => {
|
|
|
11675
11674
|
app.post("/api/projects/sync-orphans", async (c) => {
|
|
11676
11675
|
try {
|
|
11677
11676
|
const db = getDatabase();
|
|
11678
|
-
const { readdirSync:
|
|
11677
|
+
const { readdirSync: readdirSync5, statSync: statSync4, existsSync: existsSync6 } = await import("fs");
|
|
11679
11678
|
if (!existsSync6(PROJECTS_DIR)) {
|
|
11680
11679
|
return c.json({ synced: 0, projects: [], message: "Projects directory does not exist" });
|
|
11681
11680
|
}
|
|
11682
|
-
const dirs =
|
|
11683
|
-
const p =
|
|
11684
|
-
return
|
|
11681
|
+
const dirs = readdirSync5(PROJECTS_DIR).filter((d) => {
|
|
11682
|
+
const p = join6(PROJECTS_DIR, d);
|
|
11683
|
+
return statSync4(p).isDirectory() && !d.startsWith(".");
|
|
11685
11684
|
});
|
|
11686
11685
|
const existing = await db.select({ id: projects.id }).from(projects);
|
|
11687
11686
|
const existingIds = new Set(existing.map((p) => p.id));
|
|
@@ -11689,7 +11688,7 @@ app.post("/api/projects/sync-orphans", async (c) => {
|
|
|
11689
11688
|
const orphans = dirs.filter((d) => !existingIds.has(d) && !specialDirs.has(d));
|
|
11690
11689
|
const created = [];
|
|
11691
11690
|
for (const id of orphans) {
|
|
11692
|
-
const dirPath =
|
|
11691
|
+
const dirPath = join6(PROJECTS_DIR, id);
|
|
11693
11692
|
const platform = id.includes("_web_") ? "web" : "mobile";
|
|
11694
11693
|
const now = /* @__PURE__ */ new Date();
|
|
11695
11694
|
await db.insert(projects).values({
|
|
@@ -12269,15 +12268,15 @@ app.delete("/api/projects/:id", async (c) => {
|
|
|
12269
12268
|
const db = getDatabase();
|
|
12270
12269
|
const id = c.req.param("id");
|
|
12271
12270
|
const { rmSync: rmSync3 } = await import("fs");
|
|
12272
|
-
const { join:
|
|
12271
|
+
const { join: join7 } = await import("path");
|
|
12273
12272
|
await db.delete(frames).where(eq(frames.projectId, id));
|
|
12274
12273
|
await db.delete(projects).where(eq(projects.id, id));
|
|
12275
12274
|
await deleteTestVariablesForOwner("project", id);
|
|
12276
12275
|
await deleteTestVariablesForOwner("mobile-recording", id);
|
|
12277
12276
|
await deleteTestVariablesForOwner("web-recording", id);
|
|
12278
|
-
const mobileRecordingDir =
|
|
12279
|
-
const webRecordingDir =
|
|
12280
|
-
const projectDir =
|
|
12277
|
+
const mobileRecordingDir = join7(PROJECTS_DIR, "maestro-recordings", id);
|
|
12278
|
+
const webRecordingDir = join7(PROJECTS_DIR, "web-recordings", id);
|
|
12279
|
+
const projectDir = join7(PROJECTS_DIR, id);
|
|
12281
12280
|
if (existsSync5(mobileRecordingDir)) {
|
|
12282
12281
|
rmSync3(mobileRecordingDir, { recursive: true, force: true });
|
|
12283
12282
|
console.log(`[Delete] Removed mobile recording: ${id}`);
|
|
@@ -12291,12 +12290,12 @@ app.delete("/api/projects/:id", async (c) => {
|
|
|
12291
12290
|
console.log(`[Delete] Removed project directory: ${id}`);
|
|
12292
12291
|
}
|
|
12293
12292
|
const { EXPORTS_DIR: exportsDir, FRAMES_DIR: framesDir } = await import("./db-745LC5YC.js");
|
|
12294
|
-
const projectExportsDir =
|
|
12293
|
+
const projectExportsDir = join7(exportsDir, id);
|
|
12295
12294
|
if (existsSync5(projectExportsDir)) {
|
|
12296
12295
|
rmSync3(projectExportsDir, { recursive: true, force: true });
|
|
12297
12296
|
console.log(`[Delete] Removed exports directory: ${id}`);
|
|
12298
12297
|
}
|
|
12299
|
-
const projectFramesDir =
|
|
12298
|
+
const projectFramesDir = join7(framesDir, id);
|
|
12300
12299
|
if (existsSync5(projectFramesDir)) {
|
|
12301
12300
|
rmSync3(projectFramesDir, { recursive: true, force: true });
|
|
12302
12301
|
console.log(`[Delete] Removed frames directory: ${id}`);
|
|
@@ -12315,19 +12314,19 @@ app.post("/api/upload", async (c) => {
|
|
|
12315
12314
|
return c.json({ error: "No file provided" }, 400);
|
|
12316
12315
|
}
|
|
12317
12316
|
const db = getDatabase();
|
|
12318
|
-
const { mkdirSync: mkdirSync5, writeFileSync: writeFileSync5, existsSync: fsExists, readdirSync:
|
|
12317
|
+
const { mkdirSync: mkdirSync5, writeFileSync: writeFileSync5, existsSync: fsExists, readdirSync: readdirSync5 } = await import("fs");
|
|
12319
12318
|
const { exec: exec2 } = await import("child_process");
|
|
12320
12319
|
const { promisify } = await import("util");
|
|
12321
12320
|
const execAsync = promisify(exec2);
|
|
12322
12321
|
const { PROJECTS_DIR: PROJECTS_DIR2, FRAMES_DIR } = await import("./db-745LC5YC.js");
|
|
12323
12322
|
const id = crypto.randomUUID();
|
|
12324
12323
|
const now = /* @__PURE__ */ new Date();
|
|
12325
|
-
const projectDir =
|
|
12324
|
+
const projectDir = join6(PROJECTS_DIR2, id);
|
|
12326
12325
|
if (!fsExists(projectDir)) {
|
|
12327
12326
|
mkdirSync5(projectDir, { recursive: true });
|
|
12328
12327
|
}
|
|
12329
12328
|
const fileName = file.name;
|
|
12330
|
-
const filePath =
|
|
12329
|
+
const filePath = join6(projectDir, fileName);
|
|
12331
12330
|
const arrayBuffer = await file.arrayBuffer();
|
|
12332
12331
|
writeFileSync5(filePath, Buffer.from(arrayBuffer));
|
|
12333
12332
|
let platform = null;
|
|
@@ -12348,17 +12347,17 @@ app.post("/api/upload", async (c) => {
|
|
|
12348
12347
|
let framePaths = [];
|
|
12349
12348
|
try {
|
|
12350
12349
|
if (isVideo) {
|
|
12351
|
-
const projectFramesDir =
|
|
12350
|
+
const projectFramesDir = join6(FRAMES_DIR, id);
|
|
12352
12351
|
if (!fsExists(projectFramesDir)) {
|
|
12353
12352
|
mkdirSync5(projectFramesDir, { recursive: true });
|
|
12354
12353
|
}
|
|
12355
|
-
const framePattern =
|
|
12354
|
+
const framePattern = join6(projectFramesDir, "frame_%04d.png");
|
|
12356
12355
|
try {
|
|
12357
12356
|
await execAsync(`ffmpeg -i "${filePath}" -vf "fps=1" -frames:v 15 "${framePattern}" -y 2>/dev/null`);
|
|
12358
12357
|
} catch {
|
|
12359
12358
|
}
|
|
12360
12359
|
if (fsExists(projectFramesDir)) {
|
|
12361
|
-
framePaths =
|
|
12360
|
+
framePaths = readdirSync5(projectFramesDir).filter((f) => f.endsWith(".png")).sort().map((f) => join6(projectFramesDir, f));
|
|
12362
12361
|
}
|
|
12363
12362
|
if (framePaths.length > 0) {
|
|
12364
12363
|
const { bestFrame, analyses } = await selectBestFrame(framePaths);
|
|
@@ -12417,11 +12416,11 @@ app.post("/api/capture/start", async (c) => {
|
|
|
12417
12416
|
const db = getDatabase();
|
|
12418
12417
|
const id = crypto.randomUUID();
|
|
12419
12418
|
const now = /* @__PURE__ */ new Date();
|
|
12420
|
-
const projectDir =
|
|
12419
|
+
const projectDir = join6(PROJECTS_DIR2, id);
|
|
12421
12420
|
if (!fsExists(projectDir)) {
|
|
12422
12421
|
mkdirSync5(projectDir, { recursive: true });
|
|
12423
12422
|
}
|
|
12424
|
-
const screenshotPath =
|
|
12423
|
+
const screenshotPath = join6(projectDir, "capture.png");
|
|
12425
12424
|
const captureType = body.sourceType || body.type || "screen";
|
|
12426
12425
|
const sourceId = typeof body.sourceId === "string" ? body.sourceId.trim() : "";
|
|
12427
12426
|
const osPlatform = process.platform === "darwin" ? "macos" : process.platform;
|
|
@@ -12585,10 +12584,10 @@ app.post("/api/capture/mobile/start", async (c) => {
|
|
|
12585
12584
|
const { mkdirSync: mkdirSync5 } = await import("fs");
|
|
12586
12585
|
const execAsync = promisify(exec2);
|
|
12587
12586
|
const projectId = `capture_${Date.now()}`;
|
|
12588
|
-
const projectDir =
|
|
12589
|
-
const screenshotsDir =
|
|
12587
|
+
const projectDir = join6(PROJECTS_DIR, projectId);
|
|
12588
|
+
const screenshotsDir = join6(projectDir, "screenshots");
|
|
12590
12589
|
mkdirSync5(screenshotsDir, { recursive: true });
|
|
12591
|
-
const videoPath =
|
|
12590
|
+
const videoPath = join6(projectDir, "recording.mp4");
|
|
12592
12591
|
const initialAppId = getForegroundAppIdForPlatform(platform, targetDeviceId);
|
|
12593
12592
|
let recordProcess = null;
|
|
12594
12593
|
if (platform === "ios") {
|
|
@@ -12705,8 +12704,8 @@ app.post("/api/capture/web/start", async (c) => {
|
|
|
12705
12704
|
const vpRes = CAPTURE_RESOLUTIONS[vpResKey || captureResKey || "1080"] || captureRes;
|
|
12706
12705
|
const { mkdirSync: mkdirSync5 } = await import("fs");
|
|
12707
12706
|
const projectId = `capture_web_${Date.now()}`;
|
|
12708
|
-
const projectDir =
|
|
12709
|
-
const screenshotsDir =
|
|
12707
|
+
const projectDir = join6(PROJECTS_DIR, projectId);
|
|
12708
|
+
const screenshotsDir = join6(projectDir, "screenshots");
|
|
12710
12709
|
mkdirSync5(screenshotsDir, { recursive: true });
|
|
12711
12710
|
let browser = null;
|
|
12712
12711
|
let page = null;
|
|
@@ -12802,7 +12801,7 @@ app.post("/api/capture/web/stop", async (c) => {
|
|
|
12802
12801
|
const projectId = session.projectId;
|
|
12803
12802
|
const endedAt = Date.now();
|
|
12804
12803
|
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
12805
|
-
const projectDir =
|
|
12804
|
+
const projectDir = join6(PROJECTS_DIR, projectId);
|
|
12806
12805
|
let finalPageUrl = session.url || null;
|
|
12807
12806
|
let faviconCandidates = [];
|
|
12808
12807
|
if (session.page) {
|
|
@@ -12833,7 +12832,7 @@ app.post("/api/capture/web/stop", async (c) => {
|
|
|
12833
12832
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
12834
12833
|
session.networkDetach?.();
|
|
12835
12834
|
if (projectId) {
|
|
12836
|
-
const sessionPath =
|
|
12835
|
+
const sessionPath = join6(projectDir, "session.json");
|
|
12837
12836
|
writeFileSync5(sessionPath, JSON.stringify({
|
|
12838
12837
|
id: projectId,
|
|
12839
12838
|
name: `Web Capture - ${new Date(session.startTime).toISOString()}`,
|
|
@@ -12901,8 +12900,8 @@ async function analyzeProjectInBackground(projectId) {
|
|
|
12901
12900
|
});
|
|
12902
12901
|
return;
|
|
12903
12902
|
}
|
|
12904
|
-
const { readdirSync:
|
|
12905
|
-
const files =
|
|
12903
|
+
const { readdirSync: readdirSync5 } = await import("fs");
|
|
12904
|
+
const files = readdirSync5(projectDir);
|
|
12906
12905
|
const videoFile = files.find((f) => f.endsWith(".mp4") || f.endsWith(".webm") || f.endsWith(".mov"));
|
|
12907
12906
|
if (!videoFile) {
|
|
12908
12907
|
broadcastProgress("save", "done", "Capture saved without video analysis");
|
|
@@ -12922,25 +12921,25 @@ async function analyzeProjectInBackground(projectId) {
|
|
|
12922
12921
|
});
|
|
12923
12922
|
return;
|
|
12924
12923
|
}
|
|
12925
|
-
const videoPath =
|
|
12924
|
+
const videoPath = join6(projectDir, videoFile);
|
|
12926
12925
|
const { exec: exec2 } = await import("child_process");
|
|
12927
12926
|
const { promisify } = await import("util");
|
|
12928
12927
|
const { mkdirSync: mkdirSync5 } = await import("fs");
|
|
12929
12928
|
const execAsync = promisify(exec2);
|
|
12930
|
-
const framesDir =
|
|
12929
|
+
const framesDir = join6(projectDir, "frames");
|
|
12931
12930
|
mkdirSync5(framesDir, { recursive: true });
|
|
12932
12931
|
broadcastProgress("extract", "running", "Extracting frames from video...");
|
|
12933
12932
|
await execAsync(`ffmpeg -i "${videoPath}" -vf "fps=1" -q:v 2 "${framesDir}/frame_%04d.jpg" -y`);
|
|
12934
12933
|
broadcastProgress("extract", "done", "Frames extracted");
|
|
12935
12934
|
const { isBlankFrame } = await import("./frames-RCNLSDD6.js");
|
|
12936
12935
|
const { unlinkSync } = await import("fs");
|
|
12937
|
-
const allFrameFiles =
|
|
12936
|
+
const allFrameFiles = readdirSync5(framesDir).filter((f) => f.endsWith(".jpg")).sort();
|
|
12938
12937
|
const frameFiles = [];
|
|
12939
12938
|
for (const f of allFrameFiles) {
|
|
12940
|
-
const { isBlank } = isBlankFrame(
|
|
12939
|
+
const { isBlank } = isBlankFrame(join6(framesDir, f));
|
|
12941
12940
|
if (isBlank) {
|
|
12942
12941
|
try {
|
|
12943
|
-
unlinkSync(
|
|
12942
|
+
unlinkSync(join6(framesDir, f));
|
|
12944
12943
|
} catch {
|
|
12945
12944
|
}
|
|
12946
12945
|
} else {
|
|
@@ -12977,7 +12976,7 @@ async function analyzeProjectInBackground(projectId) {
|
|
|
12977
12976
|
const ocrFrames = frameFiles.slice(0, 10);
|
|
12978
12977
|
broadcastProgress("ocr", "running", `Processing 0/${ocrFrames.length} frames...`, void 0, 0, ocrFrames.length);
|
|
12979
12978
|
for (const [index, frameFile] of ocrFrames.entries()) {
|
|
12980
|
-
const framePath =
|
|
12979
|
+
const framePath = join6(framesDir, frameFile);
|
|
12981
12980
|
const ocrResult = await recognizeText(framePath);
|
|
12982
12981
|
if (ocrResult.success && ocrResult.text) {
|
|
12983
12982
|
allOcrText += ocrResult.text + "\n\n";
|
|
@@ -13042,7 +13041,7 @@ async function analyzeProjectInBackground(projectId) {
|
|
|
13042
13041
|
} else {
|
|
13043
13042
|
broadcastProgress("summary", "skipped", "Skipped \u2014 no text to analyze");
|
|
13044
13043
|
}
|
|
13045
|
-
const extractedThumbnailPath =
|
|
13044
|
+
const extractedThumbnailPath = join6(framesDir, bestFrame);
|
|
13046
13045
|
const resolvedThumbnailPath = project.thumbnailPath && existsSync5(project.thumbnailPath) ? project.thumbnailPath : existsSync5(extractedThumbnailPath) ? extractedThumbnailPath : null;
|
|
13047
13046
|
const ocrEngine = ocrEngines.has("vision") ? "vision" : ocrEngines.has("tesseract") ? "tesseract" : null;
|
|
13048
13047
|
const ocrConfidence = ocrConfidences.length > 0 ? ocrConfidences.reduce((sum, value) => sum + value, 0) / ocrConfidences.length : null;
|
|
@@ -13113,14 +13112,14 @@ app.post("/api/analyze/:id", async (c) => {
|
|
|
13113
13112
|
try {
|
|
13114
13113
|
const { exec: exec2 } = await import("child_process");
|
|
13115
13114
|
const { promisify } = await import("util");
|
|
13116
|
-
const { mkdirSync: mkdirSync5, readdirSync:
|
|
13115
|
+
const { mkdirSync: mkdirSync5, readdirSync: readdirSync5 } = await import("fs");
|
|
13117
13116
|
const execAsync = promisify(exec2);
|
|
13118
13117
|
const { FRAMES_DIR } = await import("./db-745LC5YC.js");
|
|
13119
|
-
const projectFramesDir =
|
|
13118
|
+
const projectFramesDir = join6(FRAMES_DIR, id);
|
|
13120
13119
|
if (!existsSync5(projectFramesDir)) {
|
|
13121
13120
|
mkdirSync5(projectFramesDir, { recursive: true });
|
|
13122
13121
|
}
|
|
13123
|
-
const framePattern =
|
|
13122
|
+
const framePattern = join6(projectFramesDir, "frame_%04d.png");
|
|
13124
13123
|
try {
|
|
13125
13124
|
const { stderr } = await execAsync(`ffmpeg -i "${filePath}" -vf "fps=1" -frames:v 10 "${framePattern}" -y 2>&1`);
|
|
13126
13125
|
if (stderr) console.log("[FFmpeg] Output:", stderr.slice(0, 500));
|
|
@@ -13133,7 +13132,7 @@ app.post("/api/analyze/:id", async (c) => {
|
|
|
13133
13132
|
console.error("[FFmpeg] Keyframe fallback also failed:", fallbackError);
|
|
13134
13133
|
}
|
|
13135
13134
|
}
|
|
13136
|
-
const frameFiles =
|
|
13135
|
+
const frameFiles = readdirSync5(projectFramesDir).filter((f) => f.endsWith(".png")).sort().map((f) => join6(projectFramesDir, f));
|
|
13137
13136
|
frameCount = frameFiles.length;
|
|
13138
13137
|
console.log(`[FFmpeg] Extracted ${frameCount} frames from video`);
|
|
13139
13138
|
if (frameFiles.length > 0) {
|
|
@@ -13244,9 +13243,9 @@ app.get("/api/grid/backgrounds", async (c) => {
|
|
|
13244
13243
|
try {
|
|
13245
13244
|
const { getAvailableBackgrounds, PRESET_GRADIENTS, PRESET_SOLID_COLORS } = await import("./gridCompositor-VUWBZXYL.js");
|
|
13246
13245
|
const possibleBgDirs = [
|
|
13247
|
-
|
|
13248
|
-
|
|
13249
|
-
|
|
13246
|
+
join6(__dirname, "..", "assets", "backgrounds"),
|
|
13247
|
+
join6(__dirname, "..", "..", "assets", "backgrounds"),
|
|
13248
|
+
join6(process.cwd(), "assets", "backgrounds")
|
|
13250
13249
|
];
|
|
13251
13250
|
const backgroundsDir = possibleBgDirs.find((d) => existsSync5(d)) || possibleBgDirs[0];
|
|
13252
13251
|
const imageBackgrounds = getAvailableBackgrounds(backgroundsDir);
|
|
@@ -13273,21 +13272,21 @@ app.post("/api/grid/preview", async (c) => {
|
|
|
13273
13272
|
const { composeGrid, recommendLayout } = await import("./gridCompositor-VUWBZXYL.js");
|
|
13274
13273
|
const { EXPORTS_DIR: EXPORTS_DIR2 } = await import("./db-745LC5YC.js");
|
|
13275
13274
|
const { mkdirSync: mkdirSync5 } = await import("fs");
|
|
13276
|
-
const previewDir =
|
|
13275
|
+
const previewDir = join6(EXPORTS_DIR2, "grid-previews");
|
|
13277
13276
|
if (!existsSync5(previewDir)) {
|
|
13278
13277
|
mkdirSync5(previewDir, { recursive: true });
|
|
13279
13278
|
}
|
|
13280
13279
|
const previewId = crypto.randomUUID();
|
|
13281
|
-
const outputPath =
|
|
13280
|
+
const outputPath = join6(previewDir, `preview-${previewId}.png`);
|
|
13282
13281
|
const gridConfig = {
|
|
13283
13282
|
...config,
|
|
13284
13283
|
layout: config?.layout || recommendLayout(images.length, config?.aspectRatio || "9:16")
|
|
13285
13284
|
};
|
|
13286
13285
|
if (gridConfig.background?.type === "image" && gridConfig.background?.imageId) {
|
|
13287
13286
|
const possibleBgDirs = [
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
|
|
13287
|
+
join6(__dirname, "..", "assets", "backgrounds"),
|
|
13288
|
+
join6(__dirname, "..", "..", "assets", "backgrounds"),
|
|
13289
|
+
join6(process.cwd(), "assets", "backgrounds")
|
|
13291
13290
|
];
|
|
13292
13291
|
const backgroundsDir = possibleBgDirs.find((d) => existsSync5(d)) || possibleBgDirs[0];
|
|
13293
13292
|
const { getAvailableBackgrounds } = await import("./gridCompositor-VUWBZXYL.js");
|
|
@@ -13430,13 +13429,13 @@ app.post("/api/grid/export", async (c) => {
|
|
|
13430
13429
|
const { composeGrid, recommendLayout } = await import("./gridCompositor-VUWBZXYL.js");
|
|
13431
13430
|
const { EXPORTS_DIR: EXPORTS_DIR2 } = await import("./db-745LC5YC.js");
|
|
13432
13431
|
const { mkdirSync: mkdirSync5 } = await import("fs");
|
|
13433
|
-
const exportDir = projectId ?
|
|
13432
|
+
const exportDir = projectId ? join6(EXPORTS_DIR2, projectId) : join6(EXPORTS_DIR2, "grids");
|
|
13434
13433
|
if (!existsSync5(exportDir)) {
|
|
13435
13434
|
mkdirSync5(exportDir, { recursive: true });
|
|
13436
13435
|
}
|
|
13437
13436
|
const timestamp = Date.now();
|
|
13438
13437
|
const exportFilename = filename || `grid-${timestamp}.png`;
|
|
13439
|
-
const outputPath =
|
|
13438
|
+
const outputPath = join6(exportDir, exportFilename);
|
|
13440
13439
|
const gridConfig = {
|
|
13441
13440
|
...config,
|
|
13442
13441
|
layout: config?.layout || recommendLayout(images.length, config?.aspectRatio || "9:16"),
|
|
@@ -13445,9 +13444,9 @@ app.post("/api/grid/export", async (c) => {
|
|
|
13445
13444
|
};
|
|
13446
13445
|
if (gridConfig.background?.type === "image" && gridConfig.background?.imageId) {
|
|
13447
13446
|
const possibleBgDirs = [
|
|
13448
|
-
|
|
13449
|
-
|
|
13450
|
-
|
|
13447
|
+
join6(__dirname, "..", "assets", "backgrounds"),
|
|
13448
|
+
join6(__dirname, "..", "..", "assets", "backgrounds"),
|
|
13449
|
+
join6(process.cwd(), "assets", "backgrounds")
|
|
13451
13450
|
];
|
|
13452
13451
|
const backgroundsDir = possibleBgDirs.find((d) => existsSync5(d)) || possibleBgDirs[0];
|
|
13453
13452
|
const { getAvailableBackgrounds } = await import("./gridCompositor-VUWBZXYL.js");
|
|
@@ -13514,11 +13513,11 @@ app.post("/api/grid/infographic", async (c) => {
|
|
|
13514
13513
|
}
|
|
13515
13514
|
const { composeInfographic } = await import("./gridCompositor-VUWBZXYL.js");
|
|
13516
13515
|
const { EXPORTS_DIR: EXPORTS_DIR2 } = await import("./db-745LC5YC.js");
|
|
13517
|
-
const exportDir = projectId ?
|
|
13516
|
+
const exportDir = projectId ? join6(EXPORTS_DIR2, projectId) : join6(EXPORTS_DIR2, "grids");
|
|
13518
13517
|
if (!existsSync5(exportDir)) {
|
|
13519
13518
|
mkdirSync4(exportDir, { recursive: true });
|
|
13520
13519
|
}
|
|
13521
|
-
const outputPath =
|
|
13520
|
+
const outputPath = join6(exportDir, `infographic-${Date.now()}.png`);
|
|
13522
13521
|
const result = await composeInfographic(
|
|
13523
13522
|
images.map((img, i) => ({
|
|
13524
13523
|
imagePath: img.path || img.imagePath,
|
|
@@ -13620,14 +13619,14 @@ app.get("/api/grid/project-frames/:id", async (c) => {
|
|
|
13620
13619
|
}
|
|
13621
13620
|
const project = result[0];
|
|
13622
13621
|
const { FRAMES_DIR } = await import("./db-745LC5YC.js");
|
|
13623
|
-
const { readdirSync:
|
|
13622
|
+
const { readdirSync: readdirSync5 } = await import("fs");
|
|
13624
13623
|
const availableFrames = [];
|
|
13625
13624
|
const { isBlankFrame } = await import("./frames-RCNLSDD6.js");
|
|
13626
|
-
const projectFramesDir =
|
|
13625
|
+
const projectFramesDir = join6(FRAMES_DIR, id);
|
|
13627
13626
|
if (existsSync5(projectFramesDir)) {
|
|
13628
|
-
const frameFiles =
|
|
13627
|
+
const frameFiles = readdirSync5(projectFramesDir).filter((f) => /\.(png|jpg|jpeg)$/i.test(f)).sort();
|
|
13629
13628
|
for (const f of frameFiles) {
|
|
13630
|
-
const framePath =
|
|
13629
|
+
const framePath = join6(projectFramesDir, f);
|
|
13631
13630
|
if (isBlankFrame(framePath).isBlank) continue;
|
|
13632
13631
|
availableFrames.push({
|
|
13633
13632
|
path: framePath,
|
|
@@ -13636,11 +13635,11 @@ app.get("/api/grid/project-frames/:id", async (c) => {
|
|
|
13636
13635
|
});
|
|
13637
13636
|
}
|
|
13638
13637
|
}
|
|
13639
|
-
const projectFramesDir2 =
|
|
13638
|
+
const projectFramesDir2 = join6(PROJECTS_DIR, id, "frames");
|
|
13640
13639
|
if (existsSync5(projectFramesDir2)) {
|
|
13641
|
-
const frameFiles =
|
|
13640
|
+
const frameFiles = readdirSync5(projectFramesDir2).filter((f) => /\.(png|jpg|jpeg)$/i.test(f)).sort();
|
|
13642
13641
|
for (const f of frameFiles) {
|
|
13643
|
-
const framePath =
|
|
13642
|
+
const framePath = join6(projectFramesDir2, f);
|
|
13644
13643
|
if (!availableFrames.some((frame) => frame.path === framePath)) {
|
|
13645
13644
|
availableFrames.push({
|
|
13646
13645
|
path: framePath,
|
|
@@ -13668,14 +13667,14 @@ app.get("/api/grid/project-frames/:id", async (c) => {
|
|
|
13668
13667
|
});
|
|
13669
13668
|
}
|
|
13670
13669
|
if (project.videoPath && existsSync5(project.videoPath)) {
|
|
13671
|
-
const { statSync:
|
|
13672
|
-
const stats =
|
|
13670
|
+
const { statSync: statSync4 } = await import("fs");
|
|
13671
|
+
const stats = statSync4(project.videoPath);
|
|
13673
13672
|
if (stats.isDirectory()) {
|
|
13674
|
-
const screenshotsDir =
|
|
13673
|
+
const screenshotsDir = join6(project.videoPath, "screenshots");
|
|
13675
13674
|
if (existsSync5(screenshotsDir)) {
|
|
13676
|
-
const screenshotFiles =
|
|
13675
|
+
const screenshotFiles = readdirSync5(screenshotsDir).filter((f) => /\.(png|jpg|jpeg)$/i.test(f)).sort();
|
|
13677
13676
|
for (const f of screenshotFiles) {
|
|
13678
|
-
const screenshotPath =
|
|
13677
|
+
const screenshotPath = join6(screenshotsDir, f);
|
|
13679
13678
|
if (!availableFrames.some((frame) => frame.path === screenshotPath)) {
|
|
13680
13679
|
availableFrames.push({
|
|
13681
13680
|
path: screenshotPath,
|
|
@@ -13715,7 +13714,7 @@ function writeExportText(filePath, text) {
|
|
|
13715
13714
|
function copyPathIntoExportBundle(sourcePath, destinationPath) {
|
|
13716
13715
|
if (!sourcePath || !existsSync5(sourcePath)) return false;
|
|
13717
13716
|
ensureExportParentDir(destinationPath);
|
|
13718
|
-
if (
|
|
13717
|
+
if (statSync3(sourcePath).isDirectory()) {
|
|
13719
13718
|
cpSync(sourcePath, destinationPath, { recursive: true });
|
|
13720
13719
|
} else {
|
|
13721
13720
|
copyFileSync(sourcePath, destinationPath);
|
|
@@ -13725,16 +13724,16 @@ function copyPathIntoExportBundle(sourcePath, destinationPath) {
|
|
|
13725
13724
|
function looksLikeRecordingDirectory(dirPath) {
|
|
13726
13725
|
if (!dirPath || !existsSync5(dirPath)) return false;
|
|
13727
13726
|
try {
|
|
13728
|
-
if (!
|
|
13727
|
+
if (!statSync3(dirPath).isDirectory()) return false;
|
|
13729
13728
|
} catch {
|
|
13730
13729
|
return false;
|
|
13731
13730
|
}
|
|
13732
13731
|
return [
|
|
13733
|
-
|
|
13734
|
-
|
|
13735
|
-
|
|
13736
|
-
|
|
13737
|
-
|
|
13732
|
+
join6(dirPath, "session.json"),
|
|
13733
|
+
join6(dirPath, "screenshots"),
|
|
13734
|
+
join6(dirPath, "video"),
|
|
13735
|
+
join6(dirPath, "test.yaml"),
|
|
13736
|
+
join6(dirPath, "test.spec.ts")
|
|
13738
13737
|
].some((candidate) => existsSync5(candidate));
|
|
13739
13738
|
}
|
|
13740
13739
|
function resolveProjectBundleRecordingDir(originalVideoPath, resolvedVideoPath) {
|
|
@@ -13751,13 +13750,13 @@ function resolveProjectBundleRecordingDir(originalVideoPath, resolvedVideoPath)
|
|
|
13751
13750
|
return null;
|
|
13752
13751
|
}
|
|
13753
13752
|
function copyProjectExportArtifacts(sourceDir, destinationDir) {
|
|
13754
|
-
if (!existsSync5(sourceDir) || !
|
|
13753
|
+
if (!existsSync5(sourceDir) || !statSync3(sourceDir).isDirectory()) {
|
|
13755
13754
|
return 0;
|
|
13756
13755
|
}
|
|
13757
13756
|
let copiedCount = 0;
|
|
13758
|
-
for (const entry of
|
|
13757
|
+
for (const entry of readdirSync4(sourceDir)) {
|
|
13759
13758
|
if (/\.(applab|esvp)$/i.test(entry)) continue;
|
|
13760
|
-
if (copyPathIntoExportBundle(
|
|
13759
|
+
if (copyPathIntoExportBundle(join6(sourceDir, entry), join6(destinationDir, entry))) {
|
|
13761
13760
|
copiedCount += 1;
|
|
13762
13761
|
}
|
|
13763
13762
|
}
|
|
@@ -13881,7 +13880,7 @@ app.post("/api/export", async (c) => {
|
|
|
13881
13880
|
}
|
|
13882
13881
|
const { promisify } = await import("util");
|
|
13883
13882
|
const execAsync = promisify(exec);
|
|
13884
|
-
const exportDir =
|
|
13883
|
+
const exportDir = join6(EXPORTS_DIR, projectId);
|
|
13885
13884
|
if (!existsSync5(exportDir)) {
|
|
13886
13885
|
mkdirSync4(exportDir, { recursive: true });
|
|
13887
13886
|
}
|
|
@@ -13889,13 +13888,13 @@ app.post("/api/export", async (c) => {
|
|
|
13889
13888
|
let outputPath = "";
|
|
13890
13889
|
let mimeType = "image/png";
|
|
13891
13890
|
const resolvedVideoPath = resolveVideoPath(rawProject.videoPath);
|
|
13892
|
-
const isVideoPathDirectory = !!rawProject.videoPath && existsSync5(rawProject.videoPath) &&
|
|
13891
|
+
const isVideoPathDirectory = !!rawProject.videoPath && existsSync5(rawProject.videoPath) && statSync3(rawProject.videoPath).isDirectory();
|
|
13893
13892
|
if (format === "applab" || format === "esvp") {
|
|
13894
13893
|
const projectFrames = await db.select().from(frames).where(eq(frames.projectId, projectId)).orderBy(frames.frameNumber);
|
|
13895
13894
|
const exportRecords = await db.select().from(projectExports).where(eq(projectExports.projectId, projectId)).orderBy(desc(projectExports.createdAt));
|
|
13896
13895
|
const recordingBaseDir = resolveProjectBundleRecordingDir(rawProject.videoPath, resolvedVideoPath);
|
|
13897
|
-
const exportArtifactsDir =
|
|
13898
|
-
const sessionPath = recordingBaseDir ?
|
|
13896
|
+
const exportArtifactsDir = join6(EXPORTS_DIR, projectId);
|
|
13897
|
+
const sessionPath = recordingBaseDir ? join6(recordingBaseDir, "session.json") : null;
|
|
13899
13898
|
let sessionData = null;
|
|
13900
13899
|
let networkEntries = [];
|
|
13901
13900
|
let networkCapture = null;
|
|
@@ -13911,9 +13910,9 @@ app.post("/api/export", async (c) => {
|
|
|
13911
13910
|
sessionData = null;
|
|
13912
13911
|
}
|
|
13913
13912
|
}
|
|
13914
|
-
const stagingRoot = mkdtempSync(
|
|
13913
|
+
const stagingRoot = mkdtempSync(join6(tmpdir2(), `${format}-export-`));
|
|
13915
13914
|
const bundleFolderName = `${sanitizeProjectExportSlug(rawProject.name)}-${new Date(timestamp).toISOString().slice(0, 10)}`;
|
|
13916
|
-
const bundleRoot =
|
|
13915
|
+
const bundleRoot = join6(stagingRoot, bundleFolderName);
|
|
13917
13916
|
mkdirSync4(bundleRoot, { recursive: true });
|
|
13918
13917
|
try {
|
|
13919
13918
|
const summaryPath = rawProject.aiSummary ? "analysis/app-intelligence.md" : null;
|
|
@@ -13924,25 +13923,25 @@ app.post("/api/export", async (c) => {
|
|
|
13924
13923
|
const extensionMatch = basename3(frame.imagePath).match(/(\.[^.]+)$/);
|
|
13925
13924
|
const extension = extensionMatch ? extensionMatch[1] : ".png";
|
|
13926
13925
|
const relativeImagePath = `frames/frame-${String(frame.frameNumber).padStart(4, "0")}${extension}`;
|
|
13927
|
-
copyPathIntoExportBundle(frame.imagePath,
|
|
13926
|
+
copyPathIntoExportBundle(frame.imagePath, join6(bundleRoot, relativeImagePath));
|
|
13928
13927
|
return {
|
|
13929
13928
|
...frame,
|
|
13930
13929
|
imagePath: relativeImagePath
|
|
13931
13930
|
};
|
|
13932
13931
|
});
|
|
13933
13932
|
const mediaFiles = [];
|
|
13934
|
-
if (resolvedVideoPath && existsSync5(resolvedVideoPath) && !
|
|
13933
|
+
if (resolvedVideoPath && existsSync5(resolvedVideoPath) && !statSync3(resolvedVideoPath).isDirectory()) {
|
|
13935
13934
|
const relativePath = `media/${resolvedMediaName}`;
|
|
13936
|
-
copyPathIntoExportBundle(resolvedVideoPath,
|
|
13935
|
+
copyPathIntoExportBundle(resolvedVideoPath, join6(bundleRoot, relativePath));
|
|
13937
13936
|
mediaFiles.push({ role: "primary-media", path: relativePath });
|
|
13938
13937
|
}
|
|
13939
13938
|
if (rawProject.thumbnailPath && existsSync5(rawProject.thumbnailPath) && thumbnailName) {
|
|
13940
13939
|
const relativePath = `media/${thumbnailName}`;
|
|
13941
|
-
copyPathIntoExportBundle(rawProject.thumbnailPath,
|
|
13940
|
+
copyPathIntoExportBundle(rawProject.thumbnailPath, join6(bundleRoot, relativePath));
|
|
13942
13941
|
mediaFiles.push({ role: "thumbnail", path: relativePath });
|
|
13943
13942
|
}
|
|
13944
|
-
const recordingIncluded = recordingBaseDir ? copyPathIntoExportBundle(recordingBaseDir,
|
|
13945
|
-
const exportArtifactCount = copyProjectExportArtifacts(exportArtifactsDir,
|
|
13943
|
+
const recordingIncluded = recordingBaseDir ? copyPathIntoExportBundle(recordingBaseDir, join6(bundleRoot, "recording")) : false;
|
|
13944
|
+
const exportArtifactCount = copyProjectExportArtifacts(exportArtifactsDir, join6(bundleRoot, "exports"));
|
|
13946
13945
|
const esvpSessionId = resolveProjectESVPSessionId(esvp);
|
|
13947
13946
|
const esvpServerUrl = resolveProjectESVPServerUrl(esvp);
|
|
13948
13947
|
let esvpSnapshot = null;
|
|
@@ -13968,7 +13967,7 @@ app.post("/api/export", async (c) => {
|
|
|
13968
13967
|
thumbnailPath: thumbnailName ? `media/${thumbnailName}` : normalizedProject.thumbnailPath,
|
|
13969
13968
|
frames: bundledFrames
|
|
13970
13969
|
};
|
|
13971
|
-
writeExportJson(
|
|
13970
|
+
writeExportJson(join6(bundleRoot, "manifest.json"), {
|
|
13972
13971
|
bundleVersion: 1,
|
|
13973
13972
|
packageFormat: format,
|
|
13974
13973
|
exportedAt: new Date(timestamp).toISOString(),
|
|
@@ -13996,42 +13995,42 @@ app.post("/api/export", async (c) => {
|
|
|
13996
13995
|
esvpSessionId: esvpSessionId || null
|
|
13997
13996
|
}
|
|
13998
13997
|
});
|
|
13999
|
-
writeExportJson(
|
|
14000
|
-
writeExportJson(
|
|
13998
|
+
writeExportJson(join6(bundleRoot, "metadata", "project.json"), packagedProject);
|
|
13999
|
+
writeExportJson(join6(bundleRoot, "metadata", "exports.json"), exportRecords);
|
|
14001
14000
|
if (sessionData) {
|
|
14002
|
-
writeExportJson(
|
|
14001
|
+
writeExportJson(join6(bundleRoot, "metadata", "session.json"), sessionData);
|
|
14003
14002
|
}
|
|
14004
14003
|
if (rawProject.taskHubLinks) {
|
|
14005
|
-
writeExportJson(
|
|
14004
|
+
writeExportJson(join6(bundleRoot, "taskhub", "links.json"), normalizedProject.taskHubLinks);
|
|
14006
14005
|
}
|
|
14007
14006
|
if (rawProject.taskRequirements) {
|
|
14008
|
-
writeExportJson(
|
|
14007
|
+
writeExportJson(join6(bundleRoot, "taskhub", "requirements.json"), normalizedProject.taskRequirements);
|
|
14009
14008
|
}
|
|
14010
14009
|
if (rawProject.taskTestMap) {
|
|
14011
|
-
writeExportJson(
|
|
14010
|
+
writeExportJson(join6(bundleRoot, "taskhub", "test-map.json"), normalizedProject.taskTestMap);
|
|
14012
14011
|
}
|
|
14013
14012
|
if (summaryPath) {
|
|
14014
|
-
writeExportText(
|
|
14013
|
+
writeExportText(join6(bundleRoot, summaryPath), rawProject.aiSummary || "");
|
|
14015
14014
|
}
|
|
14016
14015
|
if (ocrPath) {
|
|
14017
|
-
writeExportText(
|
|
14016
|
+
writeExportText(join6(bundleRoot, ocrPath), rawProject.ocrText || "");
|
|
14018
14017
|
}
|
|
14019
14018
|
if (bundledFrames.length > 0) {
|
|
14020
|
-
writeExportJson(
|
|
14019
|
+
writeExportJson(join6(bundleRoot, "analysis", "frames.json"), bundledFrames);
|
|
14021
14020
|
}
|
|
14022
14021
|
if (networkEntries.length > 0) {
|
|
14023
|
-
writeExportJson(
|
|
14022
|
+
writeExportJson(join6(bundleRoot, "network", "entries.json"), networkEntries);
|
|
14024
14023
|
}
|
|
14025
14024
|
if (networkCapture) {
|
|
14026
|
-
writeExportJson(
|
|
14025
|
+
writeExportJson(join6(bundleRoot, "network", "capture.json"), networkCapture);
|
|
14027
14026
|
}
|
|
14028
14027
|
if (esvp) {
|
|
14029
|
-
writeExportJson(
|
|
14028
|
+
writeExportJson(join6(bundleRoot, "network", "esvp.json"), esvp);
|
|
14030
14029
|
}
|
|
14031
14030
|
if (esvpSnapshot) {
|
|
14032
|
-
writeExportJson(
|
|
14031
|
+
writeExportJson(join6(bundleRoot, "esvp", "snapshot.json"), esvpSnapshot);
|
|
14033
14032
|
}
|
|
14034
|
-
writeExportText(
|
|
14033
|
+
writeExportText(join6(bundleRoot, "README.txt"), [
|
|
14035
14034
|
`${rawProject.name}`,
|
|
14036
14035
|
`Exported from DiscoveryLab ${APP_VERSION} on ${new Date(timestamp).toISOString()}.`,
|
|
14037
14036
|
"",
|
|
@@ -14045,24 +14044,24 @@ app.post("/api/export", async (c) => {
|
|
|
14045
14044
|
"- previously generated export assets such as grids and renders",
|
|
14046
14045
|
"- Task Hub links, requirements, and test map"
|
|
14047
14046
|
].join("\n"));
|
|
14048
|
-
outputPath =
|
|
14047
|
+
outputPath = join6(exportDir, `export-${timestamp}.${format}`);
|
|
14049
14048
|
mimeType = "application/zip";
|
|
14050
14049
|
await createProjectArchive(bundleRoot, outputPath);
|
|
14051
14050
|
} finally {
|
|
14052
14051
|
rmSync2(stagingRoot, { recursive: true, force: true });
|
|
14053
14052
|
}
|
|
14054
14053
|
} else if (isVideoPathDirectory && format === "gif") {
|
|
14055
|
-
outputPath =
|
|
14054
|
+
outputPath = join6(exportDir, `export-${timestamp}.gif`);
|
|
14056
14055
|
mimeType = "image/gif";
|
|
14057
|
-
const screenshotsDir =
|
|
14058
|
-
const screenshotsDirExists = existsSync5(screenshotsDir) &&
|
|
14056
|
+
const screenshotsDir = join6(rawProject.videoPath, "screenshots");
|
|
14057
|
+
const screenshotsDirExists = existsSync5(screenshotsDir) && statSync3(screenshotsDir).isDirectory();
|
|
14059
14058
|
const sourceDir = screenshotsDirExists ? screenshotsDir : rawProject.videoPath;
|
|
14060
|
-
const { readdirSync:
|
|
14061
|
-
const pngFiles =
|
|
14059
|
+
const { readdirSync: readdirSync5 } = await import("fs");
|
|
14060
|
+
const pngFiles = readdirSync5(sourceDir).filter((f) => f.endsWith(".png")).sort().map((f) => join6(sourceDir, f));
|
|
14062
14061
|
if (pngFiles.length === 0) {
|
|
14063
14062
|
return c.json({ error: "No screenshots found in recording" }, 400);
|
|
14064
14063
|
}
|
|
14065
|
-
const concatPath =
|
|
14064
|
+
const concatPath = join6(exportDir, `concat-${timestamp}.txt`);
|
|
14066
14065
|
const concatContent = pngFiles.map((f) => `file '${f}'
|
|
14067
14066
|
duration 0.5`).join("\n");
|
|
14068
14067
|
writeFileSync4(concatPath, concatContent);
|
|
@@ -14079,17 +14078,17 @@ duration 0.5`).join("\n");
|
|
|
14079
14078
|
return c.json({ error: "GIF creation failed - FFmpeg required. Install with: brew install ffmpeg" }, 400);
|
|
14080
14079
|
}
|
|
14081
14080
|
} else if (isVideoPathDirectory && format === "mp4") {
|
|
14082
|
-
outputPath =
|
|
14081
|
+
outputPath = join6(exportDir, `export-${timestamp}.mp4`);
|
|
14083
14082
|
mimeType = "video/mp4";
|
|
14084
|
-
const screenshotsDir =
|
|
14085
|
-
const screenshotsDirExists = existsSync5(screenshotsDir) &&
|
|
14083
|
+
const screenshotsDir = join6(rawProject.videoPath, "screenshots");
|
|
14084
|
+
const screenshotsDirExists = existsSync5(screenshotsDir) && statSync3(screenshotsDir).isDirectory();
|
|
14086
14085
|
const sourceDir = screenshotsDirExists ? screenshotsDir : rawProject.videoPath;
|
|
14087
|
-
const { readdirSync:
|
|
14088
|
-
const pngFiles =
|
|
14086
|
+
const { readdirSync: readdirSync5 } = await import("fs");
|
|
14087
|
+
const pngFiles = readdirSync5(sourceDir).filter((f) => f.endsWith(".png")).sort().map((f) => join6(sourceDir, f));
|
|
14089
14088
|
if (pngFiles.length === 0) {
|
|
14090
14089
|
return c.json({ error: "No screenshots found in recording" }, 400);
|
|
14091
14090
|
}
|
|
14092
|
-
const concatPath =
|
|
14091
|
+
const concatPath = join6(exportDir, `concat-${timestamp}.txt`);
|
|
14093
14092
|
const concatContent = pngFiles.map((f) => `file '${f}'
|
|
14094
14093
|
duration 0.5`).join("\n");
|
|
14095
14094
|
writeFileSync4(concatPath, concatContent);
|
|
@@ -14106,12 +14105,12 @@ duration 0.5`).join("\n");
|
|
|
14106
14105
|
return c.json({ error: "Video creation failed - FFmpeg required. Install with: brew install ffmpeg" }, 400);
|
|
14107
14106
|
}
|
|
14108
14107
|
} else if (isVideoPathDirectory) {
|
|
14109
|
-
outputPath =
|
|
14108
|
+
outputPath = join6(exportDir, `export-${timestamp}`);
|
|
14110
14109
|
cpSync(rawProject.videoPath, outputPath, { recursive: true });
|
|
14111
14110
|
mimeType = "application/octet-stream";
|
|
14112
14111
|
} else if (format === "png" || format === "jpg" || format === "jpeg") {
|
|
14113
14112
|
const ext = format === "jpg" ? "jpeg" : format;
|
|
14114
|
-
outputPath =
|
|
14113
|
+
outputPath = join6(exportDir, `export-${timestamp}.${format}`);
|
|
14115
14114
|
mimeType = `image/${ext}`;
|
|
14116
14115
|
if (resolvedVideoPath?.endsWith(`.${format}`)) {
|
|
14117
14116
|
copyFileSync(resolvedVideoPath, outputPath);
|
|
@@ -14120,11 +14119,11 @@ duration 0.5`).join("\n");
|
|
|
14120
14119
|
await execAsync(`sips -s format ${format} "${resolvedVideoPath}" --out "${outputPath}"`);
|
|
14121
14120
|
} catch {
|
|
14122
14121
|
copyFileSync(resolvedVideoPath, outputPath);
|
|
14123
|
-
outputPath =
|
|
14122
|
+
outputPath = join6(exportDir, `export-${timestamp}${resolvedVideoPath?.substring(resolvedVideoPath.lastIndexOf("."))}`);
|
|
14124
14123
|
}
|
|
14125
14124
|
}
|
|
14126
14125
|
} else if (format === "gif") {
|
|
14127
|
-
outputPath =
|
|
14126
|
+
outputPath = join6(exportDir, `export-${timestamp}.gif`);
|
|
14128
14127
|
mimeType = "image/gif";
|
|
14129
14128
|
try {
|
|
14130
14129
|
await execAsync(`ffmpeg -i "${resolvedVideoPath}" -vf "fps=10,scale=320:-1:flags=lanczos" "${outputPath}" -y`);
|
|
@@ -14134,7 +14133,7 @@ duration 0.5`).join("\n");
|
|
|
14134
14133
|
mimeType = "image/png";
|
|
14135
14134
|
}
|
|
14136
14135
|
} else if (format === "mp4") {
|
|
14137
|
-
outputPath =
|
|
14136
|
+
outputPath = join6(exportDir, `export-${timestamp}.mp4`);
|
|
14138
14137
|
mimeType = "video/mp4";
|
|
14139
14138
|
if (resolvedVideoPath?.endsWith(".mp4")) {
|
|
14140
14139
|
copyFileSync(resolvedVideoPath, outputPath);
|
|
@@ -14147,7 +14146,7 @@ duration 0.5`).join("\n");
|
|
|
14147
14146
|
}
|
|
14148
14147
|
} else {
|
|
14149
14148
|
const ext = resolvedVideoPath?.substring(resolvedVideoPath.lastIndexOf(".")) || "";
|
|
14150
|
-
outputPath =
|
|
14149
|
+
outputPath = join6(exportDir, `export-${timestamp}${ext}`);
|
|
14151
14150
|
if (resolvedVideoPath) {
|
|
14152
14151
|
copyFileSync(resolvedVideoPath, outputPath);
|
|
14153
14152
|
}
|
|
@@ -14170,7 +14169,7 @@ ${rawProject.aiSummary}
|
|
|
14170
14169
|
${rawProject.ocrText}
|
|
14171
14170
|
`;
|
|
14172
14171
|
}
|
|
14173
|
-
const textPath =
|
|
14172
|
+
const textPath = join6(exportDir, `export-${timestamp}.txt`);
|
|
14174
14173
|
writeFileSync4(textPath, textContent);
|
|
14175
14174
|
}
|
|
14176
14175
|
if (destination === "local" || destination === "clipboard") {
|
|
@@ -14205,7 +14204,7 @@ app.get("/api/visualization/:projectId/:templateId", async (c) => {
|
|
|
14205
14204
|
const [project] = await db.select().from(projects).where(eq(projects.id, projectId)).limit(1);
|
|
14206
14205
|
if (!project) return c.json({ error: "Project not found" }, 404);
|
|
14207
14206
|
const { FRAMES_DIR } = await import("./db-745LC5YC.js");
|
|
14208
|
-
const projectFramesDir =
|
|
14207
|
+
const projectFramesDir = join6(FRAMES_DIR, projectId);
|
|
14209
14208
|
const dbFrames = await db.select().from(frames).where(eq(frames.projectId, projectId)).orderBy(frames.frameNumber).limit(10);
|
|
14210
14209
|
let frameImages = [];
|
|
14211
14210
|
if (dbFrames.length > 0) {
|
|
@@ -14216,20 +14215,20 @@ app.get("/api/visualization/:projectId/:templateId", async (c) => {
|
|
|
14216
14215
|
}));
|
|
14217
14216
|
} else {
|
|
14218
14217
|
const screenshotsDirs = [
|
|
14219
|
-
|
|
14218
|
+
join6(projectFramesDir),
|
|
14220
14219
|
...project.videoPath ? [
|
|
14221
|
-
|
|
14220
|
+
join6(project.videoPath, "screenshots"),
|
|
14222
14221
|
project.videoPath
|
|
14223
14222
|
] : [],
|
|
14224
|
-
|
|
14225
|
-
|
|
14223
|
+
join6(PROJECTS_DIR, "maestro-recordings", projectId, "screenshots"),
|
|
14224
|
+
join6(PROJECTS_DIR, "web-recordings", projectId, "screenshots")
|
|
14226
14225
|
];
|
|
14227
14226
|
for (const dir of screenshotsDirs) {
|
|
14228
|
-
if (!existsSync5(dir) || !
|
|
14229
|
-
const files =
|
|
14227
|
+
if (!existsSync5(dir) || !statSync3(dir).isDirectory()) continue;
|
|
14228
|
+
const files = readdirSync4(dir).filter((f) => /\.(png|jpg|jpeg|gif|webp)$/i.test(f)).sort().slice(0, 10);
|
|
14230
14229
|
if (files.length > 0) {
|
|
14231
14230
|
frameImages = files.map((f, i) => ({
|
|
14232
|
-
imageUrl: `/api/file?path=${encodeURIComponent(
|
|
14231
|
+
imageUrl: `/api/file?path=${encodeURIComponent(join6(dir, f))}`,
|
|
14233
14232
|
label: `Screen ${i + 1}`,
|
|
14234
14233
|
number: i + 1
|
|
14235
14234
|
}));
|
|
@@ -14290,7 +14289,7 @@ app.get("/api/visualization/:projectId/:templateId", async (c) => {
|
|
|
14290
14289
|
case "app-flow-map": {
|
|
14291
14290
|
let phases = [];
|
|
14292
14291
|
const { EXPORTS_DIR: expDir } = await import("./db-745LC5YC.js");
|
|
14293
|
-
const cachePath =
|
|
14292
|
+
const cachePath = join6(expDir, projectId, "flowmap-cache.json");
|
|
14294
14293
|
let cachedFlowMap = null;
|
|
14295
14294
|
try {
|
|
14296
14295
|
if (existsSync5(cachePath)) {
|
|
@@ -14379,7 +14378,7 @@ Rules:
|
|
|
14379
14378
|
}
|
|
14380
14379
|
}
|
|
14381
14380
|
try {
|
|
14382
|
-
const cacheDir =
|
|
14381
|
+
const cacheDir = join6(expDir, projectId);
|
|
14383
14382
|
if (!existsSync5(cacheDir)) mkdirSync4(cacheDir, { recursive: true });
|
|
14384
14383
|
const cacheData = {
|
|
14385
14384
|
title: vizData.title,
|
|
@@ -14417,11 +14416,11 @@ Rules:
|
|
|
14417
14416
|
default:
|
|
14418
14417
|
return c.json({ error: `Unknown template: ${templateId}` }, 400);
|
|
14419
14418
|
}
|
|
14420
|
-
const templatePath =
|
|
14419
|
+
const templatePath = join6(__dirname, "..", "core", "visualizations", "templates", `${templateId}.html`);
|
|
14421
14420
|
const possiblePaths = [
|
|
14422
14421
|
templatePath,
|
|
14423
|
-
|
|
14424
|
-
|
|
14422
|
+
join6(__dirname, "..", "..", "src", "core", "visualizations", "templates", `${templateId}.html`),
|
|
14423
|
+
join6(process.cwd(), "src", "core", "visualizations", "templates", `${templateId}.html`)
|
|
14425
14424
|
];
|
|
14426
14425
|
let htmlContent = "";
|
|
14427
14426
|
for (const p of possiblePaths) {
|
|
@@ -14459,12 +14458,12 @@ app.post("/api/visualization/screenshot", async (c) => {
|
|
|
14459
14458
|
return c.json({ error: "projectId and templateId required" }, 400);
|
|
14460
14459
|
}
|
|
14461
14460
|
const { EXPORTS_DIR: EXPORTS_DIR2 } = await import("./db-745LC5YC.js");
|
|
14462
|
-
const exportDir =
|
|
14461
|
+
const exportDir = join6(EXPORTS_DIR2, projectId);
|
|
14463
14462
|
if (!existsSync5(exportDir)) {
|
|
14464
14463
|
mkdirSync4(exportDir, { recursive: true });
|
|
14465
14464
|
}
|
|
14466
14465
|
const ext = format === "gif" ? "gif" : "png";
|
|
14467
|
-
const outputPath =
|
|
14466
|
+
const outputPath = join6(exportDir, `viz-${templateId}-${Date.now()}.${ext}`);
|
|
14468
14467
|
const serverPort = process.env.PORT || "3847";
|
|
14469
14468
|
const vizUrl = `http://localhost:${serverPort}/api/visualization/${projectId}/${templateId}`;
|
|
14470
14469
|
const { chromium: chromium2 } = await import("playwright");
|
|
@@ -14473,12 +14472,12 @@ app.post("/api/visualization/screenshot", async (c) => {
|
|
|
14473
14472
|
await page.goto(vizUrl, { waitUntil: "networkidle" });
|
|
14474
14473
|
await page.waitForTimeout(2500);
|
|
14475
14474
|
if (format === "gif") {
|
|
14476
|
-
const framesDir =
|
|
14475
|
+
const framesDir = join6(exportDir, `viz-gif-frames-${Date.now()}`);
|
|
14477
14476
|
mkdirSync4(framesDir, { recursive: true });
|
|
14478
14477
|
const frameCount = 20;
|
|
14479
14478
|
const intervalMs = 200;
|
|
14480
14479
|
for (let i = 0; i < frameCount; i++) {
|
|
14481
|
-
await page.screenshot({ path:
|
|
14480
|
+
await page.screenshot({ path: join6(framesDir, `frame-${String(i).padStart(3, "0")}.png`) });
|
|
14482
14481
|
await page.waitForTimeout(intervalMs);
|
|
14483
14482
|
}
|
|
14484
14483
|
await browser.close();
|
|
@@ -14486,13 +14485,13 @@ app.post("/api/visualization/screenshot", async (c) => {
|
|
|
14486
14485
|
const execPromise = promisify(exec);
|
|
14487
14486
|
try {
|
|
14488
14487
|
await execPromise(
|
|
14489
|
-
`ffmpeg -framerate 5 -i "${
|
|
14488
|
+
`ffmpeg -framerate 5 -i "${join6(framesDir, "frame-%03d.png")}" -vf "scale=600:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 "${outputPath}" -y`,
|
|
14490
14489
|
{ timeout: 3e4 }
|
|
14491
14490
|
);
|
|
14492
14491
|
} catch (ffmpegError) {
|
|
14493
14492
|
const { copyFileSync: copyFileSync2 } = await import("fs");
|
|
14494
14493
|
const fallbackPath = outputPath.replace(".gif", ".png");
|
|
14495
|
-
copyFileSync2(
|
|
14494
|
+
copyFileSync2(join6(framesDir, "frame-000.png"), fallbackPath);
|
|
14496
14495
|
const { rmSync: rmSync4 } = await import("fs");
|
|
14497
14496
|
rmSync4(framesDir, { recursive: true, force: true });
|
|
14498
14497
|
return c.json({
|
|
@@ -14531,16 +14530,16 @@ app.get("/api/export/document/:projectId", async (c) => {
|
|
|
14531
14530
|
if (frameData.length === 0 && project.videoPath) {
|
|
14532
14531
|
const { FRAMES_DIR } = await import("./db-745LC5YC.js");
|
|
14533
14532
|
const dirs = [
|
|
14534
|
-
|
|
14535
|
-
|
|
14536
|
-
|
|
14537
|
-
|
|
14533
|
+
join6(FRAMES_DIR, projectId),
|
|
14534
|
+
join6(project.videoPath, "screenshots"),
|
|
14535
|
+
join6(PROJECTS_DIR, "maestro-recordings", projectId, "screenshots"),
|
|
14536
|
+
join6(PROJECTS_DIR, "web-recordings", projectId, "screenshots")
|
|
14538
14537
|
];
|
|
14539
14538
|
for (const dir of dirs) {
|
|
14540
|
-
if (existsSync5(dir) &&
|
|
14541
|
-
const files =
|
|
14539
|
+
if (existsSync5(dir) && statSync3(dir).isDirectory()) {
|
|
14540
|
+
const files = readdirSync4(dir).filter((f) => /\.(png|jpg|jpeg|webp)$/i.test(f)).sort().slice(0, 20);
|
|
14542
14541
|
if (files.length > 0) {
|
|
14543
|
-
frameData = files.map((f, i) => ({ imagePath:
|
|
14542
|
+
frameData = files.map((f, i) => ({ imagePath: join6(dir, f), ocrText: null }));
|
|
14544
14543
|
break;
|
|
14545
14544
|
}
|
|
14546
14545
|
}
|
|
@@ -14576,7 +14575,7 @@ app.post("/api/export/notion-page", async (c) => {
|
|
|
14576
14575
|
if (!doc || !parentPageId) {
|
|
14577
14576
|
return c.json({ error: "document and parentPageId required" }, 400);
|
|
14578
14577
|
}
|
|
14579
|
-
const notionSettingsPath =
|
|
14578
|
+
const notionSettingsPath = join6(DATA_DIR, "notion-settings.json");
|
|
14580
14579
|
let apiToken = "";
|
|
14581
14580
|
if (existsSync5(notionSettingsPath)) {
|
|
14582
14581
|
const settings = JSON.parse(readFileSync2(notionSettingsPath, "utf-8"));
|
|
@@ -14616,7 +14615,7 @@ app.post("/api/export/batch", async (c) => {
|
|
|
14616
14615
|
return p || null;
|
|
14617
14616
|
},
|
|
14618
14617
|
getFramesDir(projectId) {
|
|
14619
|
-
return
|
|
14618
|
+
return join6(FRAMES_DIR, projectId);
|
|
14620
14619
|
}
|
|
14621
14620
|
};
|
|
14622
14621
|
const result = await executeBatchExport(manifest, dataProvider, (progress) => {
|
|
@@ -14653,13 +14652,13 @@ app.get("/assets/*", async (c) => {
|
|
|
14653
14652
|
const assetPath = c.req.path.replace("/assets/", "");
|
|
14654
14653
|
const cwd = process.cwd();
|
|
14655
14654
|
const possiblePaths = [
|
|
14656
|
-
|
|
14655
|
+
join6(__dirname, "..", "assets", assetPath),
|
|
14657
14656
|
// Production: dist/../assets
|
|
14658
|
-
|
|
14657
|
+
join6(__dirname, "..", "..", "assets", assetPath),
|
|
14659
14658
|
// Alternative structure
|
|
14660
|
-
|
|
14659
|
+
join6(cwd, "assets", assetPath),
|
|
14661
14660
|
// Development: running from project root
|
|
14662
|
-
|
|
14661
|
+
join6(cwd, "src", "assets", assetPath)
|
|
14663
14662
|
// Development: src/assets
|
|
14664
14663
|
];
|
|
14665
14664
|
for (const path of possiblePaths) {
|
|
@@ -14701,9 +14700,9 @@ app.get("/api/file", async (c) => {
|
|
|
14701
14700
|
if (!existsSync5(resolvedPath)) {
|
|
14702
14701
|
return c.json({ error: "File not found" }, 404);
|
|
14703
14702
|
}
|
|
14704
|
-
const { readFileSync: readFileSync3, statSync:
|
|
14703
|
+
const { readFileSync: readFileSync3, statSync: statSync4 } = await import("fs");
|
|
14705
14704
|
const { basename: basename4, extname: extname3 } = await import("path");
|
|
14706
|
-
const stat =
|
|
14705
|
+
const stat = statSync4(resolvedPath);
|
|
14707
14706
|
if (stat.isDirectory()) {
|
|
14708
14707
|
return c.json({ error: "Path resolves to a directory" }, 400);
|
|
14709
14708
|
}
|
|
@@ -14746,10 +14745,10 @@ app.get("/api/files", async (c) => {
|
|
|
14746
14745
|
if (!existsSync5(decodedPath)) {
|
|
14747
14746
|
return c.json({ error: "Directory not found" }, 404);
|
|
14748
14747
|
}
|
|
14749
|
-
const { readdirSync:
|
|
14750
|
-
const files =
|
|
14748
|
+
const { readdirSync: readdirSync5, statSync: statSync4 } = await import("fs");
|
|
14749
|
+
const files = readdirSync5(decodedPath).filter((name) => {
|
|
14751
14750
|
try {
|
|
14752
|
-
return
|
|
14751
|
+
return statSync4(join6(decodedPath, name)).isFile();
|
|
14753
14752
|
} catch {
|
|
14754
14753
|
return false;
|
|
14755
14754
|
}
|
|
@@ -14865,7 +14864,7 @@ app.get("/api/integrations/jira-mcp/status", async (c) => {
|
|
|
14865
14864
|
const { execSync: execSync3 } = await import("child_process");
|
|
14866
14865
|
const { existsSync: existsSync6, readFileSync: readFileSync3 } = await import("fs");
|
|
14867
14866
|
const { homedir: homedir4 } = await import("os");
|
|
14868
|
-
const { join:
|
|
14867
|
+
const { join: join7 } = await import("path");
|
|
14869
14868
|
const claudeCliAvailable = isClaudeCliAvailable();
|
|
14870
14869
|
let configured = false;
|
|
14871
14870
|
let available = false;
|
|
@@ -14914,8 +14913,8 @@ app.get("/api/integrations/jira-mcp/status", async (c) => {
|
|
|
14914
14913
|
}
|
|
14915
14914
|
if (!configured) {
|
|
14916
14915
|
const settingsPaths = [
|
|
14917
|
-
|
|
14918
|
-
|
|
14916
|
+
join7(homedir4(), ".claude", "settings.json"),
|
|
14917
|
+
join7(homedir4(), ".claude", "settings.local.json")
|
|
14919
14918
|
];
|
|
14920
14919
|
for (const filePath of settingsPaths) {
|
|
14921
14920
|
if (!existsSync6(filePath)) continue;
|
|
@@ -15472,12 +15471,12 @@ app.post("/api/testing/mobile/device/tap", async (c) => {
|
|
|
15472
15471
|
await lock.acquired;
|
|
15473
15472
|
try {
|
|
15474
15473
|
const { mkdir, writeFile, rm } = await import("fs/promises");
|
|
15475
|
-
const { join:
|
|
15476
|
-
const tempBase =
|
|
15477
|
-
const flowPath =
|
|
15478
|
-
const outputDir =
|
|
15474
|
+
const { join: join7 } = await import("path");
|
|
15475
|
+
const tempBase = join7(tmpdir2(), "discoverylab-maestro-live");
|
|
15476
|
+
const flowPath = join7(tempBase, `tap-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.yaml`);
|
|
15477
|
+
const outputDir = join7(tempBase, `out-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
15479
15478
|
await mkdir(tempBase, { recursive: true });
|
|
15480
|
-
const maestroLogDir =
|
|
15479
|
+
const maestroLogDir = join7(homedir3(), "Library", "Logs", "maestro");
|
|
15481
15480
|
await mkdir(maestroLogDir, { recursive: true });
|
|
15482
15481
|
const flowLines = [];
|
|
15483
15482
|
let resolvedAppId = appId || session?.appId;
|
|
@@ -15581,8 +15580,8 @@ app.post("/api/testing/mobile/record/stop", async (c) => {
|
|
|
15581
15580
|
}
|
|
15582
15581
|
const session = await recorder.stopRecording();
|
|
15583
15582
|
const projectId = session.id;
|
|
15584
|
-
const { dirname: dirname3, join:
|
|
15585
|
-
const { readdirSync:
|
|
15583
|
+
const { dirname: dirname3, join: join7 } = await import("path");
|
|
15584
|
+
const { readdirSync: readdirSync5, writeFileSync: writeFileSync5, existsSync: existsSync6, readFileSync: readFileSync3 } = await import("fs");
|
|
15586
15585
|
const outputDir = dirname3(session.flowPath || session.screenshotsDir);
|
|
15587
15586
|
if (!session.appId && session.deviceId && session.platform) {
|
|
15588
15587
|
session.appId = getForegroundAppIdForPlatform(session.platform, session.deviceId) || void 0;
|
|
@@ -15591,10 +15590,10 @@ app.post("/api/testing/mobile/record/stop", async (c) => {
|
|
|
15591
15590
|
let screenshotCount = 0;
|
|
15592
15591
|
let screenshotFiles = [];
|
|
15593
15592
|
try {
|
|
15594
|
-
screenshotFiles =
|
|
15593
|
+
screenshotFiles = readdirSync5(session.screenshotsDir).filter((f) => f.endsWith(".png")).sort();
|
|
15595
15594
|
screenshotCount = screenshotFiles.length;
|
|
15596
15595
|
if (screenshotFiles.length > 0) {
|
|
15597
|
-
thumbnailPath =
|
|
15596
|
+
thumbnailPath = join7(session.screenshotsDir, screenshotFiles[0]);
|
|
15598
15597
|
}
|
|
15599
15598
|
} catch {
|
|
15600
15599
|
}
|
|
@@ -15614,7 +15613,7 @@ app.post("/api/testing/mobile/record/stop", async (c) => {
|
|
|
15614
15613
|
const actionsCount = actions.length;
|
|
15615
15614
|
const deviceShort = session.deviceName?.split(" ")[0] || session.platform?.toUpperCase() || "Mobile";
|
|
15616
15615
|
const projectName = actionsCount > 0 ? `${deviceShort} Test - ${actionsCount} actions - ${dateStr}` : `${deviceShort} Recording - ${dateStr}`;
|
|
15617
|
-
const flowPath =
|
|
15616
|
+
const flowPath = join7(outputDir, "test.yaml");
|
|
15618
15617
|
let flowContent = "";
|
|
15619
15618
|
if (existsSync6(flowPath)) {
|
|
15620
15619
|
try {
|
|
@@ -15804,9 +15803,9 @@ app.post("/api/projects/:id/retry-analysis", async (c) => {
|
|
|
15804
15803
|
const candidateScreenshotDirs = [];
|
|
15805
15804
|
if (projectPath && existsSync5(projectPath)) {
|
|
15806
15805
|
try {
|
|
15807
|
-
const projectStats =
|
|
15806
|
+
const projectStats = statSync3(projectPath);
|
|
15808
15807
|
if (projectStats.isDirectory()) {
|
|
15809
|
-
candidateScreenshotDirs.push(
|
|
15808
|
+
candidateScreenshotDirs.push(join6(projectPath, "screenshots"));
|
|
15810
15809
|
candidateScreenshotDirs.push(projectPath);
|
|
15811
15810
|
}
|
|
15812
15811
|
} catch (err) {
|
|
@@ -15818,7 +15817,7 @@ app.post("/api/projects/:id/retry-analysis", async (c) => {
|
|
|
15818
15817
|
for (const candidateDir of candidateScreenshotDirs) {
|
|
15819
15818
|
if (!existsSync5(candidateDir)) continue;
|
|
15820
15819
|
try {
|
|
15821
|
-
const pngs =
|
|
15820
|
+
const pngs = readdirSync4(candidateDir).filter((file) => file.toLowerCase().endsWith(".png")).sort();
|
|
15822
15821
|
if (pngs.length > 0) {
|
|
15823
15822
|
screenshotsDir = candidateDir;
|
|
15824
15823
|
screenshotFiles = pngs;
|
|
@@ -15850,7 +15849,7 @@ app.post("/api/projects/:id/retry-analysis", async (c) => {
|
|
|
15850
15849
|
}
|
|
15851
15850
|
if (projectPath && existsSync5(projectPath)) {
|
|
15852
15851
|
try {
|
|
15853
|
-
const projectStats =
|
|
15852
|
+
const projectStats = statSync3(projectPath);
|
|
15854
15853
|
if (projectStats.isDirectory()) {
|
|
15855
15854
|
console.log(`[RetryAnalysis] Restarting directory analysis for ${projectId}`);
|
|
15856
15855
|
runProjectAnalysisInBackgroundWithWatchdog(projectId, "RetryAnalysis(directory)");
|
|
@@ -15880,14 +15879,14 @@ app.post("/api/projects/:id/retry-analysis", async (c) => {
|
|
|
15880
15879
|
app.post("/api/testing/mobile/recordings/:id/analyze", async (c) => {
|
|
15881
15880
|
try {
|
|
15882
15881
|
const recordingId = c.req.param("id");
|
|
15883
|
-
const { readdirSync:
|
|
15884
|
-
const { join:
|
|
15885
|
-
const recordingDir =
|
|
15886
|
-
const screenshotsDir =
|
|
15882
|
+
const { readdirSync: readdirSync5, writeFileSync: writeFileSync5, existsSync: fsExistsSync } = await import("fs");
|
|
15883
|
+
const { join: join7 } = await import("path");
|
|
15884
|
+
const recordingDir = join7(PROJECTS_DIR, "maestro-recordings", recordingId);
|
|
15885
|
+
const screenshotsDir = join7(recordingDir, "screenshots");
|
|
15887
15886
|
if (!fsExistsSync(screenshotsDir)) {
|
|
15888
15887
|
return c.json({ error: "Screenshots directory not found" }, 404);
|
|
15889
15888
|
}
|
|
15890
|
-
const screenshotFiles =
|
|
15889
|
+
const screenshotFiles = readdirSync5(screenshotsDir).filter((f) => f.endsWith(".png")).sort();
|
|
15891
15890
|
if (screenshotFiles.length < 2) {
|
|
15892
15891
|
return c.json({ error: "Not enough screenshots for analysis (need at least 2)" }, 400);
|
|
15893
15892
|
}
|
|
@@ -15912,7 +15911,7 @@ app.post("/api/testing/mobile/recordings/:id/analyze", async (c) => {
|
|
|
15912
15911
|
analysisResult.appName,
|
|
15913
15912
|
analysisResult.actionDetectionProvider
|
|
15914
15913
|
);
|
|
15915
|
-
const flowPath =
|
|
15914
|
+
const flowPath = join7(recordingDir, "test.yaml");
|
|
15916
15915
|
writeFileSync5(flowPath, maestroYaml, "utf-8");
|
|
15917
15916
|
console.log(`[MobileRecording] AI detected ${analysisResult.actions.length} actions, YAML saved to ${flowPath}`);
|
|
15918
15917
|
return c.json({
|
|
@@ -15943,16 +15942,16 @@ app.post("/api/testing/playwright/codegen", async (c) => {
|
|
|
15943
15942
|
});
|
|
15944
15943
|
app.get("/api/testing/mobile/recordings", async (c) => {
|
|
15945
15944
|
try {
|
|
15946
|
-
const { readdirSync:
|
|
15947
|
-
const { join:
|
|
15948
|
-
const recordingsDir =
|
|
15945
|
+
const { readdirSync: readdirSync5, readFileSync: readFileSync3, statSync: statSync4 } = await import("fs");
|
|
15946
|
+
const { join: join7 } = await import("path");
|
|
15947
|
+
const recordingsDir = join7(PROJECTS_DIR, "maestro-recordings");
|
|
15949
15948
|
if (!existsSync5(recordingsDir)) {
|
|
15950
15949
|
return c.json({ recordings: [] });
|
|
15951
15950
|
}
|
|
15952
15951
|
const recordings = [];
|
|
15953
|
-
const dirs =
|
|
15952
|
+
const dirs = readdirSync5(recordingsDir);
|
|
15954
15953
|
for (const dir of dirs) {
|
|
15955
|
-
const sessionPath =
|
|
15954
|
+
const sessionPath = join7(recordingsDir, dir, "session.json");
|
|
15956
15955
|
if (existsSync5(sessionPath)) {
|
|
15957
15956
|
try {
|
|
15958
15957
|
const session = JSON.parse(readFileSync3(sessionPath, "utf-8"));
|
|
@@ -15984,9 +15983,9 @@ app.get("/api/testing/mobile/recordings/:id", async (c) => {
|
|
|
15984
15983
|
try {
|
|
15985
15984
|
const { id } = c.req.param();
|
|
15986
15985
|
const { readFileSync: readFileSync3 } = await import("fs");
|
|
15987
|
-
const { join:
|
|
15988
|
-
const sessionPath =
|
|
15989
|
-
const flowPath =
|
|
15986
|
+
const { join: join7 } = await import("path");
|
|
15987
|
+
const sessionPath = join7(PROJECTS_DIR, "maestro-recordings", id, "session.json");
|
|
15988
|
+
const flowPath = join7(PROJECTS_DIR, "maestro-recordings", id, "test.yaml");
|
|
15990
15989
|
if (!existsSync5(sessionPath)) {
|
|
15991
15990
|
return c.json({ error: "Recording not found" }, 404);
|
|
15992
15991
|
}
|
|
@@ -16663,8 +16662,8 @@ app.put("/api/testing/mobile/recordings/:id/flow", async (c) => {
|
|
|
16663
16662
|
const body = await c.req.json();
|
|
16664
16663
|
const { flowCode } = body;
|
|
16665
16664
|
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
16666
|
-
const { join:
|
|
16667
|
-
const flowPath =
|
|
16665
|
+
const { join: join7 } = await import("path");
|
|
16666
|
+
const flowPath = join7(PROJECTS_DIR, "maestro-recordings", id, "test.yaml");
|
|
16668
16667
|
writeFileSync5(flowPath, flowCode, "utf-8");
|
|
16669
16668
|
return c.json({ success: true });
|
|
16670
16669
|
} catch (error) {
|
|
@@ -16676,8 +16675,8 @@ app.delete("/api/testing/mobile/recordings/:id", async (c) => {
|
|
|
16676
16675
|
try {
|
|
16677
16676
|
const { id } = c.req.param();
|
|
16678
16677
|
const { rmSync: rmSync3 } = await import("fs");
|
|
16679
|
-
const { join:
|
|
16680
|
-
const recordingDir =
|
|
16678
|
+
const { join: join7 } = await import("path");
|
|
16679
|
+
const recordingDir = join7(PROJECTS_DIR, "maestro-recordings", id);
|
|
16681
16680
|
if (!existsSync5(recordingDir)) {
|
|
16682
16681
|
return c.json({ error: "Recording not found" }, 404);
|
|
16683
16682
|
}
|
|
@@ -16752,8 +16751,8 @@ app.post("/api/testing/mobile/recordings/:id/replay", async (c) => {
|
|
|
16752
16751
|
const requestedDevicePlatform = body?.devicePlatform === "ios" || body?.devicePlatform === "android" ? body.devicePlatform : void 0;
|
|
16753
16752
|
const skipDeviceValidation = body?.skipDeviceValidation === true;
|
|
16754
16753
|
const { readFileSync: readFileSync3 } = await import("fs");
|
|
16755
|
-
const { join:
|
|
16756
|
-
const flowPath =
|
|
16754
|
+
const { join: join7 } = await import("path");
|
|
16755
|
+
const flowPath = join7(PROJECTS_DIR, "maestro-recordings", id, "test.yaml");
|
|
16757
16756
|
if (!existsSync5(flowPath)) {
|
|
16758
16757
|
return c.json({ error: "Flow file not found" }, 404);
|
|
16759
16758
|
}
|
|
@@ -16891,7 +16890,7 @@ var autoCaptureScreenshotCount = 0;
|
|
|
16891
16890
|
app.post("/api/testing/mobile/auto-capture/start", async (c) => {
|
|
16892
16891
|
try {
|
|
16893
16892
|
const { recordingId, platform } = await c.req.json();
|
|
16894
|
-
const { join:
|
|
16893
|
+
const { join: join7 } = await import("path");
|
|
16895
16894
|
const { mkdirSync: mkdirSync5, existsSync: fsExistsSync, writeFileSync: writeFileSync5 } = await import("fs");
|
|
16896
16895
|
if (!recordingId) {
|
|
16897
16896
|
return c.json({ error: "recordingId required" }, 400);
|
|
@@ -16902,7 +16901,7 @@ app.post("/api/testing/mobile/auto-capture/start", async (c) => {
|
|
|
16902
16901
|
}
|
|
16903
16902
|
autoCaptureRecordingId = recordingId;
|
|
16904
16903
|
autoCaptureScreenshotCount = 0;
|
|
16905
|
-
const screenshotsDir =
|
|
16904
|
+
const screenshotsDir = join7(PROJECTS_DIR, "maestro-recordings", recordingId, "screenshots");
|
|
16906
16905
|
if (!fsExistsSync(screenshotsDir)) {
|
|
16907
16906
|
mkdirSync5(screenshotsDir, { recursive: true });
|
|
16908
16907
|
}
|
|
@@ -16910,7 +16909,7 @@ app.post("/api/testing/mobile/auto-capture/start", async (c) => {
|
|
|
16910
16909
|
const execCaptureAsync = promisify(exec);
|
|
16911
16910
|
autoCaptureInterval = setInterval(async () => {
|
|
16912
16911
|
try {
|
|
16913
|
-
const screenshotPath =
|
|
16912
|
+
const screenshotPath = join7(screenshotsDir, `auto_${Date.now()}_${autoCaptureScreenshotCount}.png`);
|
|
16914
16913
|
autoCaptureScreenshotCount++;
|
|
16915
16914
|
if (platform === "ios") {
|
|
16916
16915
|
await execCaptureAsync(`xcrun simctl io booted screenshot "${screenshotPath}"`, { timeout: 5e3 });
|
|
@@ -16944,8 +16943,8 @@ app.post("/api/testing/mobile/auto-capture/start", async (c) => {
|
|
|
16944
16943
|
app.post("/api/testing/mobile/auto-capture/stop", async (c) => {
|
|
16945
16944
|
try {
|
|
16946
16945
|
const { recordingId } = await c.req.json();
|
|
16947
|
-
const { join:
|
|
16948
|
-
const { readdirSync:
|
|
16946
|
+
const { join: join7 } = await import("path");
|
|
16947
|
+
const { readdirSync: readdirSync5, existsSync: fsExistsSync } = await import("fs");
|
|
16949
16948
|
if (autoCaptureInterval) {
|
|
16950
16949
|
clearInterval(autoCaptureInterval);
|
|
16951
16950
|
autoCaptureInterval = null;
|
|
@@ -16956,9 +16955,9 @@ app.post("/api/testing/mobile/auto-capture/stop", async (c) => {
|
|
|
16956
16955
|
autoCaptureScreenshotCount = 0;
|
|
16957
16956
|
let savedScreenshots = 0;
|
|
16958
16957
|
if (stoppedId) {
|
|
16959
|
-
const screenshotsDir =
|
|
16958
|
+
const screenshotsDir = join7(PROJECTS_DIR, "maestro-recordings", stoppedId, "screenshots");
|
|
16960
16959
|
if (fsExistsSync(screenshotsDir)) {
|
|
16961
|
-
savedScreenshots =
|
|
16960
|
+
savedScreenshots = readdirSync5(screenshotsDir).filter((f) => f.endsWith(".png")).length;
|
|
16962
16961
|
}
|
|
16963
16962
|
}
|
|
16964
16963
|
console.log(`[AutoCapture] Stopped for ${stoppedId || recordingId}, ${savedScreenshots} screenshots saved`);
|
|
@@ -17001,11 +17000,11 @@ function shellQuote(value) {
|
|
|
17001
17000
|
async function runClaudeCliWithArgs(prompt, args, timeoutMs) {
|
|
17002
17001
|
const { mkdtemp, writeFile, readFile, rm } = await import("fs/promises");
|
|
17003
17002
|
const { tmpdir: tmpdir3 } = await import("os");
|
|
17004
|
-
const { join:
|
|
17005
|
-
const tempDir = await mkdtemp(
|
|
17006
|
-
const promptPath =
|
|
17007
|
-
const stdoutPath =
|
|
17008
|
-
const stderrPath =
|
|
17003
|
+
const { join: join7 } = await import("path");
|
|
17004
|
+
const tempDir = await mkdtemp(join7(tmpdir3(), "claude-cli-"));
|
|
17005
|
+
const promptPath = join7(tempDir, "prompt.txt");
|
|
17006
|
+
const stdoutPath = join7(tempDir, "stdout.txt");
|
|
17007
|
+
const stderrPath = join7(tempDir, "stderr.txt");
|
|
17009
17008
|
await writeFile(promptPath, prompt, "utf8");
|
|
17010
17009
|
const quotedArgs = args.map((arg) => shellQuote(arg)).join(" ");
|
|
17011
17010
|
const shellScript = [
|
|
@@ -17557,9 +17556,9 @@ async function runOCRInBackground(projectId, screenshotsDir, screenshotFiles) {
|
|
|
17557
17556
|
};
|
|
17558
17557
|
try {
|
|
17559
17558
|
const { recognizeTextBatch } = await import("./ocr-FXRLEP66.js");
|
|
17560
|
-
const { join:
|
|
17559
|
+
const { join: join7 } = await import("path");
|
|
17561
17560
|
broadcastProgress("ocr", "running", `Processing 0/${screenshotFiles.length} screenshots...`, void 0, 0, screenshotFiles.length);
|
|
17562
|
-
const fullPaths = screenshotFiles.map((f) =>
|
|
17561
|
+
const fullPaths = screenshotFiles.map((f) => join7(screenshotsDir, f));
|
|
17563
17562
|
const ocrResult = await recognizeTextBatch(
|
|
17564
17563
|
fullPaths,
|
|
17565
17564
|
{ recognitionLevel: "accurate" },
|
|
@@ -17642,8 +17641,8 @@ async function runOCRInBackground(projectId, screenshotsDir, screenshotFiles) {
|
|
|
17642
17641
|
analysisResult.actionDetectionProvider
|
|
17643
17642
|
);
|
|
17644
17643
|
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
17645
|
-
const recordingDir2 =
|
|
17646
|
-
const flowPath =
|
|
17644
|
+
const recordingDir2 = join7(screenshotsDir, "..");
|
|
17645
|
+
const flowPath = join7(recordingDir2, "test.yaml");
|
|
17647
17646
|
writeFileSync5(flowPath, maestroYaml, "utf-8");
|
|
17648
17647
|
detectedActionsCount = analysisResult.actions.length;
|
|
17649
17648
|
console.log(`[BackgroundOCR] AI detected ${detectedActionsCount} actions, YAML saved to ${flowPath}`);
|
|
@@ -17664,10 +17663,10 @@ async function runOCRInBackground(projectId, screenshotsDir, screenshotFiles) {
|
|
|
17664
17663
|
} else {
|
|
17665
17664
|
broadcastProgress("actions", "skipped", "Not enough screenshots for action detection");
|
|
17666
17665
|
}
|
|
17667
|
-
const recordingDir =
|
|
17666
|
+
const recordingDir = join7(screenshotsDir, "..");
|
|
17668
17667
|
let iconCoverPath = null;
|
|
17669
17668
|
try {
|
|
17670
|
-
const sessionPath =
|
|
17669
|
+
const sessionPath = join7(recordingDir, "session.json");
|
|
17671
17670
|
if (existsSync5(sessionPath)) {
|
|
17672
17671
|
const sessionData = JSON.parse(readFileSync2(sessionPath, "utf8"));
|
|
17673
17672
|
if ((sessionData?.platform === "ios" || sessionData?.platform === "android") && typeof sessionData?.deviceId === "string") {
|
|
@@ -18363,8 +18362,8 @@ User: ${truncateForPrompt(message, 800)}`;
|
|
|
18363
18362
|
executionStarted = true;
|
|
18364
18363
|
const flowYaml = generateChatMaestroYaml(commands, appIdForFlow);
|
|
18365
18364
|
const { writeFile, rm } = await import("fs/promises");
|
|
18366
|
-
const { join:
|
|
18367
|
-
const flowPath =
|
|
18365
|
+
const { join: join7 } = await import("path");
|
|
18366
|
+
const flowPath = join7(
|
|
18368
18367
|
PROJECTS_DIR,
|
|
18369
18368
|
`temp-chat-flow-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.yaml`
|
|
18370
18369
|
);
|
|
@@ -18514,7 +18513,7 @@ function sanitizeAppLabNetworkProxySettings(value) {
|
|
|
18514
18513
|
};
|
|
18515
18514
|
}
|
|
18516
18515
|
function getAppLabNetworkProxySettingsPath() {
|
|
18517
|
-
return
|
|
18516
|
+
return join6(DATA_DIR, "network-proxy-settings.json");
|
|
18518
18517
|
}
|
|
18519
18518
|
function resolveAppLabNetworkProxyTimeoutMs() {
|
|
18520
18519
|
const raw = typeof process.env.DISCOVERYLAB_NETWORK_PROXY_MAX_DURATION_MS === "string" ? process.env.DISCOVERYLAB_NETWORK_PROXY_MAX_DURATION_MS.trim() : "";
|
|
@@ -18589,8 +18588,8 @@ var llmSettings = {};
|
|
|
18589
18588
|
(async () => {
|
|
18590
18589
|
try {
|
|
18591
18590
|
const { readFileSync: readFileSync3, existsSync: existsSync6 } = await import("fs");
|
|
18592
|
-
const { join:
|
|
18593
|
-
const settingsPath =
|
|
18591
|
+
const { join: join7 } = await import("path");
|
|
18592
|
+
const settingsPath = join7(DATA_DIR, "llm-settings.json");
|
|
18594
18593
|
if (existsSync6(settingsPath)) {
|
|
18595
18594
|
llmSettings = JSON.parse(readFileSync3(settingsPath, "utf8"));
|
|
18596
18595
|
if (llmSettings.anthropicApiKey) process.env.ANTHROPIC_API_KEY = llmSettings.anthropicApiKey;
|
|
@@ -18618,7 +18617,7 @@ app.get("/api/settings/llm", async (c) => {
|
|
|
18618
18617
|
app.put("/api/settings/llm", async (c) => {
|
|
18619
18618
|
try {
|
|
18620
18619
|
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
18621
|
-
const { join:
|
|
18620
|
+
const { join: join7 } = await import("path");
|
|
18622
18621
|
const body = await c.req.json();
|
|
18623
18622
|
if (body.anthropicApiKey && !body.anthropicApiKey.startsWith("\u2022\u2022")) {
|
|
18624
18623
|
llmSettings.anthropicApiKey = body.anthropicApiKey;
|
|
@@ -18652,7 +18651,7 @@ app.put("/api/settings/llm", async (c) => {
|
|
|
18652
18651
|
if (body.preferredProvider !== void 0) {
|
|
18653
18652
|
llmSettings.preferredProvider = body.preferredProvider;
|
|
18654
18653
|
}
|
|
18655
|
-
const settingsPath =
|
|
18654
|
+
const settingsPath = join7(DATA_DIR, "llm-settings.json");
|
|
18656
18655
|
writeFileSync5(settingsPath, JSON.stringify(llmSettings, null, 2));
|
|
18657
18656
|
return c.json({ success: true, message: "LLM settings saved" });
|
|
18658
18657
|
} catch (error) {
|
|
@@ -18664,8 +18663,8 @@ var jiraSettings = {};
|
|
|
18664
18663
|
(async () => {
|
|
18665
18664
|
try {
|
|
18666
18665
|
const { readFileSync: readFileSync3, existsSync: existsSync6 } = await import("fs");
|
|
18667
|
-
const { join:
|
|
18668
|
-
const settingsPath =
|
|
18666
|
+
const { join: join7 } = await import("path");
|
|
18667
|
+
const settingsPath = join7(DATA_DIR, "jira-settings.json");
|
|
18669
18668
|
if (existsSync6(settingsPath)) {
|
|
18670
18669
|
jiraSettings = JSON.parse(readFileSync3(settingsPath, "utf8"));
|
|
18671
18670
|
if (jiraSettings.baseUrl) process.env.JIRA_BASE_URL = jiraSettings.baseUrl;
|
|
@@ -18688,7 +18687,7 @@ app.get("/api/settings/jira", async (c) => {
|
|
|
18688
18687
|
app.put("/api/settings/jira", async (c) => {
|
|
18689
18688
|
try {
|
|
18690
18689
|
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
18691
|
-
const { join:
|
|
18690
|
+
const { join: join7 } = await import("path");
|
|
18692
18691
|
const body = await c.req.json();
|
|
18693
18692
|
if (body.baseUrl !== void 0) {
|
|
18694
18693
|
jiraSettings.baseUrl = body.baseUrl;
|
|
@@ -18702,7 +18701,7 @@ app.put("/api/settings/jira", async (c) => {
|
|
|
18702
18701
|
jiraSettings.apiToken = body.apiToken;
|
|
18703
18702
|
process.env.JIRA_API_TOKEN = body.apiToken;
|
|
18704
18703
|
}
|
|
18705
|
-
const settingsPath =
|
|
18704
|
+
const settingsPath = join7(DATA_DIR, "jira-settings.json");
|
|
18706
18705
|
writeFileSync5(settingsPath, JSON.stringify(jiraSettings, null, 2));
|
|
18707
18706
|
return c.json({ success: true, message: "Jira settings saved" });
|
|
18708
18707
|
} catch (error) {
|
|
@@ -18713,7 +18712,7 @@ app.put("/api/settings/jira", async (c) => {
|
|
|
18713
18712
|
var notionSettings = {};
|
|
18714
18713
|
(async () => {
|
|
18715
18714
|
try {
|
|
18716
|
-
const settingsPath =
|
|
18715
|
+
const settingsPath = join6(DATA_DIR, "notion-settings.json");
|
|
18717
18716
|
if (existsSync5(settingsPath)) {
|
|
18718
18717
|
notionSettings = JSON.parse(readFileSync2(settingsPath, "utf8"));
|
|
18719
18718
|
console.log("[Notion Settings] Loaded from file");
|
|
@@ -18740,7 +18739,7 @@ app.put("/api/settings/notion", async (c) => {
|
|
|
18740
18739
|
notionSettings.lastParentPageId = body.lastParentPageId;
|
|
18741
18740
|
notionSettings.lastParentPageTitle = body.lastParentPageTitle || "";
|
|
18742
18741
|
}
|
|
18743
|
-
const settingsPath =
|
|
18742
|
+
const settingsPath = join6(DATA_DIR, "notion-settings.json");
|
|
18744
18743
|
writeFileSync4(settingsPath, JSON.stringify(notionSettings, null, 2));
|
|
18745
18744
|
return c.json({ success: true });
|
|
18746
18745
|
} catch (error) {
|
|
@@ -19038,13 +19037,13 @@ app.post("/api/recorder/stop", async (c) => {
|
|
|
19038
19037
|
let projectId = null;
|
|
19039
19038
|
let ocrInProgress = false;
|
|
19040
19039
|
try {
|
|
19041
|
-
const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync:
|
|
19042
|
-
const { join:
|
|
19040
|
+
const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync: readdirSync5, copyFileSync: copyFileSync2, mkdirSync: mkdirSync5 } = await import("fs");
|
|
19041
|
+
const { join: join7 } = await import("path");
|
|
19043
19042
|
const { homedir: homedir4 } = await import("os");
|
|
19044
19043
|
const recordingId = session?.id;
|
|
19045
19044
|
if (recordingId) {
|
|
19046
|
-
const recordingDir =
|
|
19047
|
-
const sessionPath =
|
|
19045
|
+
const recordingDir = join7(homedir4(), ".discoverylab", "recordings", recordingId);
|
|
19046
|
+
const sessionPath = join7(recordingDir, "session.json");
|
|
19048
19047
|
if (existsSync6(sessionPath)) {
|
|
19049
19048
|
const sessionData = JSON.parse(readFileSync3(sessionPath, "utf8"));
|
|
19050
19049
|
const sessionName = sessionData?.name || sessionData?.session?.name || `Recording ${recordingId}`;
|
|
@@ -19060,19 +19059,19 @@ app.post("/api/recorder/stop", async (c) => {
|
|
|
19060
19059
|
INSERT INTO projects (id, name, video_path, platform, status, created_at, updated_at)
|
|
19061
19060
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
19062
19061
|
`).run(projectId, sessionName, recordingDir, "web", "ready", now, now);
|
|
19063
|
-
const framesDir =
|
|
19062
|
+
const framesDir = join7(DATA_DIR, "projects", projectId, "frames");
|
|
19064
19063
|
mkdirSync5(framesDir, { recursive: true });
|
|
19065
|
-
const screenshotsDir =
|
|
19064
|
+
const screenshotsDir = join7(recordingDir, "screenshots");
|
|
19066
19065
|
let thumbnailPath = null;
|
|
19067
19066
|
let frameCount = 0;
|
|
19068
19067
|
const screenshotFiles = [];
|
|
19069
19068
|
if (existsSync6(screenshotsDir)) {
|
|
19070
|
-
const screenshots =
|
|
19069
|
+
const screenshots = readdirSync5(screenshotsDir).filter((f) => f.endsWith(".png")).sort();
|
|
19071
19070
|
frameCount = screenshots.length;
|
|
19072
19071
|
screenshotFiles.push(...screenshots);
|
|
19073
19072
|
screenshots.forEach((file, index) => {
|
|
19074
|
-
const src =
|
|
19075
|
-
const dest =
|
|
19073
|
+
const src = join7(screenshotsDir, file);
|
|
19074
|
+
const dest = join7(framesDir, `frame_${(index + 1).toString().padStart(4, "0")}.png`);
|
|
19076
19075
|
copyFileSync2(src, dest);
|
|
19077
19076
|
if (index === 0) {
|
|
19078
19077
|
thumbnailPath = dest;
|
|
@@ -19164,15 +19163,15 @@ app.post("/api/recorder/screenshot", async (c) => {
|
|
|
19164
19163
|
});
|
|
19165
19164
|
app.get("/api/recorder/recordings", async (c) => {
|
|
19166
19165
|
try {
|
|
19167
|
-
const { readdirSync:
|
|
19168
|
-
const { join:
|
|
19166
|
+
const { readdirSync: readdirSync5, readFileSync: readFileSync3, existsSync: existsSync6 } = await import("fs");
|
|
19167
|
+
const { join: join7 } = await import("path");
|
|
19169
19168
|
const { homedir: homedir4 } = await import("os");
|
|
19170
|
-
const recordingsDir =
|
|
19169
|
+
const recordingsDir = join7(homedir4(), ".discoverylab", "recordings");
|
|
19171
19170
|
if (!existsSync6(recordingsDir)) {
|
|
19172
19171
|
return c.json({ recordings: [] });
|
|
19173
19172
|
}
|
|
19174
|
-
const recordings =
|
|
19175
|
-
const sessionPath =
|
|
19173
|
+
const recordings = readdirSync5(recordingsDir).filter((dir) => dir.startsWith("rec_")).map((dir) => {
|
|
19174
|
+
const sessionPath = join7(recordingsDir, dir, "session.json");
|
|
19176
19175
|
if (existsSync6(sessionPath)) {
|
|
19177
19176
|
try {
|
|
19178
19177
|
const session = JSON.parse(readFileSync3(sessionPath, "utf8"));
|
|
@@ -19192,18 +19191,18 @@ app.get("/api/recorder/recordings", async (c) => {
|
|
|
19192
19191
|
app.get("/api/recorder/recordings/:id", async (c) => {
|
|
19193
19192
|
try {
|
|
19194
19193
|
const { id } = c.req.param();
|
|
19195
|
-
const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync:
|
|
19196
|
-
const { join:
|
|
19194
|
+
const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync: readdirSync5 } = await import("fs");
|
|
19195
|
+
const { join: join7 } = await import("path");
|
|
19197
19196
|
const { homedir: homedir4 } = await import("os");
|
|
19198
|
-
const recordingDir =
|
|
19199
|
-
const sessionPath =
|
|
19197
|
+
const recordingDir = join7(homedir4(), ".discoverylab", "recordings", id);
|
|
19198
|
+
const sessionPath = join7(recordingDir, "session.json");
|
|
19200
19199
|
if (!existsSync6(sessionPath)) {
|
|
19201
19200
|
return c.json({ error: "Recording not found" }, 404);
|
|
19202
19201
|
}
|
|
19203
19202
|
const session = JSON.parse(readFileSync3(sessionPath, "utf8"));
|
|
19204
|
-
const screenshotsDir =
|
|
19205
|
-
const screenshots = existsSync6(screenshotsDir) ?
|
|
19206
|
-
const specPath =
|
|
19203
|
+
const screenshotsDir = join7(recordingDir, "screenshots");
|
|
19204
|
+
const screenshots = existsSync6(screenshotsDir) ? readdirSync5(screenshotsDir).filter((f) => f.endsWith(".png")) : [];
|
|
19205
|
+
const specPath = join7(recordingDir, "test.spec.ts");
|
|
19207
19206
|
const specContent = existsSync6(specPath) ? readFileSync3(specPath, "utf8") : null;
|
|
19208
19207
|
return c.json({
|
|
19209
19208
|
session,
|
|
@@ -19221,13 +19220,13 @@ app.put("/api/recorder/recordings/:id/spec", async (c) => {
|
|
|
19221
19220
|
const body = await c.req.json();
|
|
19222
19221
|
const specCode = typeof body?.specCode === "string" ? body.specCode : "";
|
|
19223
19222
|
const { writeFileSync: writeFileSync5, existsSync: fsExistsSync, mkdirSync: mkdirSync5 } = await import("fs");
|
|
19224
|
-
const { join:
|
|
19223
|
+
const { join: join7 } = await import("path");
|
|
19225
19224
|
const { homedir: homedir4 } = await import("os");
|
|
19226
|
-
const recordingDir =
|
|
19225
|
+
const recordingDir = join7(homedir4(), ".discoverylab", "recordings", id);
|
|
19227
19226
|
if (!fsExistsSync(recordingDir)) {
|
|
19228
19227
|
return c.json({ error: "Recording not found" }, 404);
|
|
19229
19228
|
}
|
|
19230
|
-
const specPath =
|
|
19229
|
+
const specPath = join7(recordingDir, "test.spec.ts");
|
|
19231
19230
|
mkdirSync5(recordingDir, { recursive: true });
|
|
19232
19231
|
writeFileSync5(specPath, specCode, "utf8");
|
|
19233
19232
|
const placeholders = parseScriptPlaceholders(specCode);
|
|
@@ -19245,10 +19244,10 @@ app.post("/api/recorder/recordings/:id/run", async (c) => {
|
|
|
19245
19244
|
const timeout = Number.isFinite(Number(body?.timeout)) ? Number(body.timeout) : void 0;
|
|
19246
19245
|
const browser = body?.browser === "firefox" || body?.browser === "webkit" || body?.browser === "chromium" ? body.browser : void 0;
|
|
19247
19246
|
const { readFileSync: readFileSync3, existsSync: fsExistsSync, mkdirSync: mkdirSync5, writeFileSync: writeFileSync5 } = await import("fs");
|
|
19248
|
-
const { join:
|
|
19247
|
+
const { join: join7 } = await import("path");
|
|
19249
19248
|
const { homedir: homedir4 } = await import("os");
|
|
19250
|
-
const recordingDir =
|
|
19251
|
-
const specPath =
|
|
19249
|
+
const recordingDir = join7(homedir4(), ".discoverylab", "recordings", id);
|
|
19250
|
+
const specPath = join7(recordingDir, "test.spec.ts");
|
|
19252
19251
|
if (!fsExistsSync(recordingDir) || !fsExistsSync(specPath)) {
|
|
19253
19252
|
return c.json({ error: "Recording spec not found" }, 404);
|
|
19254
19253
|
}
|
|
@@ -19269,11 +19268,11 @@ app.post("/api/recorder/recordings/:id/run", async (c) => {
|
|
|
19269
19268
|
envTest: resolved.envTestText
|
|
19270
19269
|
}, 400);
|
|
19271
19270
|
}
|
|
19272
|
-
const runtimeDir =
|
|
19271
|
+
const runtimeDir = join7(recordingDir, ".runtime");
|
|
19273
19272
|
mkdirSync5(runtimeDir, { recursive: true });
|
|
19274
19273
|
const runStamp = Date.now();
|
|
19275
|
-
const runtimeSpecPath =
|
|
19276
|
-
const runtimeEnvPath =
|
|
19274
|
+
const runtimeSpecPath = join7(runtimeDir, `test.runtime.${runStamp}.spec.ts`);
|
|
19275
|
+
const runtimeEnvPath = join7(runtimeDir, `.env.test.${runStamp}`);
|
|
19277
19276
|
writeFileSync5(runtimeSpecPath, applied.code, "utf8");
|
|
19278
19277
|
writeFileSync5(runtimeEnvPath, resolved.envTestText || "", "utf8");
|
|
19279
19278
|
const result = await runPlaywrightTest({
|
|
@@ -19289,7 +19288,7 @@ app.post("/api/recorder/recordings/:id/run", async (c) => {
|
|
|
19289
19288
|
screenshot: body?.screenshot === "on" || body?.screenshot === "only-on-failure" ? body.screenshot : "only-on-failure",
|
|
19290
19289
|
trace: body?.trace === "on" || body?.trace === "retain-on-failure" ? body.trace : "retain-on-failure"
|
|
19291
19290
|
},
|
|
19292
|
-
outputDir:
|
|
19291
|
+
outputDir: join7(recordingDir, "playwright-runs", String(runStamp)),
|
|
19293
19292
|
reporter: "json"
|
|
19294
19293
|
});
|
|
19295
19294
|
return c.json({
|
|
@@ -19310,9 +19309,9 @@ app.delete("/api/recorder/recordings/:id", async (c) => {
|
|
|
19310
19309
|
try {
|
|
19311
19310
|
const { id } = c.req.param();
|
|
19312
19311
|
const { rmSync: rmSync3, existsSync: fsExistsSync } = await import("fs");
|
|
19313
|
-
const { join:
|
|
19312
|
+
const { join: join7 } = await import("path");
|
|
19314
19313
|
const { homedir: homedir4 } = await import("os");
|
|
19315
|
-
const recordingDir =
|
|
19314
|
+
const recordingDir = join7(homedir4(), ".discoverylab", "recordings", id);
|
|
19316
19315
|
if (!fsExistsSync(recordingDir)) {
|
|
19317
19316
|
return c.json({ error: "Recording not found" }, 404);
|
|
19318
19317
|
}
|
|
@@ -19322,7 +19321,7 @@ app.delete("/api/recorder/recordings/:id", async (c) => {
|
|
|
19322
19321
|
await db.delete(projects).where(eq(projects.id, id));
|
|
19323
19322
|
await deleteTestVariablesForOwner("web-recording", id);
|
|
19324
19323
|
await deleteTestVariablesForOwner("project", id);
|
|
19325
|
-
const projectDir =
|
|
19324
|
+
const projectDir = join7(PROJECTS_DIR, id);
|
|
19326
19325
|
if (fsExistsSync(projectDir)) {
|
|
19327
19326
|
rmSync3(projectDir, { recursive: true, force: true });
|
|
19328
19327
|
}
|
|
@@ -19335,23 +19334,23 @@ app.delete("/api/recorder/recordings/:id", async (c) => {
|
|
|
19335
19334
|
});
|
|
19336
19335
|
app.delete("/api/recorder/recordings", async (c) => {
|
|
19337
19336
|
try {
|
|
19338
|
-
const { rmSync: rmSync3, existsSync: fsExistsSync, readdirSync:
|
|
19339
|
-
const { join:
|
|
19337
|
+
const { rmSync: rmSync3, existsSync: fsExistsSync, readdirSync: readdirSync5 } = await import("fs");
|
|
19338
|
+
const { join: join7 } = await import("path");
|
|
19340
19339
|
const { homedir: homedir4 } = await import("os");
|
|
19341
19340
|
const deletedIds = [];
|
|
19342
|
-
const recordingsDir =
|
|
19341
|
+
const recordingsDir = join7(homedir4(), ".discoverylab", "recordings");
|
|
19343
19342
|
if (fsExistsSync(recordingsDir)) {
|
|
19344
|
-
const dirs =
|
|
19343
|
+
const dirs = readdirSync5(recordingsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
19345
19344
|
for (const dir of dirs) {
|
|
19346
|
-
rmSync3(
|
|
19345
|
+
rmSync3(join7(recordingsDir, dir), { recursive: true, force: true });
|
|
19347
19346
|
deletedIds.push(dir);
|
|
19348
19347
|
}
|
|
19349
19348
|
}
|
|
19350
|
-
const mobileRecordingsDir =
|
|
19349
|
+
const mobileRecordingsDir = join7(PROJECTS_DIR, "maestro-recordings");
|
|
19351
19350
|
if (fsExistsSync(mobileRecordingsDir)) {
|
|
19352
|
-
const dirs =
|
|
19351
|
+
const dirs = readdirSync5(mobileRecordingsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
19353
19352
|
for (const dir of dirs) {
|
|
19354
|
-
rmSync3(
|
|
19353
|
+
rmSync3(join7(mobileRecordingsDir, dir), { recursive: true, force: true });
|
|
19355
19354
|
deletedIds.push(dir);
|
|
19356
19355
|
}
|
|
19357
19356
|
}
|
|
@@ -19363,7 +19362,7 @@ app.delete("/api/recorder/recordings", async (c) => {
|
|
|
19363
19362
|
await deleteTestVariablesForOwner("web-recording", id);
|
|
19364
19363
|
await deleteTestVariablesForOwner("mobile-recording", id);
|
|
19365
19364
|
await deleteTestVariablesForOwner("project", id);
|
|
19366
|
-
const projectDir =
|
|
19365
|
+
const projectDir = join7(PROJECTS_DIR, id);
|
|
19367
19366
|
if (fsExistsSync(projectDir)) {
|
|
19368
19367
|
rmSync3(projectDir, { recursive: true, force: true });
|
|
19369
19368
|
}
|
|
@@ -19380,9 +19379,9 @@ app.get("/api/recorder/screenshots/:sessionId/:filename", async (c) => {
|
|
|
19380
19379
|
try {
|
|
19381
19380
|
const { sessionId, filename } = c.req.param();
|
|
19382
19381
|
const { readFileSync: readFileSync3, existsSync: existsSync6 } = await import("fs");
|
|
19383
|
-
const { join:
|
|
19382
|
+
const { join: join7 } = await import("path");
|
|
19384
19383
|
const { homedir: homedir4 } = await import("os");
|
|
19385
|
-
const filePath =
|
|
19384
|
+
const filePath = join7(homedir4(), ".discoverylab", "recordings", sessionId, "screenshots", filename);
|
|
19386
19385
|
if (!existsSync6(filePath)) {
|
|
19387
19386
|
return c.json({ error: "Screenshot not found" }, 404);
|
|
19388
19387
|
}
|
|
@@ -19398,16 +19397,16 @@ app.get("/api/recorder/screenshots/:sessionId/:filename", async (c) => {
|
|
|
19398
19397
|
app.get("/api/recorder/screenshot/:filename", async (c) => {
|
|
19399
19398
|
try {
|
|
19400
19399
|
const { filename } = c.req.param();
|
|
19401
|
-
const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync:
|
|
19402
|
-
const { join:
|
|
19400
|
+
const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync: readdirSync5 } = await import("fs");
|
|
19401
|
+
const { join: join7 } = await import("path");
|
|
19403
19402
|
const { homedir: homedir4 } = await import("os");
|
|
19404
|
-
const recordingsDir =
|
|
19403
|
+
const recordingsDir = join7(homedir4(), ".discoverylab", "recordings");
|
|
19405
19404
|
if (!existsSync6(recordingsDir)) {
|
|
19406
19405
|
return c.json({ error: "No recordings directory" }, 404);
|
|
19407
19406
|
}
|
|
19408
|
-
const sessions =
|
|
19407
|
+
const sessions = readdirSync5(recordingsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
19409
19408
|
for (const sessionId of sessions) {
|
|
19410
|
-
const filePath =
|
|
19409
|
+
const filePath = join7(recordingsDir, sessionId, "screenshots", filename);
|
|
19411
19410
|
if (existsSync6(filePath)) {
|
|
19412
19411
|
const buffer = readFileSync3(filePath);
|
|
19413
19412
|
return new Response(buffer, {
|
|
@@ -19427,11 +19426,11 @@ app.get("/api/recorder/screenshot/:filename", async (c) => {
|
|
|
19427
19426
|
app.post("/api/recorder/recordings/:id/create-project", async (c) => {
|
|
19428
19427
|
try {
|
|
19429
19428
|
const { id } = c.req.param();
|
|
19430
|
-
const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync:
|
|
19431
|
-
const { join:
|
|
19429
|
+
const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync: readdirSync5, copyFileSync: copyFileSync2, mkdirSync: mkdirSync5 } = await import("fs");
|
|
19430
|
+
const { join: join7, basename: basename4 } = await import("path");
|
|
19432
19431
|
const { homedir: homedir4 } = await import("os");
|
|
19433
|
-
const recordingDir =
|
|
19434
|
-
const sessionPath =
|
|
19432
|
+
const recordingDir = join7(homedir4(), ".discoverylab", "recordings", id);
|
|
19433
|
+
const sessionPath = join7(recordingDir, "session.json");
|
|
19435
19434
|
if (!existsSync6(sessionPath)) {
|
|
19436
19435
|
return c.json({ error: "Recording not found" }, 404);
|
|
19437
19436
|
}
|
|
@@ -19457,17 +19456,17 @@ app.post("/api/recorder/recordings/:id/create-project", async (c) => {
|
|
|
19457
19456
|
now,
|
|
19458
19457
|
now
|
|
19459
19458
|
);
|
|
19460
|
-
const framesDir =
|
|
19459
|
+
const framesDir = join7(DATA_DIR, "projects", projectId, "frames");
|
|
19461
19460
|
mkdirSync5(framesDir, { recursive: true });
|
|
19462
|
-
const screenshotsDir =
|
|
19461
|
+
const screenshotsDir = join7(recordingDir, "screenshots");
|
|
19463
19462
|
let thumbnailPath = null;
|
|
19464
19463
|
let frameCount = 0;
|
|
19465
19464
|
if (existsSync6(screenshotsDir)) {
|
|
19466
|
-
const screenshots =
|
|
19465
|
+
const screenshots = readdirSync5(screenshotsDir).filter((f) => f.endsWith(".png")).sort();
|
|
19467
19466
|
frameCount = screenshots.length;
|
|
19468
19467
|
screenshots.forEach((file, index) => {
|
|
19469
|
-
const src =
|
|
19470
|
-
const dest =
|
|
19468
|
+
const src = join7(screenshotsDir, file);
|
|
19469
|
+
const dest = join7(framesDir, `frame_${(index + 1).toString().padStart(4, "0")}.png`);
|
|
19471
19470
|
copyFileSync2(src, dest);
|
|
19472
19471
|
if (index === 0) {
|
|
19473
19472
|
thumbnailPath = dest;
|
|
@@ -19499,7 +19498,7 @@ app.post("/api/recorder/recordings/:id/create-project", async (c) => {
|
|
|
19499
19498
|
}
|
|
19500
19499
|
let ocrInProgress = false;
|
|
19501
19500
|
if (frameCount > 0 && existsSync6(screenshotsDir)) {
|
|
19502
|
-
const screenshotFiles =
|
|
19501
|
+
const screenshotFiles = readdirSync5(screenshotsDir).filter((f) => f.endsWith(".png")).sort();
|
|
19503
19502
|
if (screenshotFiles.length > 0) {
|
|
19504
19503
|
sqlite.prepare(`UPDATE projects SET status = ?, updated_at = ? WHERE id = ?`).run("analyzing", Date.now(), projectId);
|
|
19505
19504
|
ocrInProgress = true;
|
|
@@ -19858,11 +19857,11 @@ app.put("/api/projects/:id/template-content", async (c) => {
|
|
|
19858
19857
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19859
19858
|
};
|
|
19860
19859
|
const { writeFileSync: writeFileSync5, mkdirSync: mkdirSync5 } = await import("fs");
|
|
19861
|
-
const projectDir =
|
|
19860
|
+
const projectDir = join6(PROJECTS_DIR, id);
|
|
19862
19861
|
if (!existsSync5(projectDir)) {
|
|
19863
19862
|
mkdirSync5(projectDir, { recursive: true });
|
|
19864
19863
|
}
|
|
19865
|
-
const contentPath =
|
|
19864
|
+
const contentPath = join6(projectDir, "template-content.json");
|
|
19866
19865
|
writeFileSync5(contentPath, JSON.stringify(savedContent));
|
|
19867
19866
|
return c.json({ success: true, message: "Template content saved", content: savedContent });
|
|
19868
19867
|
} catch (error) {
|
|
@@ -19940,7 +19939,7 @@ app.get("/api/templates/player/*", async (c) => {
|
|
|
19940
19939
|
return c.json({ error: "Templates not installed" }, 404);
|
|
19941
19940
|
}
|
|
19942
19941
|
const requestedPath = c.req.path.replace("/api/templates/player/", "");
|
|
19943
|
-
const filePath =
|
|
19942
|
+
const filePath = join6(bundlePath, requestedPath || "index.html");
|
|
19944
19943
|
if (!existsSync5(filePath)) {
|
|
19945
19944
|
return c.json({ error: "File not found" }, 404);
|
|
19946
19945
|
}
|
|
@@ -20044,7 +20043,6 @@ var TEMPLATE_MAX_DURATION_SECONDS = 60;
|
|
|
20044
20043
|
var DEFAULT_ANDROID_DEVICE_MOCKUP = "mockup-android-galaxy.png";
|
|
20045
20044
|
var ANDROID_DEVICE_MOCKUP_FALLBACKS = [
|
|
20046
20045
|
DEFAULT_ANDROID_DEVICE_MOCKUP,
|
|
20047
|
-
"mockup-android.png",
|
|
20048
20046
|
"mockup-android-google-pixel-9-pro.png"
|
|
20049
20047
|
];
|
|
20050
20048
|
function getTemplateProjectState(project) {
|
|
@@ -20076,7 +20074,7 @@ function resolveTemplateVideoPath(project) {
|
|
|
20076
20074
|
return null;
|
|
20077
20075
|
}
|
|
20078
20076
|
try {
|
|
20079
|
-
if (
|
|
20077
|
+
if (statSync3(resolvedVideoPath).isDirectory()) {
|
|
20080
20078
|
return null;
|
|
20081
20079
|
}
|
|
20082
20080
|
} catch {
|
|
@@ -20194,11 +20192,11 @@ function listAndroidDeviceMockupIds() {
|
|
|
20194
20192
|
if (!bundlePath) {
|
|
20195
20193
|
return [...ANDROID_DEVICE_MOCKUP_FALLBACKS];
|
|
20196
20194
|
}
|
|
20197
|
-
const publicDir =
|
|
20195
|
+
const publicDir = join6(bundlePath, "public");
|
|
20198
20196
|
if (!existsSync5(publicDir)) {
|
|
20199
20197
|
return [...ANDROID_DEVICE_MOCKUP_FALLBACKS];
|
|
20200
20198
|
}
|
|
20201
|
-
const files =
|
|
20199
|
+
const files = readdirSync4(publicDir).filter((file) => /^mockup-android.*\.png$/i.test(file));
|
|
20202
20200
|
const unique = new Set(files.length > 0 ? files : ANDROID_DEVICE_MOCKUP_FALLBACKS);
|
|
20203
20201
|
return [...unique].sort((left, right) => {
|
|
20204
20202
|
const leftIndex = ANDROID_DEVICE_MOCKUP_FALLBACKS.indexOf(left);
|
|
@@ -20358,7 +20356,7 @@ function groupNetworkIntoTabs(entries) {
|
|
|
20358
20356
|
});
|
|
20359
20357
|
}
|
|
20360
20358
|
function loadEditedTemplateContent(projectId) {
|
|
20361
|
-
const contentPath =
|
|
20359
|
+
const contentPath = join6(PROJECTS_DIR, projectId, "template-content.json");
|
|
20362
20360
|
if (!existsSync5(contentPath)) return null;
|
|
20363
20361
|
try {
|
|
20364
20362
|
const raw = readFileSync2(contentPath, "utf-8");
|
|
@@ -20377,7 +20375,7 @@ function loadEditedTemplateContent(projectId) {
|
|
|
20377
20375
|
function loadProjectNetworkData(project) {
|
|
20378
20376
|
const recordingDir = resolveRecordingBaseDir(project.videoPath);
|
|
20379
20377
|
if (recordingDir) {
|
|
20380
|
-
const sessionPath =
|
|
20378
|
+
const sessionPath = join6(recordingDir, "session.json");
|
|
20381
20379
|
if (existsSync5(sessionPath)) {
|
|
20382
20380
|
try {
|
|
20383
20381
|
const session = JSON.parse(readFileSync2(sessionPath, "utf-8"));
|