@lowdefy/server-dev 6.1.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (623) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +2 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +222 -36
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +67 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +679 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  104. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  105. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  106. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  107. package/lib/docs/dataSets/findCollectionCollisions.js +52 -0
  108. package/lib/docs/dataSets/getDataStore.js +106 -0
  109. package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
  110. package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
  111. package/lib/docs/dataSets/groupDataSetByCollection.js +44 -0
  112. package/lib/docs/dataSets/insertFixture.js +57 -0
  113. package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
  114. package/lib/docs/dataSets/isDataStoreGone.js +29 -0
  115. package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
  116. package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
  117. package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
  118. package/lib/docs/dataSets/openDataSession.js +119 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +343 -0
  120. package/lib/docs/dataSets/pickDataStorePort.js +58 -0
  121. package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
  122. package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
  123. package/lib/docs/dataSets/readDataSession.js +41 -0
  124. package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
  125. package/lib/docs/dataSets/readDataSet.js +68 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +220 -0
  127. package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
  128. package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
  129. package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
  130. package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
  131. package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
  132. package/lib/docs/dataSets/sweepOrphanDatabases.js +33 -0
  133. package/lib/docs/dataSets/test/app/lowdefy.yaml +203 -0
  134. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  135. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +8 -0
  136. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  137. package/lib/docs/devEventBus.js +136 -0
  138. package/lib/docs/devEventBus.test.mjs +269 -0
  139. package/lib/docs/devToolDefinitions.js +529 -0
  140. package/lib/docs/docSources.js +21 -0
  141. package/lib/docs/docs.test.mjs +171 -15
  142. package/lib/docs/docsIndex.test.mjs +377 -0
  143. package/lib/docs/evalOperator.js +5 -3
  144. package/lib/docs/evalOperatorHeadless.js +84 -12
  145. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  146. package/lib/docs/evalOperatorInTab.js +2 -1
  147. package/lib/docs/findConfig.js +95 -49
  148. package/lib/docs/fitResponse.js +81 -0
  149. package/lib/docs/fitResponse.test.mjs +128 -0
  150. package/lib/docs/formatFeedback.js +26 -5
  151. package/lib/docs/formatFeedback.test.mjs +48 -0
  152. package/lib/docs/getAppMap.js +7 -1
  153. package/lib/docs/getBrowser.js +150 -80
  154. package/lib/docs/getBrowser.test.mjs +407 -16
  155. package/lib/docs/getBuildId.js +61 -0
  156. package/lib/docs/getBuildId.test.mjs +84 -0
  157. package/lib/docs/getBuildStatus.js +54 -6
  158. package/lib/docs/getBuildStatus.test.mjs +133 -0
  159. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  160. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  161. package/lib/docs/getCoreDoc.js +9 -1
  162. package/lib/docs/getDoc.js +100 -0
  163. package/lib/docs/getDocsIndex.js +152 -0
  164. package/lib/docs/getHazards.js +115 -0
  165. package/lib/docs/getHazards.test.mjs +175 -0
  166. package/lib/docs/getMailOutboxDirectory.js +26 -0
  167. package/lib/docs/getOverview.js +79 -13
  168. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  169. package/lib/docs/getPageBuildStatus.js +46 -0
  170. package/lib/docs/getPluginDoc.js +21 -23
  171. package/lib/docs/getSchema.js +33 -1
  172. package/lib/docs/getStaleStatus.js +36 -0
  173. package/lib/docs/getStaleStatus.test.mjs +74 -0
  174. package/lib/docs/guardJourneyOrigin.js +39 -0
  175. package/lib/docs/inspectState.js +5 -3
  176. package/lib/docs/inspectStateFromTab.js +2 -2
  177. package/lib/docs/inspectStateHeadless.js +89 -27
  178. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  179. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  180. package/lib/docs/installHeadlessShell.js +123 -0
  181. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  182. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  183. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  184. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  185. package/lib/docs/isPageReady.js +77 -0
  186. package/lib/docs/isPageReady.test.mjs +235 -0
  187. package/lib/docs/isRunErrorEntry.js +25 -0
  188. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  189. package/lib/docs/journeyRunRecording.js +35 -0
  190. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  191. package/lib/docs/keepEventStreamAlive.js +68 -0
  192. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  193. package/lib/docs/launchBrowser.js +83 -0
  194. package/lib/docs/launchBrowser.test.mjs +175 -0
  195. package/lib/docs/listModuleDocEntries.js +69 -0
  196. package/lib/docs/listPackageDocFiles.js +67 -0
  197. package/lib/docs/listPluginDocEntries.js +111 -0
  198. package/lib/docs/listPlugins.js +9 -58
  199. package/lib/docs/listTypes.js +14 -6
  200. package/lib/docs/loadFrameworkHazards.js +40 -0
  201. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  202. package/lib/docs/loadState.js +103 -35
  203. package/lib/docs/makeModuleManifestDoc.js +174 -0
  204. package/lib/docs/matchExercisedPages.js +35 -0
  205. package/lib/docs/matchJourneyRoute.js +69 -0
  206. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  207. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  208. package/lib/docs/nextJourneySequence.js +30 -0
  209. package/lib/docs/noBrowserError.js +22 -0
  210. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  211. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  212. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  213. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  214. package/lib/docs/observe/evaluateInvariants.js +183 -0
  215. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  216. package/lib/docs/observe/findingKey.js +29 -0
  217. package/lib/docs/observe/hasNoEffect.js +31 -0
  218. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  219. package/lib/docs/observe/installStepObserver.js +72 -0
  220. package/lib/docs/observe/isAppApiUrl.js +33 -0
  221. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  222. package/lib/docs/observe/readStepObserver.js +42 -0
  223. package/lib/docs/observe/relativeSource.js +29 -0
  224. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  225. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  226. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  227. package/lib/docs/observe/usesSearchStage.js +45 -0
  228. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  229. package/lib/docs/observe/watchJourneyContext.js +75 -0
  230. package/lib/docs/observe/withAppErrors.js +31 -0
  231. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  232. package/lib/docs/openJourney.js +94 -0
  233. package/lib/docs/openJourneyEmail.js +41 -0
  234. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  235. package/lib/docs/pageBlocks.js +47 -0
  236. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  237. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  238. package/lib/docs/rails/authEngineActions.js +53 -0
  239. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  240. package/lib/docs/rails/authEngineMethods.js +52 -0
  241. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  242. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  243. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  244. package/lib/docs/rails/checkClickRails.js +56 -0
  245. package/lib/docs/rails/createDataSetRails.js +82 -0
  246. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  247. package/lib/docs/rails/findExternalBlocks.js +147 -0
  248. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  249. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  250. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  251. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  252. package/lib/docs/readConnectionPlugins.js +26 -0
  253. package/lib/docs/readDevAuthMode.js +32 -0
  254. package/lib/docs/readDocEntry.js +40 -0
  255. package/lib/docs/readDocTitle.js +26 -0
  256. package/lib/docs/readJourneyEmailMatch.js +57 -0
  257. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  258. package/lib/docs/readJourneySession.js +56 -0
  259. package/lib/docs/readMailOutbox.js +37 -0
  260. package/lib/docs/readPagePath.js +27 -0
  261. package/lib/docs/readPluginPackages.js +99 -0
  262. package/lib/docs/readProxyBuildWait.js +38 -0
  263. package/lib/docs/readRoutineSteps.js +45 -0
  264. package/lib/docs/registerShownTextEngine.js +118 -0
  265. package/lib/docs/requestRestart.js +30 -0
  266. package/lib/docs/requestRestart.test.mjs +49 -0
  267. package/lib/docs/resolvePageInstance.js +46 -0
  268. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  269. package/lib/docs/resolveSource.js +11 -7
  270. package/lib/docs/resolveSource.test.mjs +1 -1
  271. package/lib/docs/resolveToolCaller.js +62 -0
  272. package/lib/docs/reviewPage.js +51 -0
  273. package/lib/docs/reviewPageBuilds.js +56 -0
  274. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  275. package/lib/docs/runCheck.js +75 -0
  276. package/lib/docs/runCheck.test.mjs +91 -0
  277. package/lib/docs/runCheckChild.js +49 -0
  278. package/lib/docs/runEndpoint.js +185 -0
  279. package/lib/docs/runEndpoint.test.mjs +509 -0
  280. package/lib/docs/runErrorBuffers.js +99 -0
  281. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  282. package/lib/docs/runJourney.js +347 -0
  283. package/lib/docs/runJourney.test.mjs +2383 -0
  284. package/lib/docs/runJourneyAsUsers.js +44 -0
  285. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  286. package/lib/docs/runJourneySteps.js +1320 -0
  287. package/lib/docs/runRequest.js +100 -59
  288. package/lib/docs/runRequest.test.mjs +190 -0
  289. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  290. package/lib/docs/scaffoldPage.js +3 -1
  291. package/lib/docs/screenshotPage.js +163 -11
  292. package/lib/docs/screenshotPage.test.mjs +263 -1
  293. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  294. package/lib/docs/searchDocs.js +82 -30
  295. package/lib/docs/searchDocs.test.mjs +100 -0
  296. package/lib/docs/searchIcons.js +90 -0
  297. package/lib/docs/searchIcons.test.mjs +127 -0
  298. package/lib/docs/selectExercised.js +39 -0
  299. package/lib/docs/selectFinalState.js +66 -0
  300. package/lib/docs/selectFinalState.test.mjs +51 -0
  301. package/lib/docs/serverErrorStore.js +54 -0
  302. package/lib/docs/serverErrorStore.test.mjs +50 -0
  303. package/lib/docs/setupTestFixtures.mjs +133 -39
  304. package/lib/docs/shownTextSelector.js +28 -0
  305. package/lib/docs/snapshotState.js +13 -5
  306. package/lib/docs/splitSearchTerms.js +62 -0
  307. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  308. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  309. package/lib/docs/tabAvailable.js +6 -5
  310. package/lib/docs/tabChannel.js +127 -21
  311. package/lib/docs/tabChannel.test.mjs +187 -29
  312. package/lib/docs/unsettledPageNote.js +24 -0
  313. package/lib/docs/validateJourneyMail.js +36 -0
  314. package/lib/docs/validateJourneyTimeout.js +38 -0
  315. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  316. package/lib/docs/validateRecordingParam.js +42 -0
  317. package/lib/docs/validateStateSelection.js +33 -0
  318. package/lib/docs/validateStateSelection.test.mjs +33 -0
  319. package/lib/docs/validateViewport.js +51 -0
  320. package/lib/docs/validateViewport.test.mjs +54 -0
  321. package/lib/docs/waitForBuild.js +50 -0
  322. package/lib/docs/waitForBuild.test.mjs +75 -0
  323. package/lib/docs/waitForJourneyEmail.js +77 -0
  324. package/lib/docs/withBrowserSlot.js +45 -0
  325. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  326. package/lib/docs/withDataSession.js +53 -0
  327. package/lib/server/applyDataSetRedirect.js +83 -0
  328. package/lib/server/applyDataSetRedirect.test.mjs +340 -0
  329. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +204 -0
  330. package/lib/server/applyDataSetRedirectWalled.test.mjs +131 -0
  331. package/lib/server/auth/createSystemContext.js +73 -0
  332. package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
  333. package/lib/server/auth/getHeadlessUser.js +9 -8
  334. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  335. package/lib/server/auth/getMockUser.js +18 -14
  336. package/lib/server/auth/{session.js → getStrategies.js} +17 -16
  337. package/lib/server/auth/headlessUser.js +3 -2
  338. package/lib/server/auth/journeyActor.js +35 -0
  339. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  340. package/lib/server/checkPageRecord.js +44 -0
  341. package/lib/server/contextMapBudget.js +24 -0
  342. package/lib/server/contextMapBudget.test.mjs +98 -0
  343. package/lib/server/continueJitKeys.js +28 -0
  344. package/lib/server/continueJitKeys.test.mjs +43 -0
  345. package/lib/server/createDataSessionEndedError.js +33 -0
  346. package/lib/server/createDataSetRefusal.js +26 -0
  347. package/lib/server/createLowdefyContext.js +168 -41
  348. package/lib/server/createLowdefyContext.test.mjs +328 -8
  349. package/lib/server/getClientAddress.js +51 -0
  350. package/lib/server/getClientAddress.test.mjs +56 -0
  351. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  352. package/lib/server/hashConfigContent.js +26 -0
  353. package/lib/server/importAppCode.test.mjs +57 -0
  354. package/lib/server/isRoutineStep.js +27 -0
  355. package/lib/server/jitPageBuilder.js +309 -126
  356. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  357. package/lib/server/jitPageBuilder.test.mjs +218 -0
  358. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  359. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  360. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  361. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  362. package/lib/server/journeyCookies.js +22 -0
  363. package/lib/server/journeyCookies.test.mjs +145 -0
  364. package/lib/server/loadDynamicJsMap.js +47 -0
  365. package/lib/server/log/createHandleError.js +38 -2
  366. package/lib/server/log/createHandleError.test.mjs +125 -2
  367. package/lib/server/log/createLogger.js +3 -0
  368. package/lib/server/log/createLogger.test.mjs +15 -0
  369. package/lib/server/log/logRequest.js +2 -3
  370. package/lib/server/mutantDataSetWrappers.test.mjs +112 -0
  371. package/lib/server/mutants/applyMutant.js +39 -0
  372. package/lib/server/mutants/artifactPatterns.js +25 -0
  373. package/lib/server/mutants/buildAnchor.js +45 -0
  374. package/lib/server/mutants/collectSharedIds.js +41 -0
  375. package/lib/server/mutants/configPathSegments.js +38 -0
  376. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  377. package/lib/server/mutants/describeEvent.js +22 -0
  378. package/lib/server/mutants/enumerateArtifact.js +56 -0
  379. package/lib/server/mutants/findNodeByKey.js +45 -0
  380. package/lib/server/mutants/findNodeByPointer.js +49 -0
  381. package/lib/server/mutants/getArtifactKind.js +32 -0
  382. package/lib/server/mutants/groupMutantCopies.js +56 -0
  383. package/lib/server/mutants/hashArtifact.js +25 -0
  384. package/lib/server/mutants/hashMutantTarget.js +34 -0
  385. package/lib/server/mutants/listMutants.js +122 -0
  386. package/lib/server/mutants/listMutants.test.mjs +502 -0
  387. package/lib/server/mutants/mutantId.js +30 -0
  388. package/lib/server/mutants/mutantRuns.js +57 -0
  389. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  390. package/lib/server/mutants/operators/dropAction.js +47 -0
  391. package/lib/server/mutants/operators/dropBlock.js +44 -0
  392. package/lib/server/mutants/operators/dropPayload.js +52 -0
  393. package/lib/server/mutants/operators/dropStep.js +44 -0
  394. package/lib/server/mutants/operators/flipVisible.js +53 -0
  395. package/lib/server/mutants/operators/index.js +37 -0
  396. package/lib/server/mutants/operators/retargetLink.js +72 -0
  397. package/lib/server/mutants/operators/skipValidate.js +77 -0
  398. package/lib/server/mutants/operators/swapIf.js +112 -0
  399. package/lib/server/mutants/operators.test.mjs +396 -0
  400. package/lib/server/mutants/positionFreePath.js +38 -0
  401. package/lib/server/mutants/removeFromParent.js +28 -0
  402. package/lib/server/mutants/test/fixtures.mjs +173 -0
  403. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  404. package/lib/server/mutants/validateMutantParam.js +69 -0
  405. package/lib/server/mutants/validateMutantsBody.js +88 -0
  406. package/lib/server/mutants/walkArtifact.js +179 -0
  407. package/lib/server/pageBuildRecords.js +131 -0
  408. package/lib/server/pageBuildRecords.test.mjs +258 -0
  409. package/lib/server/pageCache.mjs +22 -7
  410. package/lib/server/pageCache.test.mjs +22 -7
  411. package/lib/server/pageRebuilds.test.mjs +543 -0
  412. package/lib/server/pruneJitMaps.js +36 -0
  413. package/lib/server/pruneJitMaps.test.mjs +55 -0
  414. package/lib/server/readInstanceRecord.js +33 -0
  415. package/lib/server/readInstanceRecord.test.mjs +99 -0
  416. package/lib/server/readMergedMaps.js +86 -0
  417. package/lib/server/readMergedMaps.test.mjs +79 -0
  418. package/lib/server/recording/appendRecords.js +33 -0
  419. package/lib/server/recording/errorRecordingStamp.js +34 -0
  420. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  421. package/lib/server/recording/isRecordingEnabled.js +31 -0
  422. package/lib/server/recording/readRecordingCookie.js +71 -0
  423. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  424. package/lib/server/recording/recordingCookiePayload.js +33 -0
  425. package/lib/server/recording/servedBuilds.js +46 -0
  426. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  427. package/lib/server/sinkExternalConnections.js +95 -0
  428. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  429. package/lib/server/skipStaleMapWrites.js +62 -0
  430. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  431. package/lib/server/startIdleGc.js +67 -0
  432. package/lib/server/startIdleGc.test.mjs +138 -0
  433. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  434. package/lib/server/trackSessionWork.js +30 -0
  435. package/lib/vite/createDevServerExclude.js +40 -0
  436. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  437. package/manager/getContext.mjs +24 -2
  438. package/manager/processes/initialBuild.mjs +14 -0
  439. package/manager/processes/initialBuild.test.mjs +101 -0
  440. package/manager/processes/lowdefyBuild.mjs +30 -7
  441. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
  442. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  443. package/manager/processes/optimizeDependencies.mjs +59 -0
  444. package/manager/processes/restartServer.mjs +34 -6
  445. package/manager/processes/restartServer.test.mjs +162 -0
  446. package/manager/processes/shutdownServer.mjs +11 -0
  447. package/manager/processes/shutdownServer.test.mjs +45 -0
  448. package/manager/processes/startFirstServer.mjs +46 -0
  449. package/manager/processes/startFirstServer.test.mjs +326 -0
  450. package/manager/processes/startMailSink.mjs +135 -0
  451. package/manager/processes/startMailSink.test.mjs +146 -0
  452. package/manager/processes/startProxy.mjs +129 -12
  453. package/manager/processes/startProxy.test.mjs +366 -0
  454. package/manager/processes/startServer.mjs +47 -6
  455. package/manager/processes/startServer.test.mjs +155 -0
  456. package/manager/processes/startWatchers.mjs +6 -4
  457. package/manager/processes/syncServer.mjs +69 -0
  458. package/manager/processes/syncServer.test.mjs +141 -0
  459. package/manager/processes/waitForDevServer.mjs +48 -0
  460. package/manager/processes/waitForDevServer.test.mjs +112 -0
  461. package/manager/run.mjs +128 -43
  462. package/manager/utils/BatchChanges.mjs +21 -5
  463. package/manager/utils/BatchChanges.test.mjs +77 -0
  464. package/manager/utils/acquireDevInstance.mjs +99 -0
  465. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  466. package/manager/utils/createBuildActivity.mjs +67 -0
  467. package/manager/utils/createBuildActivity.test.mjs +58 -0
  468. package/manager/utils/createChangeSignal.mjs +39 -0
  469. package/manager/utils/createChangeSignal.test.mjs +62 -0
  470. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  471. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  472. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  473. package/manager/utils/createDotPathIgnore.mjs +44 -0
  474. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  475. package/manager/utils/createIdleGc.mjs +51 -0
  476. package/manager/utils/createIdleGc.test.mjs +77 -0
  477. package/manager/utils/createRequestActivity.mjs +100 -0
  478. package/manager/utils/createRequestActivity.test.mjs +121 -0
  479. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  480. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  481. package/manager/utils/createServerEnv.mjs +39 -0
  482. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  483. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  485. package/manager/utils/importFresh.mjs +46 -0
  486. package/manager/utils/importFresh.test.mjs +72 -0
  487. package/manager/utils/importFreshWorker.mjs +20 -0
  488. package/manager/utils/killTaggedBrowser.mjs +35 -0
  489. package/manager/utils/pagePathChanged.mjs +56 -0
  490. package/manager/utils/pruneRecordings.mjs +85 -0
  491. package/manager/utils/pruneRecordings.test.mjs +117 -0
  492. package/manager/utils/publishBuildDirectory.mjs +121 -0
  493. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  494. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  495. package/manager/utils/readBasePath.mjs +27 -0
  496. package/manager/utils/readBasePath.test.mjs +43 -0
  497. package/manager/utils/readBuildStatusWait.mjs +67 -0
  498. package/manager/utils/readManagerVersion.mjs +30 -0
  499. package/manager/utils/readManagerVersion.test.mjs +35 -0
  500. package/manager/utils/readPluginDefinitions.mjs +36 -0
  501. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  502. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  503. package/manager/utils/resolvePorts.mjs +49 -0
  504. package/manager/utils/setupWatcher.mjs +19 -11
  505. package/manager/utils/startRecordingPruner.mjs +45 -0
  506. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  507. package/manager/utils/waitForChildExit.mjs +53 -0
  508. package/manager/utils/waitForChildExit.test.mjs +70 -0
  509. package/manager/utils/waitForServer.mjs +44 -0
  510. package/manager/utils/writeBuildStatus.mjs +1 -1
  511. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  512. package/manager/watchers/envWatcher.mjs +9 -2
  513. package/manager/watchers/envWatcher.test.mjs +87 -0
  514. package/manager/watchers/listServerSidePackages.mjs +47 -0
  515. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  516. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  517. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  518. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  519. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  520. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  521. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  522. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  523. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  524. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  525. package/package.json +60 -47
  526. package/package.original.json +60 -47
  527. package/src/app.js +131 -20
  528. package/src/html/renderDevPage.js +63 -11
  529. package/src/html/renderDevPage.test.mjs +138 -0
  530. package/src/lib/getRequestPath.js +29 -0
  531. package/src/middleware/createSameOriginGuard.js +37 -0
  532. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  533. package/src/middleware/errorHandler.js +51 -1
  534. package/src/middleware/errorHandler.test.mjs +173 -3
  535. package/src/middleware/isRebindingSafeHost.js +44 -0
  536. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  537. package/src/middleware/localDevToolsOnly.js +38 -0
  538. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  539. package/src/middleware/noStore.js +27 -0
  540. package/src/middleware/noStore.test.mjs +48 -0
  541. package/src/middleware/requestTimeout.js +38 -0
  542. package/src/middleware/staleFlag.js +76 -0
  543. package/src/middleware/staleFlag.test.mjs +127 -0
  544. package/src/middleware/stampCaller.js +38 -0
  545. package/src/middleware/stampCaller.test.mjs +64 -0
  546. package/src/routes/agent.js +15 -3
  547. package/src/routes/auth.js +29 -20
  548. package/src/routes/auth.test.mjs +30 -56
  549. package/src/routes/clientError.js +13 -13
  550. package/src/routes/cron.js +3 -1
  551. package/src/routes/cronForward.js +3 -1
  552. package/src/routes/detached.js +22 -9
  553. package/src/routes/devInspect.js +17 -8
  554. package/src/routes/devInspect.test.mjs +44 -7
  555. package/src/routes/devRecording.js +113 -0
  556. package/src/routes/devRecording.test.mjs +253 -0
  557. package/src/routes/docs/buildStatus.js +10 -2
  558. package/src/routes/docs/buildStatus.test.mjs +46 -0
  559. package/src/routes/docs/content.js +5 -3
  560. package/src/routes/docs/evalOperator.js +15 -1
  561. package/src/routes/docs/events.js +50 -0
  562. package/src/routes/docs/events.test.mjs +117 -0
  563. package/src/routes/docs/find.js +9 -1
  564. package/src/routes/docs/icons.js +30 -0
  565. package/src/routes/docs/inspectState.js +16 -1
  566. package/src/routes/docs/journey.js +194 -0
  567. package/src/routes/docs/journey.test.mjs +401 -0
  568. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  569. package/src/routes/docs/loadState.js +4 -2
  570. package/src/routes/docs/mcp.js +34 -3
  571. package/src/routes/docs/mcp.test.mjs +130 -0
  572. package/src/routes/docs/mutants.js +78 -0
  573. package/src/routes/docs/mutants.test.mjs +180 -0
  574. package/src/routes/docs/parsePathParamsParam.js +41 -0
  575. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  576. package/src/routes/docs/parseUserParam.js +27 -21
  577. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  578. package/src/routes/docs/restart.js +35 -0
  579. package/src/routes/docs/restart.test.mjs +64 -0
  580. package/src/routes/docs/runEndpoint.js +54 -0
  581. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  582. package/src/routes/docs/runRequest.js +25 -8
  583. package/src/routes/docs/runRequest.test.mjs +92 -0
  584. package/src/routes/docs/screenshot.js +50 -2
  585. package/src/routes/docs/screenshot.test.mjs +117 -0
  586. package/src/routes/docs/search.js +15 -2
  587. package/src/routes/docs/snapshotState.js +17 -3
  588. package/src/routes/endpoints.js +27 -26
  589. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  590. package/src/routes/feedback.js +10 -13
  591. package/src/routes/jitPage.js +99 -35
  592. package/src/routes/jitPage.test.mjs +248 -0
  593. package/src/routes/mcp.js +82 -5
  594. package/src/routes/mcp.test.mjs +215 -0
  595. package/src/routes/mcpMemberToken.test.mjs +125 -0
  596. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  597. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  598. package/src/routes/mountDevPageRoutes.js +25 -0
  599. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  600. package/src/routes/mountOauthDiscovery.js +44 -0
  601. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  602. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  604. package/src/routes/reload.js +16 -0
  605. package/src/routes/request.js +6 -4
  606. package/src/routes/request.test.mjs +9 -5
  607. package/src/routes/requestBody.test.mjs +159 -0
  608. package/src/routes/usage.js +6 -2
  609. package/src/routes/user.js +27 -0
  610. package/src/routes/websocket.js +29 -8
  611. package/src/routes/websocket.test.mjs +65 -0
  612. package/src/routes/wellKnownFallback.js +30 -0
  613. package/src/routes/wellKnownFallback.test.mjs +32 -0
  614. package/src/websocket/devWebSocket.js +41 -26
  615. package/src/websocket/devWebSocket.test.mjs +157 -0
  616. package/vite.config.js +53 -23
  617. package/lib/server/auth/getDevSession.js +0 -70
  618. package/lib/server/auth/getDevSession.test.mjs +0 -138
  619. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  620. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  621. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  622. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  623. package/src/routes/apiPage.js +0 -37
