@veolab/discoverylab 1.3.2 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/dist/chunk-7EDIUVIO.js +4304 -0
- package/dist/{chunk-H4FYBUX6.js → chunk-AHVBE25Y.js} +23 -17
- package/dist/{chunk-2RQ7BDPA.js → chunk-BE7BFMYC.js} +280 -56
- package/dist/chunk-HGWEHWKJ.js +94 -0
- package/dist/{chunk-N6JJ2RGV.js → chunk-ZLHIHMSL.js} +1 -1
- package/dist/cli.js +26 -26
- package/dist/{esvp-GSISVXLC.js → esvp-KVOWYW6G.js} +2 -1
- package/dist/{esvp-mobile-GC7MAGMI.js → esvp-mobile-GZ5EMYPG.js} +3 -2
- package/dist/index.d.ts +13 -17
- package/dist/index.html +149 -29
- package/dist/index.js +6 -6
- package/dist/{server-RBJ2VROA.js → server-QKZXPZRC.js} +5 -5
- package/dist/templates/bundle/bundle.js +8 -4
- package/dist/templates/bundle/public/mockup-android-galaxy.png +0 -0
- package/dist/{tools-EYWRLTRB.js → tools-YGM5HRIB.js} +4 -4
- package/package.json +2 -2
- package/dist/chunk-GAKEFJ5T.js +0 -481
- package/dist/chunk-VEIZLLCI.js +0 -1696
|
@@ -4,20 +4,9 @@ import {
|
|
|
4
4
|
createTextResult
|
|
5
5
|
} from "./chunk-XKX6NBHF.js";
|
|
6
6
|
import {
|
|
7
|
-
createLoginFlow,
|
|
8
|
-
createNavigationTestFlow,
|
|
9
|
-
createOnboardingFlow,
|
|
10
|
-
generateMaestroFlow,
|
|
11
|
-
getAdbCommand,
|
|
12
7
|
getAvailableTemplates,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
isTemplatesInstalled,
|
|
16
|
-
listMaestroDevices,
|
|
17
|
-
runMaestroTest,
|
|
18
|
-
runMaestroWithCapture,
|
|
19
|
-
startMaestroStudio
|
|
20
|
-
} from "./chunk-VEIZLLCI.js";
|
|
8
|
+
isTemplatesInstalled
|
|
9
|
+
} from "./chunk-HGWEHWKJ.js";
|
|
21
10
|
import {
|
|
22
11
|
createFormSubmissionScript,
|
|
23
12
|
createLoginScript,
|
|
@@ -35,12 +24,18 @@ import {
|
|
|
35
24
|
buildAppLabNetworkProfile
|
|
36
25
|
} from "./chunk-LB3RNE3O.js";
|
|
37
26
|
import {
|
|
27
|
+
LOCAL_ESVP_SERVER_URL,
|
|
38
28
|
attachESVPNetworkTrace,
|
|
39
29
|
captureESVPCheckpoint,
|
|
40
30
|
clearESVPNetwork,
|
|
41
31
|
configureESVPNetwork,
|
|
42
32
|
createESVPSession,
|
|
33
|
+
createLoginFlow,
|
|
34
|
+
createNavigationTestFlow,
|
|
35
|
+
createOnboardingFlow,
|
|
43
36
|
finishESVPSession,
|
|
37
|
+
generateMaestroFlow,
|
|
38
|
+
getAdbCommand,
|
|
44
39
|
getESVPArtifactContent,
|
|
45
40
|
getESVPConnection,
|
|
46
41
|
getESVPHealth,
|
|
@@ -48,15 +43,21 @@ import {
|
|
|
48
43
|
getESVPSession,
|
|
49
44
|
getESVPSessionNetwork,
|
|
50
45
|
getESVPTranscript,
|
|
46
|
+
getMaestroVersion,
|
|
51
47
|
inspectESVPSession,
|
|
48
|
+
isMaestroInstalled,
|
|
52
49
|
listESVPArtifacts,
|
|
53
50
|
listESVPDevices,
|
|
54
51
|
listESVPSessions,
|
|
52
|
+
listMaestroDevices,
|
|
55
53
|
replayESVPSession,
|
|
56
54
|
runESVPActions,
|
|
57
55
|
runESVPPreflight,
|
|
56
|
+
runMaestroTest,
|
|
57
|
+
runMaestroWithCapture,
|
|
58
|
+
startMaestroStudio,
|
|
58
59
|
validateESVPReplay
|
|
59
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-7EDIUVIO.js";
|
|
60
61
|
import {
|
|
61
62
|
DATA_DIR,
|
|
62
63
|
EXPORTS_DIR,
|
|
@@ -6452,9 +6453,14 @@ function resolveProjectRecordingReplaySessionId(session) {
|
|
|
6452
6453
|
}
|
|
6453
6454
|
function resolveProjectRecordingESVPServerUrl(session) {
|
|
6454
6455
|
const esvp = resolveProjectRecordingESVPState(session);
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6456
|
+
const serverUrl = typeof esvp?.serverUrl === "string" ? esvp.serverUrl.trim().replace(/\/+$/, "") : "";
|
|
6457
|
+
if (serverUrl === LOCAL_ESVP_SERVER_URL) {
|
|
6458
|
+
return LOCAL_ESVP_SERVER_URL;
|
|
6459
|
+
}
|
|
6460
|
+
if (!serverUrl && esvp?.connectionMode === "local") {
|
|
6461
|
+
return LOCAL_ESVP_SERVER_URL;
|
|
6462
|
+
}
|
|
6463
|
+
return void 0;
|
|
6458
6464
|
}
|
|
6459
6465
|
function resolveAppLabBaseUrl(appLabUrl) {
|
|
6460
6466
|
const raw = String(appLabUrl || process.env.DISCOVERYLAB_APP_URL || "http://127.0.0.1:3847").trim();
|
|
@@ -1,25 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
findAndroidSdkPath,
|
|
3
|
-
getAdbCommand,
|
|
4
2
|
getAvailableTemplates,
|
|
5
3
|
getBundlePath,
|
|
6
|
-
getEmulatorPath,
|
|
7
|
-
getMaestroRecorder,
|
|
8
4
|
getTemplate,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
isTemplatesInstalled,
|
|
12
|
-
killZombieMaestroProcesses,
|
|
13
|
-
listConnectedAndroidDevices,
|
|
14
|
-
listMaestroDevices,
|
|
15
|
-
parseMaestroActionsFromYaml,
|
|
16
|
-
resolveAndroidDeviceSerial,
|
|
17
|
-
runMaestroTest,
|
|
18
|
-
tapViaIdb
|
|
19
|
-
} from "./chunk-VEIZLLCI.js";
|
|
20
|
-
import {
|
|
21
|
-
APP_VERSION
|
|
22
|
-
} from "./chunk-6EGBXRDK.js";
|
|
5
|
+
isTemplatesInstalled
|
|
6
|
+
} from "./chunk-HGWEHWKJ.js";
|
|
23
7
|
import {
|
|
24
8
|
runPlaywrightTest
|
|
25
9
|
} from "./chunk-FIL7IWEL.js";
|
|
@@ -37,20 +21,37 @@ import {
|
|
|
37
21
|
resolveLocalAppHttpTraceCollectorById,
|
|
38
22
|
startLocalAppHttpTraceCollector,
|
|
39
23
|
validateMaestroRecordingWithESVP
|
|
40
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-ZLHIHMSL.js";
|
|
41
25
|
import {
|
|
42
26
|
buildAppLabNetworkProfile
|
|
43
27
|
} from "./chunk-LB3RNE3O.js";
|
|
44
28
|
import {
|
|
29
|
+
LOCAL_ESVP_SERVER_URL,
|
|
45
30
|
attachESVPNetworkTrace,
|
|
46
31
|
configureESVPNetwork,
|
|
47
32
|
createESVPSession,
|
|
33
|
+
findAndroidSdkPath,
|
|
34
|
+
getAdbCommand,
|
|
48
35
|
getESVPReplayConsistency,
|
|
36
|
+
getEmulatorPath,
|
|
37
|
+
getMaestroRecorder,
|
|
49
38
|
inspectESVPSession,
|
|
39
|
+
isIdbInstalled,
|
|
40
|
+
isMaestroInstalled,
|
|
41
|
+
killZombieMaestroProcesses,
|
|
42
|
+
listConnectedAndroidDevices,
|
|
43
|
+
listMaestroDevices,
|
|
44
|
+
parseMaestroActionsFromYaml,
|
|
50
45
|
replayESVPSession,
|
|
46
|
+
resolveAndroidDeviceSerial,
|
|
51
47
|
runESVPActions,
|
|
48
|
+
runMaestroTest,
|
|
49
|
+
tapViaIdb,
|
|
52
50
|
validateESVPReplay
|
|
53
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-7EDIUVIO.js";
|
|
52
|
+
import {
|
|
53
|
+
APP_VERSION
|
|
54
|
+
} from "./chunk-6EGBXRDK.js";
|
|
54
55
|
import {
|
|
55
56
|
redactQuotedStringsInText,
|
|
56
57
|
redactSensitiveTestInput
|
|
@@ -239,7 +240,9 @@ async function startRender(projectId, templateId, props, onProgress) {
|
|
|
239
240
|
}
|
|
240
241
|
async function renderAsync(job, bundlePath, templateId, compositionId, props, onProgress) {
|
|
241
242
|
job.status = "rendering";
|
|
243
|
+
const originalCwd = process.cwd();
|
|
242
244
|
try {
|
|
245
|
+
process.chdir(DATA_DIR);
|
|
243
246
|
const { selectComposition, renderMedia } = await import("@remotion/renderer");
|
|
244
247
|
const realVideoDuration = getVideoDuration(props.videoUrl);
|
|
245
248
|
const optimizedProps = optimizeTemplatePropsForRender(templateId, props, realVideoDuration);
|
|
@@ -278,6 +281,8 @@ async function renderAsync(job, bundlePath, templateId, compositionId, props, on
|
|
|
278
281
|
job.error = err.message;
|
|
279
282
|
job.completedAt = Date.now();
|
|
280
283
|
throw err;
|
|
284
|
+
} finally {
|
|
285
|
+
process.chdir(originalCwd);
|
|
281
286
|
}
|
|
282
287
|
}
|
|
283
288
|
function getVideoDuration(videoUrl) {
|
|
@@ -4589,9 +4594,20 @@ function resolveProjectESVPSessionId(esvp) {
|
|
|
4589
4594
|
return validationId || networkId || directId || null;
|
|
4590
4595
|
}
|
|
4591
4596
|
function resolveProjectESVPServerUrl(esvp) {
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4597
|
+
return normalizePersistedLocalESVPServerUrl(esvp?.serverUrl, esvp?.connectionMode);
|
|
4598
|
+
}
|
|
4599
|
+
function normalizePersistedLocalESVPServerUrl(serverUrl, connectionMode) {
|
|
4600
|
+
const normalized = typeof serverUrl === "string" ? serverUrl.trim().replace(/\/+$/, "") : "";
|
|
4601
|
+
if (normalized === LOCAL_ESVP_SERVER_URL) {
|
|
4602
|
+
return LOCAL_ESVP_SERVER_URL;
|
|
4603
|
+
}
|
|
4604
|
+
if (!normalized && connectionMode === "local") {
|
|
4605
|
+
return LOCAL_ESVP_SERVER_URL;
|
|
4606
|
+
}
|
|
4607
|
+
return void 0;
|
|
4608
|
+
}
|
|
4609
|
+
function resolvePersistedLocalESVPServerUrl(serverUrl, esvp) {
|
|
4610
|
+
return normalizePersistedLocalESVPServerUrl(serverUrl, "local") || resolveProjectESVPServerUrl(esvp) || LOCAL_ESVP_SERVER_URL;
|
|
4595
4611
|
}
|
|
4596
4612
|
function isESVPReplayValidationSupported(result) {
|
|
4597
4613
|
if (!result) return true;
|
|
@@ -6433,7 +6449,7 @@ app.post("/api/testing/mobile/recordings/:id/esvp/validate", async (c) => {
|
|
|
6433
6449
|
let autoSynced = false;
|
|
6434
6450
|
if (result.networkEntries.length === 0 && result.supported && result.sourceSessionId && ((result.networkState?.managed_proxy?.entry_count ?? 0) > 0 || (result.networkState?.trace_count ?? 0) > 0)) {
|
|
6435
6451
|
try {
|
|
6436
|
-
const { collectESVPSessionNetworkData: collectESVPSessionNetworkData2 } = await import("./esvp-mobile-
|
|
6452
|
+
const { collectESVPSessionNetworkData: collectESVPSessionNetworkData2 } = await import("./esvp-mobile-GZ5EMYPG.js");
|
|
6437
6453
|
const deferred = await collectESVPSessionNetworkData2(result.sourceSessionId, serverUrl);
|
|
6438
6454
|
if (deferred.networkEntries.length > 0) {
|
|
6439
6455
|
session.networkEntries = deferred.networkEntries;
|
|
@@ -6517,7 +6533,7 @@ app.post("/api/testing/mobile/recordings/:id/esvp/replay", async (c) => {
|
|
|
6517
6533
|
session.esvp = {
|
|
6518
6534
|
...esvp || {},
|
|
6519
6535
|
currentSessionId: sourceSessionId,
|
|
6520
|
-
serverUrl:
|
|
6536
|
+
serverUrl: resolvePersistedLocalESVPServerUrl(serverUrl, esvp),
|
|
6521
6537
|
executor,
|
|
6522
6538
|
validation: {
|
|
6523
6539
|
...existingValidation,
|
|
@@ -6660,7 +6676,10 @@ app.post("/api/testing/mobile/recordings/:id/esvp/network/start", async (c) => {
|
|
|
6660
6676
|
...session.esvp && typeof session.esvp === "object" ? session.esvp : {},
|
|
6661
6677
|
currentSessionId: sourceSessionId,
|
|
6662
6678
|
connectionMode: typeof session?.esvp?.connectionMode === "string" ? session.esvp.connectionMode : "local",
|
|
6663
|
-
serverUrl:
|
|
6679
|
+
serverUrl: resolvePersistedLocalESVPServerUrl(
|
|
6680
|
+
serverUrl,
|
|
6681
|
+
session?.esvp && typeof session.esvp === "object" ? session.esvp : null
|
|
6682
|
+
),
|
|
6664
6683
|
executor,
|
|
6665
6684
|
network: {
|
|
6666
6685
|
...existingNetwork || {},
|
|
@@ -9938,11 +9957,11 @@ app.post("/api/templates/props", async (c) => {
|
|
|
9938
9957
|
if (!project) {
|
|
9939
9958
|
return c.json({ error: "Project not found" }, 404);
|
|
9940
9959
|
}
|
|
9941
|
-
const
|
|
9942
|
-
if (!
|
|
9960
|
+
const templateState = getTemplateProjectState(project);
|
|
9961
|
+
if (!templateState) {
|
|
9943
9962
|
return c.json({ error: "Project has no video to render" }, 400);
|
|
9944
9963
|
}
|
|
9945
|
-
return c.json(
|
|
9964
|
+
return c.json(templateState);
|
|
9946
9965
|
} catch (error) {
|
|
9947
9966
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
9948
9967
|
return c.json({ error: message }, 500);
|
|
@@ -10000,26 +10019,39 @@ app.put("/api/projects/:id/template-content", async (c) => {
|
|
|
10000
10019
|
try {
|
|
10001
10020
|
const id = c.req.param("id");
|
|
10002
10021
|
const body = await c.req.json();
|
|
10003
|
-
const { title, titleLines, terminalTabs, showcaseMode } = body;
|
|
10022
|
+
const { title, titleLines, terminalTabs, showcaseMode, deviceMockup } = body;
|
|
10004
10023
|
const db = getDatabase();
|
|
10005
10024
|
const project = db.select().from(projects).where(eq(projects.id, id)).get();
|
|
10006
10025
|
if (!project) {
|
|
10007
10026
|
return c.json({ error: "Project not found" }, 404);
|
|
10008
10027
|
}
|
|
10028
|
+
const defaultTitle = sanitizeTemplateTitle(
|
|
10029
|
+
extractFirstSentence(project.aiSummary || project.name || "App Recording"),
|
|
10030
|
+
"App Recording"
|
|
10031
|
+
);
|
|
10032
|
+
const sanitizedTitle = sanitizeTemplateTitle(title, defaultTitle);
|
|
10033
|
+
const sanitizedTitleLines = sanitizeTemplateTitleLines(titleLines, sanitizedTitle);
|
|
10034
|
+
const sanitizedTerminalTabs = sanitizeTemplateTerminalTabs(terminalTabs);
|
|
10035
|
+
const sanitizedShowcaseMode = showcaseMode === "artistic" || showcaseMode === "terminal" ? showcaseMode : void 0;
|
|
10036
|
+
const platform = project.platform === "ios" || project.platform === "android" || project.platform === "web" ? project.platform : "web";
|
|
10037
|
+
const availableAndroidMockups = listAndroidDeviceMockupIds();
|
|
10038
|
+
const sanitizedDeviceMockup = platform === "android" ? resolveAndroidDeviceMockup(deviceMockup, availableAndroidMockups) : void 0;
|
|
10039
|
+
const savedContent = {
|
|
10040
|
+
title: sanitizedTitle,
|
|
10041
|
+
titleLines: sanitizedTitleLines,
|
|
10042
|
+
terminalTabs: sanitizedTerminalTabs,
|
|
10043
|
+
showcaseMode: sanitizedShowcaseMode,
|
|
10044
|
+
deviceMockup: sanitizedDeviceMockup,
|
|
10045
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10046
|
+
};
|
|
10009
10047
|
const { writeFileSync: writeFileSync5, mkdirSync: mkdirSync6 } = await import("fs");
|
|
10010
10048
|
const projectDir = join5(PROJECTS_DIR, id);
|
|
10011
10049
|
if (!existsSync5(projectDir)) {
|
|
10012
10050
|
mkdirSync6(projectDir, { recursive: true });
|
|
10013
10051
|
}
|
|
10014
10052
|
const contentPath = join5(projectDir, "template-content.json");
|
|
10015
|
-
writeFileSync5(contentPath, JSON.stringify(
|
|
10016
|
-
|
|
10017
|
-
titleLines,
|
|
10018
|
-
terminalTabs,
|
|
10019
|
-
showcaseMode,
|
|
10020
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10021
|
-
}));
|
|
10022
|
-
return c.json({ success: true, message: "Template content saved" });
|
|
10053
|
+
writeFileSync5(contentPath, JSON.stringify(savedContent));
|
|
10054
|
+
return c.json({ success: true, message: "Template content saved", content: savedContent });
|
|
10023
10055
|
} catch (error) {
|
|
10024
10056
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
10025
10057
|
return c.json({ error: message }, 500);
|
|
@@ -10040,10 +10072,14 @@ app.post("/api/templates/render", async (c) => {
|
|
|
10040
10072
|
if (!project) {
|
|
10041
10073
|
return c.json({ error: "Project not found" }, 404);
|
|
10042
10074
|
}
|
|
10043
|
-
const
|
|
10044
|
-
if (!
|
|
10075
|
+
const templateState = getTemplateProjectState(project);
|
|
10076
|
+
if (!templateState) {
|
|
10045
10077
|
return c.json({ error: "Project has no video to render" }, 400);
|
|
10046
10078
|
}
|
|
10079
|
+
if (!templateState.eligibility.templatesAllowed) {
|
|
10080
|
+
return c.json({ error: templateState.eligibility.reason || `Templates are limited to videos up to ${TEMPLATE_MAX_DURATION_SECONDS} seconds.` }, 400);
|
|
10081
|
+
}
|
|
10082
|
+
const { props } = templateState;
|
|
10047
10083
|
const job = await startRender(projectId, templateId, props, (progress) => {
|
|
10048
10084
|
broadcastToClients({
|
|
10049
10085
|
type: "templateRenderProgress",
|
|
@@ -10139,29 +10175,27 @@ app.get("/api/settings/template-preference", async (c) => {
|
|
|
10139
10175
|
}
|
|
10140
10176
|
});
|
|
10141
10177
|
function assembleTemplateProps(project) {
|
|
10142
|
-
const resolvedVideoPath =
|
|
10143
|
-
if (!resolvedVideoPath
|
|
10178
|
+
const resolvedVideoPath = resolveTemplateVideoPath(project);
|
|
10179
|
+
if (!resolvedVideoPath) {
|
|
10144
10180
|
return null;
|
|
10145
10181
|
}
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
} catch {
|
|
10151
|
-
return null;
|
|
10152
|
-
}
|
|
10153
|
-
const defaultTitle = extractFirstSentence(project.aiSummary || project.name || "App Recording");
|
|
10182
|
+
const defaultTitle = sanitizeTemplateTitle(
|
|
10183
|
+
extractFirstSentence(project.aiSummary || project.name || "App Recording"),
|
|
10184
|
+
"App Recording"
|
|
10185
|
+
);
|
|
10154
10186
|
const subtitle = project.name || void 0;
|
|
10155
10187
|
const editedContent = loadEditedTemplateContent(project.id);
|
|
10188
|
+
const availableAndroidMockups = listAndroidDeviceMockupIds();
|
|
10156
10189
|
let title = defaultTitle;
|
|
10157
10190
|
let titleLines;
|
|
10158
10191
|
let terminalTabs = [];
|
|
10159
10192
|
let hasNetworkData = false;
|
|
10160
10193
|
let showcaseMode;
|
|
10194
|
+
let deviceMockup;
|
|
10161
10195
|
if (editedContent) {
|
|
10162
|
-
title = editedContent.title || defaultTitle;
|
|
10163
|
-
titleLines = editedContent.titleLines;
|
|
10164
|
-
terminalTabs = editedContent.terminalTabs
|
|
10196
|
+
title = sanitizeTemplateTitle(editedContent.title || defaultTitle, defaultTitle);
|
|
10197
|
+
titleLines = sanitizeTemplateTitleLines(editedContent.titleLines, title);
|
|
10198
|
+
terminalTabs = sanitizeTemplateTerminalTabs(editedContent.terminalTabs);
|
|
10165
10199
|
hasNetworkData = terminalTabs.length > 0;
|
|
10166
10200
|
showcaseMode = editedContent.showcaseMode;
|
|
10167
10201
|
} else {
|
|
@@ -10171,8 +10205,14 @@ function assembleTemplateProps(project) {
|
|
|
10171
10205
|
terminalTabs = groupNetworkIntoTabs(networkEntries);
|
|
10172
10206
|
}
|
|
10173
10207
|
}
|
|
10174
|
-
const videoDuration = project.duration || 0;
|
|
10175
10208
|
const platform = project.platform === "ios" || project.platform === "android" || project.platform === "web" ? project.platform : "web";
|
|
10209
|
+
if (platform === "android") {
|
|
10210
|
+
deviceMockup = resolveAndroidDeviceMockup(editedContent?.deviceMockup, availableAndroidMockups);
|
|
10211
|
+
}
|
|
10212
|
+
if (!titleLines && (!showcaseMode || showcaseMode === "artistic") && !hasNetworkData) {
|
|
10213
|
+
titleLines = splitTemplateTitleIntoLines(title);
|
|
10214
|
+
}
|
|
10215
|
+
const videoDuration = getActualTemplateVideoDuration(project, resolvedVideoPath);
|
|
10176
10216
|
const videoUrl = `http://localhost:${currentServerPort}/api/file?path=${encodeURIComponent(resolvedVideoPath)}`;
|
|
10177
10217
|
return {
|
|
10178
10218
|
videoUrl,
|
|
@@ -10183,7 +10223,32 @@ function assembleTemplateProps(project) {
|
|
|
10183
10223
|
subtitle,
|
|
10184
10224
|
terminalTabs,
|
|
10185
10225
|
hasNetworkData,
|
|
10186
|
-
showcaseMode
|
|
10226
|
+
showcaseMode,
|
|
10227
|
+
deviceMockup
|
|
10228
|
+
};
|
|
10229
|
+
}
|
|
10230
|
+
var TEMPLATE_MAX_DURATION_SECONDS = 60;
|
|
10231
|
+
var DEFAULT_ANDROID_DEVICE_MOCKUP = "mockup-android-galaxy.png";
|
|
10232
|
+
var ANDROID_DEVICE_MOCKUP_FALLBACKS = [
|
|
10233
|
+
DEFAULT_ANDROID_DEVICE_MOCKUP,
|
|
10234
|
+
"mockup-android.png",
|
|
10235
|
+
"mockup-android-google-pixel-9-pro.png"
|
|
10236
|
+
];
|
|
10237
|
+
function getTemplateProjectState(project) {
|
|
10238
|
+
const props = assembleTemplateProps(project);
|
|
10239
|
+
if (!props) {
|
|
10240
|
+
return null;
|
|
10241
|
+
}
|
|
10242
|
+
const eligibility = buildTemplateEligibility(props.videoDuration);
|
|
10243
|
+
const androidDeviceMockups = listAndroidDeviceMockupIds().map((id) => ({
|
|
10244
|
+
id,
|
|
10245
|
+
label: formatAndroidDeviceMockupLabel(id)
|
|
10246
|
+
}));
|
|
10247
|
+
return {
|
|
10248
|
+
props,
|
|
10249
|
+
eligibility,
|
|
10250
|
+
androidDeviceMockups,
|
|
10251
|
+
defaultAndroidDeviceMockup: resolveAndroidDeviceMockup(void 0, androidDeviceMockups.map((option) => option.id))
|
|
10187
10252
|
};
|
|
10188
10253
|
}
|
|
10189
10254
|
function extractFirstSentence(text) {
|
|
@@ -10192,6 +10257,164 @@ function extractFirstSentence(text) {
|
|
|
10192
10257
|
if (match) return match[0].trim();
|
|
10193
10258
|
return text.substring(0, 80).trim();
|
|
10194
10259
|
}
|
|
10260
|
+
function resolveTemplateVideoPath(project) {
|
|
10261
|
+
const resolvedVideoPath = resolveVideoPath(project.videoPath);
|
|
10262
|
+
if (!resolvedVideoPath || !existsSync5(resolvedVideoPath)) {
|
|
10263
|
+
return null;
|
|
10264
|
+
}
|
|
10265
|
+
try {
|
|
10266
|
+
if (statSync2(resolvedVideoPath).isDirectory()) {
|
|
10267
|
+
return null;
|
|
10268
|
+
}
|
|
10269
|
+
} catch {
|
|
10270
|
+
return null;
|
|
10271
|
+
}
|
|
10272
|
+
return resolvedVideoPath;
|
|
10273
|
+
}
|
|
10274
|
+
function getActualTemplateVideoDuration(project, resolvedVideoPath) {
|
|
10275
|
+
const probedDuration = probeVideoDurationSeconds(resolvedVideoPath);
|
|
10276
|
+
if (probedDuration && probedDuration > 0) {
|
|
10277
|
+
return probedDuration;
|
|
10278
|
+
}
|
|
10279
|
+
const projectDuration = Number(project?.duration);
|
|
10280
|
+
if (Number.isFinite(projectDuration) && projectDuration > 0) {
|
|
10281
|
+
return projectDuration;
|
|
10282
|
+
}
|
|
10283
|
+
return 0;
|
|
10284
|
+
}
|
|
10285
|
+
function probeVideoDurationSeconds(filePath) {
|
|
10286
|
+
try {
|
|
10287
|
+
if (!existsSync5(filePath)) return null;
|
|
10288
|
+
const output = execSync3(
|
|
10289
|
+
`ffprobe -v quiet -print_format json -show_format "${filePath}"`,
|
|
10290
|
+
{ encoding: "utf-8", timeout: 1e4 }
|
|
10291
|
+
);
|
|
10292
|
+
const data = JSON.parse(output);
|
|
10293
|
+
const duration = parseFloat(data?.format?.duration || "0");
|
|
10294
|
+
return Number.isFinite(duration) && duration > 0 ? duration : null;
|
|
10295
|
+
} catch {
|
|
10296
|
+
return null;
|
|
10297
|
+
}
|
|
10298
|
+
}
|
|
10299
|
+
function buildTemplateEligibility(actualDurationSeconds) {
|
|
10300
|
+
const templatesAllowed = isTemplateDurationAllowed(actualDurationSeconds);
|
|
10301
|
+
return {
|
|
10302
|
+
templatesAllowed,
|
|
10303
|
+
maxTemplateDurationSeconds: TEMPLATE_MAX_DURATION_SECONDS,
|
|
10304
|
+
actualDurationSeconds,
|
|
10305
|
+
reason: templatesAllowed || actualDurationSeconds <= 0 ? void 0 : `Templates are limited to videos up to ${TEMPLATE_MAX_DURATION_SECONDS} seconds. This recording is ${formatTemplateDuration(actualDurationSeconds)}.`
|
|
10306
|
+
};
|
|
10307
|
+
}
|
|
10308
|
+
function isTemplateDurationAllowed(actualDurationSeconds) {
|
|
10309
|
+
if (!Number.isFinite(actualDurationSeconds) || actualDurationSeconds <= 0) {
|
|
10310
|
+
return true;
|
|
10311
|
+
}
|
|
10312
|
+
return Math.round(actualDurationSeconds * 1e3) <= TEMPLATE_MAX_DURATION_SECONDS * 1e3;
|
|
10313
|
+
}
|
|
10314
|
+
function formatTemplateDuration(actualDurationSeconds) {
|
|
10315
|
+
if (!Number.isFinite(actualDurationSeconds) || actualDurationSeconds <= 0) {
|
|
10316
|
+
return "unknown duration";
|
|
10317
|
+
}
|
|
10318
|
+
const totalSeconds = Math.max(1, Math.round(actualDurationSeconds));
|
|
10319
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
10320
|
+
const seconds = totalSeconds % 60;
|
|
10321
|
+
return minutes > 0 ? `${minutes}m ${seconds}s` : `${totalSeconds}s`;
|
|
10322
|
+
}
|
|
10323
|
+
function sanitizeTemplateTitle(value, fallback = "App Recording") {
|
|
10324
|
+
const normalized = normalizeTemplateTitle(value);
|
|
10325
|
+
if (normalized) return normalized;
|
|
10326
|
+
const safeFallback = normalizeTemplateTitle(fallback);
|
|
10327
|
+
return safeFallback || "App Recording";
|
|
10328
|
+
}
|
|
10329
|
+
function normalizeTemplateTitle(value) {
|
|
10330
|
+
if (typeof value !== "string") return "";
|
|
10331
|
+
const withoutMarkdown = value.replace(/[#*_`~>\-[\]{}()<>\\/|]+/g, " ").replace(/&/g, " and ").replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
10332
|
+
if (!withoutMarkdown) return "";
|
|
10333
|
+
const limitedWords = withoutMarkdown.split(" ").filter(Boolean).slice(0, 7).join(" ");
|
|
10334
|
+
if (!limitedWords) return "";
|
|
10335
|
+
if (limitedWords.length <= 48) return limitedWords;
|
|
10336
|
+
const truncated = limitedWords.slice(0, 48).trim();
|
|
10337
|
+
return truncated.replace(/\s+\S*$/, "").trim() || truncated;
|
|
10338
|
+
}
|
|
10339
|
+
function sanitizeTemplateTitleLines(value, fallbackTitle) {
|
|
10340
|
+
if (!Array.isArray(value)) {
|
|
10341
|
+
return void 0;
|
|
10342
|
+
}
|
|
10343
|
+
const cleaned = value.map((line) => sanitizeTemplateTitle(line, "")).filter(Boolean).slice(0, 4);
|
|
10344
|
+
if (cleaned.length > 0) {
|
|
10345
|
+
return cleaned;
|
|
10346
|
+
}
|
|
10347
|
+
return splitTemplateTitleIntoLines(fallbackTitle);
|
|
10348
|
+
}
|
|
10349
|
+
function splitTemplateTitleIntoLines(title) {
|
|
10350
|
+
const words = title.split(" ").filter(Boolean);
|
|
10351
|
+
if (words.length === 0) return void 0;
|
|
10352
|
+
if (words.length <= 2) return [title];
|
|
10353
|
+
const lines = [];
|
|
10354
|
+
for (let index = 0; index < words.length && lines.length < 4; index += 2) {
|
|
10355
|
+
lines.push(words.slice(index, index + 2).join(" "));
|
|
10356
|
+
}
|
|
10357
|
+
return lines;
|
|
10358
|
+
}
|
|
10359
|
+
function sanitizeTemplateTerminalTabs(value) {
|
|
10360
|
+
if (!Array.isArray(value)) return [];
|
|
10361
|
+
return value.map((tab) => {
|
|
10362
|
+
if (!tab || typeof tab !== "object") return null;
|
|
10363
|
+
const record = tab;
|
|
10364
|
+
const label = typeof record.label === "string" ? record.label.trim() : "";
|
|
10365
|
+
const methodFromLabel = label.split(" ")[0] || "GET";
|
|
10366
|
+
const routeFromLabel = label.split(" ").slice(1).join(" ") || "/";
|
|
10367
|
+
const method = typeof record.method === "string" && record.method.trim() ? record.method.trim().toUpperCase() : methodFromLabel.toUpperCase();
|
|
10368
|
+
const route = typeof record.route === "string" && record.route.trim() ? record.route.trim() : routeFromLabel;
|
|
10369
|
+
const content = typeof record.content === "string" ? record.content : "";
|
|
10370
|
+
if (!label && !content.trim()) return null;
|
|
10371
|
+
return {
|
|
10372
|
+
label: label || `${method} ${route}`.trim(),
|
|
10373
|
+
method: method || "GET",
|
|
10374
|
+
route: route || "/",
|
|
10375
|
+
content
|
|
10376
|
+
};
|
|
10377
|
+
}).filter((tab) => !!tab);
|
|
10378
|
+
}
|
|
10379
|
+
function listAndroidDeviceMockupIds() {
|
|
10380
|
+
const bundlePath = getBundlePath();
|
|
10381
|
+
if (!bundlePath) {
|
|
10382
|
+
return [...ANDROID_DEVICE_MOCKUP_FALLBACKS];
|
|
10383
|
+
}
|
|
10384
|
+
const publicDir = join5(bundlePath, "public");
|
|
10385
|
+
if (!existsSync5(publicDir)) {
|
|
10386
|
+
return [...ANDROID_DEVICE_MOCKUP_FALLBACKS];
|
|
10387
|
+
}
|
|
10388
|
+
const files = readdirSync2(publicDir).filter((file) => /^mockup-android.*\.png$/i.test(file));
|
|
10389
|
+
const unique = new Set(files.length > 0 ? files : ANDROID_DEVICE_MOCKUP_FALLBACKS);
|
|
10390
|
+
return [...unique].sort((left, right) => {
|
|
10391
|
+
const leftIndex = ANDROID_DEVICE_MOCKUP_FALLBACKS.indexOf(left);
|
|
10392
|
+
const rightIndex = ANDROID_DEVICE_MOCKUP_FALLBACKS.indexOf(right);
|
|
10393
|
+
if (leftIndex !== -1 || rightIndex !== -1) {
|
|
10394
|
+
if (leftIndex === -1) return 1;
|
|
10395
|
+
if (rightIndex === -1) return -1;
|
|
10396
|
+
return leftIndex - rightIndex;
|
|
10397
|
+
}
|
|
10398
|
+
return left.localeCompare(right);
|
|
10399
|
+
});
|
|
10400
|
+
}
|
|
10401
|
+
function resolveAndroidDeviceMockup(requested, available) {
|
|
10402
|
+
const candidate = typeof requested === "string" ? requested.trim() : "";
|
|
10403
|
+
if (candidate && available.includes(candidate)) {
|
|
10404
|
+
return candidate;
|
|
10405
|
+
}
|
|
10406
|
+
for (const fallback of ANDROID_DEVICE_MOCKUP_FALLBACKS) {
|
|
10407
|
+
if (available.includes(fallback)) {
|
|
10408
|
+
return fallback;
|
|
10409
|
+
}
|
|
10410
|
+
}
|
|
10411
|
+
return available[0] || DEFAULT_ANDROID_DEVICE_MOCKUP;
|
|
10412
|
+
}
|
|
10413
|
+
function formatAndroidDeviceMockupLabel(filename) {
|
|
10414
|
+
const base = filename.replace(/^mockup-android-?/i, "").replace(/\.png$/i, "");
|
|
10415
|
+
if (!base) return "Android";
|
|
10416
|
+
return base.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
10417
|
+
}
|
|
10195
10418
|
function isTunnelLikeNetworkEntry(entry) {
|
|
10196
10419
|
if (!entry || typeof entry !== "object") return false;
|
|
10197
10420
|
const method = typeof entry.method === "string" ? entry.method.toUpperCase() : "";
|
|
@@ -10331,7 +10554,8 @@ function loadEditedTemplateContent(projectId) {
|
|
|
10331
10554
|
title: data.title,
|
|
10332
10555
|
titleLines: data.titleLines,
|
|
10333
10556
|
terminalTabs: data.terminalTabs,
|
|
10334
|
-
showcaseMode: data.showcaseMode
|
|
10557
|
+
showcaseMode: data.showcaseMode,
|
|
10558
|
+
deviceMockup: data.deviceMockup
|
|
10335
10559
|
};
|
|
10336
10560
|
} catch {
|
|
10337
10561
|
return null;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TEMPLATES_DIR
|
|
3
|
+
} from "./chunk-VVIOB362.js";
|
|
4
|
+
|
|
5
|
+
// src/core/templates/loader.ts
|
|
6
|
+
import { existsSync, readFileSync } from "fs";
|
|
7
|
+
import { join, dirname } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
var MANIFEST_FILE = "manifest.json";
|
|
10
|
+
var BUNDLE_DIR = "bundle";
|
|
11
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
var BUNDLED_TEMPLATES_DIR_CANDIDATES = [
|
|
13
|
+
join(__dirname, "templates"),
|
|
14
|
+
join(__dirname, "..", "templates"),
|
|
15
|
+
join(__dirname, "..", "..", "templates")
|
|
16
|
+
];
|
|
17
|
+
var cachedManifest = null;
|
|
18
|
+
var cachedTemplatesDir = null;
|
|
19
|
+
var cachedAt = 0;
|
|
20
|
+
var CACHE_TTL = 3e4;
|
|
21
|
+
function resolveTemplatesDir() {
|
|
22
|
+
const explicitOverride = process.env.DISCOVERYLAB_TEMPLATE_DIR?.trim() || process.env.DISCOVERYLAB_TEMPLATE_SOURCE_DIR?.trim();
|
|
23
|
+
if (explicitOverride) {
|
|
24
|
+
const overrideManifest = join(explicitOverride, MANIFEST_FILE);
|
|
25
|
+
const overrideBundle = join(explicitOverride, BUNDLE_DIR);
|
|
26
|
+
if (existsSync(overrideManifest) && existsSync(overrideBundle)) {
|
|
27
|
+
return explicitOverride;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const candidate of BUNDLED_TEMPLATES_DIR_CANDIDATES) {
|
|
31
|
+
const bundledManifest = join(candidate, MANIFEST_FILE);
|
|
32
|
+
const bundledBundle = join(candidate, BUNDLE_DIR);
|
|
33
|
+
if (existsSync(bundledManifest) && existsSync(bundledBundle)) {
|
|
34
|
+
return candidate;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const localManifest = join(TEMPLATES_DIR, MANIFEST_FILE);
|
|
38
|
+
const localBundle = join(TEMPLATES_DIR, BUNDLE_DIR);
|
|
39
|
+
if (existsSync(localManifest) && existsSync(localBundle)) {
|
|
40
|
+
return TEMPLATES_DIR;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function loadManifest() {
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
if (cachedManifest && now - cachedAt < CACHE_TTL) {
|
|
47
|
+
return cachedManifest;
|
|
48
|
+
}
|
|
49
|
+
const dir = resolveTemplatesDir();
|
|
50
|
+
if (!dir) {
|
|
51
|
+
cachedManifest = null;
|
|
52
|
+
cachedTemplatesDir = null;
|
|
53
|
+
cachedAt = now;
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const raw = readFileSync(join(dir, MANIFEST_FILE), "utf-8");
|
|
58
|
+
const manifest = JSON.parse(raw);
|
|
59
|
+
cachedManifest = manifest;
|
|
60
|
+
cachedTemplatesDir = dir;
|
|
61
|
+
cachedAt = now;
|
|
62
|
+
return manifest;
|
|
63
|
+
} catch {
|
|
64
|
+
cachedManifest = null;
|
|
65
|
+
cachedTemplatesDir = null;
|
|
66
|
+
cachedAt = now;
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function isTemplatesInstalled() {
|
|
71
|
+
return loadManifest() !== null;
|
|
72
|
+
}
|
|
73
|
+
function getAvailableTemplates() {
|
|
74
|
+
const manifest = loadManifest();
|
|
75
|
+
return manifest?.templates ?? [];
|
|
76
|
+
}
|
|
77
|
+
function getTemplate(id) {
|
|
78
|
+
const templates = getAvailableTemplates();
|
|
79
|
+
return templates.find((t) => t.id === id) ?? null;
|
|
80
|
+
}
|
|
81
|
+
function getBundlePath() {
|
|
82
|
+
loadManifest();
|
|
83
|
+
if (!cachedTemplatesDir) return null;
|
|
84
|
+
const bundlePath = join(cachedTemplatesDir, BUNDLE_DIR);
|
|
85
|
+
if (!existsSync(bundlePath)) return null;
|
|
86
|
+
return bundlePath;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export {
|
|
90
|
+
isTemplatesInstalled,
|
|
91
|
+
getAvailableTemplates,
|
|
92
|
+
getTemplate,
|
|
93
|
+
getBundlePath
|
|
94
|
+
};
|