@@ -0,0 +1,150 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import http from 'node:http';
18
+
19
+ import { jest } from '@jest/globals';
20
+
21
+ import pageReadyScript from '../../test-utils/pageReadyScript.mjs';
22
+
23
+ // A real Chromium against a small server that resolves every request's
24
+ // cookie header the way createLowdefyContext does, so the browser leg of a
25
+ // mutant run is pinned: the cookie openPage writes is the one the dev server
26
+ // verifies, it rides every request of every actor of the journey and nothing
27
+ // from any other context, page JavaScript cannot read it, and a detached hop
28
+ // forwards it alone. Skipped when no Chromium can be launched.
29
+ jest.unstable_mockModule('../build/config.js', () => ({ default: {} }));
30
+
31
+ const { getBrowser, openPage } = await import('./getBrowser.js');
32
+ const { default: createJourneyActors } = await import('./createJourneyActors.js');
33
+ const { openMutantRun, readMutantRun } = await import('../server/mutants/mutantRuns.js');
34
+ const { forwardJourneyCookies } = await import('../server/journeyCookies.js');
35
+ const { journeyActorToken } = await import('../server/auth/journeyActor.js');
36
+
37
+ // The page marks itself ready once its requests finish, so every request the
38
+ // test counts is made before openPage returns.
39
+ function pageHtml(pageId) {
40
+ return `<!doctype html><html><head><title>loading</title></head><body><script>
41
+ fetch('/api/page/${pageId}', { credentials: 'same-origin' })
42
+ .then(() => fetch('/api/request/${pageId}/save', { method: 'POST' }))
43
+ .then(() => {
44
+ ${pageReadyScript({ pageId })}
45
+ document.title = 'done';
46
+ });
47
+ </script></body></html>`;
48
+ }
49
+
50
+ let server;
51
+ let origin;
52
+ const received = [];
53
+ let browser = null;
54
+ try {
55
+ browser = await getBrowser();
56
+ } catch {
57
+ browser = null;
58
+ }
59
+ const chromiumTest = browser === null ? test.skip : test;
60
+
61
+ beforeAll(async () => {
62
+ server = http.createServer((req, res) => {
63
+ const [, pageId] = req.url.match(/^\/(?:api\/(?:page|request)\/)?([a-z]+)/) ?? [];
64
+ received.push({ url: req.url, pageId, cookie: req.headers.cookie });
65
+ if (!req.url.startsWith('/api/')) {
66
+ res.writeHead(200, { 'content-type': 'text/html' });
67
+ res.end(pageHtml(pageId));
68
+ return;
69
+ }
70
+ res.writeHead(200, { 'content-type': 'application/json' });
71
+ res.end('{}');
72
+ });
73
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
74
+ origin = `http://127.0.0.1:${server.address().port}`;
75
+ });
76
+
77
+ // Closing Chromium on Windows can take longer than Jest's 5 s hook default.
78
+ afterAll(async () => {
79
+ await browser?.close();
80
+ server.closeAllConnections();
81
+ await new Promise((resolve) => server.close(resolve));
82
+ }, 30000);
83
+
84
+ // What is pinned is which requests carry the cookie, not how fast a page
85
+ // loads: a cold Chromium on a Windows runner can take seconds over the first
86
+ // navigation.
87
+ const PAGE_TIMEOUT = 15000;
88
+
89
+ chromiumTest(
90
+ 'a mutant cookie reaches only the journey actors, unreadable by the page, and alone on the loopback hop',
91
+ async () => {
92
+ const opened = openMutantRun({
93
+ mutant: {
94
+ buildId: 'b',
95
+ artifact: 'pages/journey.json',
96
+ key: 'k',
97
+ arg: null,
98
+ operator: 'drop-block',
99
+ },
100
+ });
101
+ const actors = createJourneyActors({
102
+ browser,
103
+ origin,
104
+ basePath: '',
105
+ pageId: 'journey',
106
+ user: 'none',
107
+ timeout: PAGE_TIMEOUT,
108
+ mutantCookie: opened.cookiePayload,
109
+ });
110
+ let bystander;
111
+ try {
112
+ const main = await actors.switchTo('main');
113
+ await main.page.waitForFunction(() => document.title === 'done');
114
+ const other = await actors.switchTo('other');
115
+ await other.page.waitForFunction(() => document.title === 'done');
116
+ // A developer's tab, open beside the run.
117
+ bystander = await openPage({
118
+ browser,
119
+ origin,
120
+ pageId: 'bystander',
121
+ user: 'none',
122
+ timeout: PAGE_TIMEOUT,
123
+ });
124
+ await bystander.page.waitForFunction(() => document.title === 'done');
125
+
126
+ expect(await main.page.evaluate(() => document.cookie)).not.toContain('lowdefy_journey');
127
+
128
+ const journeyRequests = received.filter((request) => request.pageId === 'journey');
129
+ const bystanderRequests = received.filter((request) => request.pageId === 'bystander');
130
+ // Each actor loads the page, then its page config and request: six in all.
131
+ expect(journeyRequests).toHaveLength(6);
132
+ expect(bystanderRequests).toHaveLength(3);
133
+ journeyRequests.forEach((request) => {
134
+ expect(readMutantRun(request.cookie)).toBe(opened.run);
135
+ expect(forwardJourneyCookies({ cookieHeader: request.cookie })).toEqual(
136
+ `lowdefy_journey_mutant=${journeyActorToken}.${opened.cookiePayload}`
137
+ );
138
+ });
139
+ bystanderRequests.forEach((request) => {
140
+ expect(readMutantRun(request.cookie)).toBeNull();
141
+ expect(forwardJourneyCookies({ cookieHeader: request.cookie })).toEqual('');
142
+ });
143
+ } finally {
144
+ await bystander?.context.close();
145
+ await actors.closeAll();
146
+ opened.close();
147
+ }
148
+ },
149
+ 30000
150
+ );
@@ -0,0 +1,35 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { createTraceId, type } from '@lowdefy/helpers';
18
+
19
+ // The recording identity a journey run's browser contexts carry. A run the
20
+ // caller records keeps the identity it was given. Any other run gets one of
21
+ // its own that records nothing (record: false), so the errors its pages and
22
+ // requests cause are still stamped with its run and claimed by its own error
23
+ // buffer, not left in the shared stores. `by` names who started the run.
24
+ function journeyRunRecording({ recording, by }) {
25
+ if (!type.isUndefined(recording)) {
26
+ return recording;
27
+ }
28
+ return {
29
+ source: 'journey',
30
+ run: { id: createTraceId(), by: by ?? null, journey: null },
31
+ record: false,
32
+ };
33
+ }
34
+
35
+ export default journeyRunRecording;
@@ -0,0 +1,39 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { isTraceId } from '@lowdefy/helpers';
18
+
19
+ import journeyRunRecording from './journeyRunRecording.js';
20
+
21
+ test('journeyRunRecording keeps the recording a caller passes', () => {
22
+ const recording = {
23
+ source: 'journey',
24
+ run: { id: '20261003T151200Z-p0d4rm', by: 'test', journey: 'a' },
25
+ };
26
+ expect(journeyRunRecording({ recording, by: 'tool' })).toBe(recording);
27
+ });
28
+
29
+ test('journeyRunRecording gives a run with no recording a fresh identity that records nothing', () => {
30
+ const first = journeyRunRecording({ by: 'test' });
31
+ const second = journeyRunRecording({ by: 'test' });
32
+ expect(first).toEqual({
33
+ source: 'journey',
34
+ run: { id: expect.any(String), by: 'test', journey: null },
35
+ record: false,
36
+ });
37
+ expect(isTraceId(first.run.id)).toBe(true);
38
+ expect(second.run.id).not.toEqual(first.run.id);
39
+ });
@@ -0,0 +1,68 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ // The interval @hono/mcp pings its GET stream at.
18
+ const KEEP_ALIVE_MS = 30000;
19
+
20
+ // An SSE comment: every client skips it, and it resets their idle timers.
21
+ const KEEP_ALIVE_COMMENT = new TextEncoder().encode(': keep-alive\n\n');
22
+
23
+ // A tool call answered as an event stream sends nothing until it is done, and
24
+ // a journey can run for many minutes. HTTP clients end a response body that
25
+ // stays silent too long - Node's fetch after 5 minutes, which is what cut the
26
+ // lowdefy mcp shim off a long journey - and @hono/mcp keeps only its GET
27
+ // stream alive. Comments go between the transport's chunks, each of which is
28
+ // a whole event.
29
+ function keepEventStreamAlive({ response, intervalMs = KEEP_ALIVE_MS }) {
30
+ const contentType = response.headers.get('content-type') ?? '';
31
+ if (!contentType.includes('text/event-stream') || response.body === null) {
32
+ return response;
33
+ }
34
+ const reader = response.body.getReader();
35
+ let timer;
36
+ const body = new ReadableStream({
37
+ start(controller) {
38
+ timer = setInterval(() => controller.enqueue(KEEP_ALIVE_COMMENT), intervalMs);
39
+ },
40
+ async pull(controller) {
41
+ let chunk;
42
+ try {
43
+ chunk = await reader.read();
44
+ } catch (error) {
45
+ clearInterval(timer);
46
+ controller.error(error);
47
+ return;
48
+ }
49
+ if (chunk.done) {
50
+ clearInterval(timer);
51
+ controller.close();
52
+ return;
53
+ }
54
+ controller.enqueue(chunk.value);
55
+ },
56
+ cancel(reason) {
57
+ clearInterval(timer);
58
+ return reader.cancel(reason);
59
+ },
60
+ });
61
+ return new Response(body, {
62
+ status: response.status,
63
+ statusText: response.statusText,
64
+ headers: response.headers,
65
+ });
66
+ }
67
+
68
+ export default keepEventStreamAlive;
@@ -0,0 +1,77 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import keepEventStreamAlive from './keepEventStreamAlive.js';
18
+
19
+ const encoder = new TextEncoder();
20
+ const answer = 'event: message\ndata: {"jsonrpc":"2.0","id":1,"result":{}}\n\n';
21
+
22
+ // An event stream that says nothing for a while, then answers and ends.
23
+ function slowAnswer({ delayMs }) {
24
+ const body = new ReadableStream({
25
+ start(controller) {
26
+ setTimeout(() => {
27
+ controller.enqueue(encoder.encode(answer));
28
+ controller.close();
29
+ }, delayMs);
30
+ },
31
+ });
32
+ return new Response(body, { headers: { 'content-type': 'text/event-stream' } });
33
+ }
34
+
35
+ test('keepEventStreamAlive sends comments while a tool call is silent, then the answer', async () => {
36
+ // The tool call answers only once the client has read two comments, so the
37
+ // test does not depend on how promptly timers fire.
38
+ let answerNow;
39
+ const body = new ReadableStream({
40
+ start(controller) {
41
+ answerNow = () => {
42
+ controller.enqueue(encoder.encode(answer));
43
+ controller.close();
44
+ };
45
+ },
46
+ });
47
+ const response = keepEventStreamAlive({
48
+ response: new Response(body, { headers: { 'content-type': 'text/event-stream' } }),
49
+ intervalMs: 10,
50
+ });
51
+ expect(response.headers.get('content-type')).toEqual('text/event-stream');
52
+ const reader = response.body.getReader();
53
+ const decoder = new TextDecoder();
54
+ const read = async () => decoder.decode((await reader.read()).value);
55
+ expect(await read()).toEqual(': keep-alive\n\n');
56
+ expect(await read()).toEqual(': keep-alive\n\n');
57
+ answerNow();
58
+ let rest = '';
59
+ let chunk = await reader.read();
60
+ while (!chunk.done) {
61
+ rest += decoder.decode(chunk.value);
62
+ chunk = await reader.read();
63
+ }
64
+ expect(rest.endsWith(answer)).toBe(true);
65
+ });
66
+
67
+ test('keepEventStreamAlive stops its comments when the stream ends', async () => {
68
+ const response = keepEventStreamAlive({ response: slowAnswer({ delayMs: 0 }), intervalMs: 10 });
69
+ const text = await response.text();
70
+ await new Promise((resolve) => setTimeout(resolve, 50));
71
+ expect(text).toEqual(answer);
72
+ });
73
+
74
+ test('keepEventStreamAlive leaves a JSON answer alone', () => {
75
+ const response = new Response('{}', { headers: { 'content-type': 'application/json' } });
76
+ expect(keepEventStreamAlive({ response })).toBe(response);
77
+ });
@@ -0,0 +1,83 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { type } from '@lowdefy/helpers';
18
+
19
+ import installHeadlessShell from './installHeadlessShell.js';
20
+ import isHeadlessShellIncomplete from './isHeadlessShellIncomplete.js';
21
+ import registerShownTextEngine from './registerShownTextEngine.js';
22
+
23
+ // The manager tags every browser this child launches, so it can kill one
24
+ // left behind when the child is killed outright (system Chrome runs in its
25
+ // own process group and outlives a SIGKILLed parent). Chrome and the shell
26
+ // ignore switches they do not know.
27
+ function launchArgs() {
28
+ const tag = process.env.LOWDEFY_BROWSER_TAG;
29
+ if (type.isNone(tag) || tag === '') {
30
+ return [];
31
+ }
32
+ return [`--lowdefy-browser-tag=${tag}`];
33
+ }
34
+
35
+ function isExecutableMissing(error) {
36
+ return type.isString(error?.message) && error.message.includes("Executable doesn't exist");
37
+ }
38
+
39
+ // A shell whose install was cut short (a timed-out install this server
40
+ // killed, a dev server killed mid-download) has its executable but no
41
+ // completion marker, and fails to launch with some other error. It is as
42
+ // missing as one never installed, and the installer repairs it.
43
+ async function isShellMissing(error) {
44
+ if (isExecutableMissing(error)) {
45
+ return true;
46
+ }
47
+ return isHeadlessShellIncomplete();
48
+ }
49
+
50
+ // Playwright's chromium-headless-shell first: a fifth of system Chrome's
51
+ // memory per page, a launch under a second, and it exits with its parent.
52
+ // System Chrome is the fallback while the shell is missing. playwright-core
53
+ // is imported here, not at the top of the module, so the Vite child does not
54
+ // load it at boot. The journey runner's selector engine is registered first,
55
+ // so every page of the browser can resolve a `containing` target.
56
+ async function launchBrowser() {
57
+ const { chromium, selectors } = await import('playwright-core');
58
+ await registerShownTextEngine(selectors);
59
+ const args = launchArgs();
60
+ try {
61
+ return await chromium.launch({ args });
62
+ } catch (shellError) {
63
+ const install = (await isShellMissing(shellError)) ? installHeadlessShell() : null;
64
+ try {
65
+ return await chromium.launch({ channel: 'chrome', args });
66
+ } catch {
67
+ if (type.isNone(install)) {
68
+ throw shellError;
69
+ }
70
+ // No Chrome either: this call waits for the shell download, which
71
+ // gives up after a time limit (installHeadlessShell.js).
72
+ const { installed, reason } = await install;
73
+ if (!installed) {
74
+ throw new Error(
75
+ `The chromium-headless-shell install failed (${reason}), and system Chrome is not installed.`
76
+ );
77
+ }
78
+ return await chromium.launch({ args });
79
+ }
80
+ }
81
+ }
82
+
83
+ export default launchBrowser;
@@ -0,0 +1,175 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { jest } from '@jest/globals';
18
+
19
+ const mockLaunch = jest.fn();
20
+ const mockInstall = jest.fn();
21
+ const mockIsIncomplete = jest.fn(async () => false);
22
+ jest.unstable_mockModule('playwright-core', () => ({
23
+ chromium: { launch: mockLaunch },
24
+ selectors: { register: jest.fn(async () => {}) },
25
+ }));
26
+ jest.unstable_mockModule('./installHeadlessShell.js', () => ({ default: mockInstall }));
27
+ jest.unstable_mockModule('./isHeadlessShellIncomplete.js', () => ({ default: mockIsIncomplete }));
28
+
29
+ const { default: launchBrowser } = await import('./launchBrowser.js');
30
+
31
+ const shellMissing = new Error(
32
+ "browserType.launch: Executable doesn't exist at /cache/chromium_headless_shell-1217/chrome-headless-shell"
33
+ );
34
+ const chromeMissing = new Error('browserType.launch: Chromium distribution "chrome" is not found.');
35
+
36
+ function isChromeLaunch(options) {
37
+ return options.channel === 'chrome';
38
+ }
39
+
40
+ afterEach(() => {
41
+ delete process.env.LOWDEFY_BROWSER_TAG;
42
+ mockIsIncomplete.mockResolvedValue(false);
43
+ });
44
+
45
+ test('launchBrowser launches the headless shell and never system Chrome when the shell is installed', async () => {
46
+ const shell = { name: 'shell' };
47
+ mockLaunch.mockResolvedValue(shell);
48
+
49
+ expect(await launchBrowser()).toBe(shell);
50
+ expect(mockLaunch).toHaveBeenCalledTimes(1);
51
+ expect(mockLaunch.mock.calls[0][0]).toEqual({ args: [] });
52
+ expect(mockInstall).not.toHaveBeenCalled();
53
+ });
54
+
55
+ test('launchBrowser tags the browser with the tag the manager gave this child', async () => {
56
+ process.env.LOWDEFY_BROWSER_TAG = 'tag-1';
57
+ mockLaunch.mockImplementation(async (options) => {
58
+ if (isChromeLaunch(options)) return { name: 'chrome' };
59
+ throw shellMissing;
60
+ });
61
+ mockInstall.mockReturnValue(null);
62
+
63
+ await launchBrowser();
64
+
65
+ expect(mockLaunch.mock.calls[0][0]).toEqual({ args: ['--lowdefy-browser-tag=tag-1'] });
66
+ expect(mockLaunch.mock.calls[1][0]).toEqual({
67
+ channel: 'chrome',
68
+ args: ['--lowdefy-browser-tag=tag-1'],
69
+ });
70
+ });
71
+
72
+ test('launchBrowser serves the call with system Chrome while the shell installs', async () => {
73
+ const chrome = { name: 'chrome' };
74
+ mockLaunch.mockImplementation(async (options) => {
75
+ if (isChromeLaunch(options)) return chrome;
76
+ throw shellMissing;
77
+ });
78
+ // Never settles: the call must not wait for the download.
79
+ mockInstall.mockReturnValue(new Promise(() => {}));
80
+
81
+ expect(await launchBrowser()).toBe(chrome);
82
+ expect(mockInstall).toHaveBeenCalledTimes(1);
83
+ });
84
+
85
+ test('launchBrowser waits for the shell install when system Chrome is missing too', async () => {
86
+ const shell = { name: 'shell' };
87
+ let installed = false;
88
+ mockLaunch.mockImplementation(async (options) => {
89
+ if (isChromeLaunch(options)) throw chromeMissing;
90
+ if (!installed) throw shellMissing;
91
+ return shell;
92
+ });
93
+ mockInstall.mockImplementation(async () => {
94
+ installed = true;
95
+ return { installed: true };
96
+ });
97
+
98
+ expect(await launchBrowser()).toBe(shell);
99
+ expect(mockLaunch).toHaveBeenCalledTimes(3);
100
+ });
101
+
102
+ test('launchBrowser says why when both browsers are missing and the install failed or timed out', async () => {
103
+ mockLaunch.mockImplementation(async (options) => {
104
+ throw isChromeLaunch(options) ? chromeMissing : shellMissing;
105
+ });
106
+ mockInstall.mockResolvedValue({
107
+ installed: false,
108
+ reason: 'the install did not finish within 3 minutes',
109
+ });
110
+
111
+ await expect(launchBrowser()).rejects.toThrow(
112
+ 'The chromium-headless-shell install failed (the install did not finish within 3 minutes), and system Chrome is not installed.'
113
+ );
114
+ });
115
+
116
+ test('launchBrowser throws the shell error when both browsers are missing and downloads are off', async () => {
117
+ mockLaunch.mockImplementation(async (options) => {
118
+ throw isChromeLaunch(options) ? chromeMissing : shellMissing;
119
+ });
120
+ mockInstall.mockReturnValue(null);
121
+
122
+ await expect(launchBrowser()).rejects.toBe(shellMissing);
123
+ expect(mockLaunch).toHaveBeenCalledTimes(2);
124
+ });
125
+
126
+ test('launchBrowser falls back to system Chrome without installing when the shell fails for another reason', async () => {
127
+ const chrome = { name: 'chrome' };
128
+ mockLaunch.mockImplementation(async (options) => {
129
+ if (isChromeLaunch(options)) return chrome;
130
+ throw new Error('browserType.launch: Target page, context or browser has been closed');
131
+ });
132
+
133
+ expect(await launchBrowser()).toBe(chrome);
134
+ expect(mockInstall).not.toHaveBeenCalled();
135
+ });
136
+
137
+ // A killed install can leave the executable in place without Playwright's
138
+ // INSTALLATION_COMPLETE marker; the shell then fails to launch with an error
139
+ // that does not say the executable is missing.
140
+ const partialShellLaunchError = new Error(
141
+ 'browserType.launch: Browser closed.\n==================== Browser output: ====================\n<launching> /cache/chromium_headless_shell-1217/chrome-headless-shell'
142
+ );
143
+
144
+ test('launchBrowser installs the shell when the executable exists but its install never completed', async () => {
145
+ const shell = { name: 'shell' };
146
+ let installed = false;
147
+ mockLaunch.mockImplementation(async (options) => {
148
+ if (isChromeLaunch(options)) throw chromeMissing;
149
+ if (!installed) throw partialShellLaunchError;
150
+ return shell;
151
+ });
152
+ mockIsIncomplete.mockResolvedValue(true);
153
+ mockInstall.mockImplementation(async () => {
154
+ installed = true;
155
+ return { installed: true };
156
+ });
157
+
158
+ expect(await launchBrowser()).toBe(shell);
159
+ expect(mockInstall).toHaveBeenCalledTimes(1);
160
+ });
161
+
162
+ test('launchBrowser says why when an incomplete shell cannot be reinstalled and system Chrome is missing', async () => {
163
+ mockLaunch.mockImplementation(async (options) => {
164
+ throw isChromeLaunch(options) ? chromeMissing : partialShellLaunchError;
165
+ });
166
+ mockIsIncomplete.mockResolvedValue(true);
167
+ mockInstall.mockResolvedValue({
168
+ installed: false,
169
+ reason: 'the install did not finish within 3 minutes',
170
+ });
171
+
172
+ await expect(launchBrowser()).rejects.toThrow(
173
+ 'The chromium-headless-shell install failed (the install did not finish within 3 minutes), and system Chrome is not installed.'
174
+ );
175
+ });