@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
@@ -17,7 +17,7 @@
17
17
  import { Hono } from 'hono';
18
18
  import { jest } from '@jest/globals';
19
19
 
20
- const { listTabs, registerTab, requestFromTab, unregisterTab } = await import(
20
+ const { listTabs, registerTab, requestFromTab, unregisterTab, updateTabPage } = await import(
21
21
  '../../lib/docs/tabChannel.js'
22
22
  );
23
23
  const { default: devInspectHandler } = await import('./devInspect.js');
@@ -41,8 +41,13 @@ afterEach(() => {
41
41
  listTabs().forEach((tab) => unregisterTab({ id: tab.id }));
42
42
  });
43
43
 
44
+ function connectTab({ id, pageId, send = jest.fn() }) {
45
+ registerTab({ id, send });
46
+ updateTabPage({ id, pageId, pathParams: {}, instanceKey: `page:${pageId}` });
47
+ }
48
+
44
49
  test('GET /api/dev-inspect lists connected tabs', async () => {
45
- registerTab({ id: 'tab-1', pageId: 'home', send: jest.fn() });
50
+ connectTab({ id: 'tab-1', pageId: 'home' });
46
51
  const res = await createApp().request('/api/dev-inspect');
47
52
  expect(res.status).toBe(200);
48
53
  const body = await res.json();
@@ -51,7 +56,7 @@ test('GET /api/dev-inspect lists connected tabs', async () => {
51
56
  });
52
57
 
53
58
  test('POST /api/dev-inspect/page returns 403 when the Origin header is missing', async () => {
54
- registerTab({ id: 'tab-1', pageId: 'home', send: jest.fn() });
59
+ connectTab({ id: 'tab-1', pageId: 'home' });
55
60
  const res = await createApp().request('/api/dev-inspect/page', {
56
61
  method: 'POST',
57
62
  headers: { host: 'localhost:3001', 'content-type': 'application/json' },
@@ -62,17 +67,49 @@ test('POST /api/dev-inspect/page returns 403 when the Origin header is missing',
62
67
  });
63
68
 
64
69
  test('POST /api/dev-inspect/page moves a connected tab to the posted page', async () => {
65
- registerTab({ id: 'tab-1', pageId: 'home', send: jest.fn() });
70
+ connectTab({ id: 'tab-1', pageId: 'home' });
66
71
  const res = await createApp().request('/api/dev-inspect/page', {
67
72
  method: 'POST',
68
73
  headers: sameOriginHeaders,
69
- body: JSON.stringify({ tabId: 'tab-1', pageId: 'about' }),
74
+ body: JSON.stringify({ tabId: 'tab-1', pageId: 'about', instanceKey: 'page:about' }),
70
75
  });
71
76
  expect(res.status).toBe(200);
72
77
  expect(await res.json()).toEqual({ ok: true });
73
78
  expect(listTabs()[0].pageId).toBe('about');
74
79
  });
75
80
 
81
+ test('POST /api/dev-inspect/page records the page instance a tab shows', async () => {
82
+ connectTab({ id: 'tab-1', pageId: 'home' });
83
+ const res = await createApp().request('/api/dev-inspect/page', {
84
+ method: 'POST',
85
+ headers: sameOriginHeaders,
86
+ body: JSON.stringify({
87
+ tabId: 'tab-1',
88
+ pageId: 'ticket',
89
+ pathParams: { space: 's', ticket_id: '2' },
90
+ instanceKey: 'page:ticket#tickets/s/2',
91
+ }),
92
+ });
93
+ expect(res.status).toBe(200);
94
+ expect(listTabs()[0]).toMatchObject({
95
+ pageId: 'ticket',
96
+ pathParams: { space: 's', ticket_id: '2' },
97
+ instanceKey: 'page:ticket#tickets/s/2',
98
+ });
99
+ });
100
+
101
+ test('POST /api/dev-inspect/page returns 400 when the instance key is missing', async () => {
102
+ connectTab({ id: 'tab-1', pageId: 'home' });
103
+ const res = await createApp().request('/api/dev-inspect/page', {
104
+ method: 'POST',
105
+ headers: sameOriginHeaders,
106
+ body: JSON.stringify({ tabId: 'tab-1', pageId: 'about' }),
107
+ });
108
+ expect(res.status).toBe(400);
109
+ expect(await res.json()).toEqual({ error: 'Missing "pageId" or "instanceKey".' });
110
+ expect(listTabs()[0].pageId).toBe('home');
111
+ });
112
+
76
113
  test('POST /api/dev-inspect/page returns 400 when tabId is missing', async () => {
77
114
  const res = await createApp().request('/api/dev-inspect/page', {
78
115
  method: 'POST',
@@ -87,7 +124,7 @@ test('POST /api/dev-inspect/page for a tab that already disconnected returns ok'
87
124
  const res = await createApp().request('/api/dev-inspect/page', {
88
125
  method: 'POST',
89
126
  headers: sameOriginHeaders,
90
- body: JSON.stringify({ tabId: 'gone', pageId: 'about' }),
127
+ body: JSON.stringify({ tabId: 'gone', pageId: 'about', instanceKey: 'page:about' }),
91
128
  });
92
129
  expect(res.status).toBe(200);
93
130
  expect(listTabs()).toHaveLength(0);
@@ -95,7 +132,7 @@ test('POST /api/dev-inspect/page for a tab that already disconnected returns ok'
95
132
 
96
133
  test('POST /api/dev-inspect resolves a pending tab request with the posted result', async () => {
97
134
  const send = jest.fn();
98
- registerTab({ id: 'tab-1', pageId: 'home', send });
135
+ connectTab({ id: 'tab-1', pageId: 'home', send });
99
136
  const pending = requestFromTab({ pageId: 'home', event: 'inspect-request' });
100
137
  const [, { requestId }] = send.mock.calls[0];
101
138
 
@@ -0,0 +1,113 @@
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, omit, type } from '@lowdefy/helpers';
18
+
19
+ import appendRecords from '../../lib/server/recording/appendRecords.js';
20
+ import getBuildId from '../../lib/docs/getBuildId.js';
21
+ import readRecordingCookie from '../../lib/server/recording/readRecordingCookie.js';
22
+ import servedBuilds from '../../lib/server/recording/servedBuilds.js';
23
+ import { getConfigDirectory } from '../../lib/docs/checkpointPaths.js';
24
+
25
+ const MAX_RECORDS = 500;
26
+
27
+ // This route writes to disk, so unlike localDevToolsOnly it does not let a
28
+ // request with no Origin through: only the recorder in a same-origin dev tab
29
+ // posts here. Agents and curl have no reason to.
30
+ function isSameOrigin(c) {
31
+ const origin = c.req.header('origin');
32
+ if (!origin) {
33
+ return false;
34
+ }
35
+ try {
36
+ return new URL(origin).host === c.req.header('host');
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ async function readBody(c) {
43
+ try {
44
+ return await c.req.json();
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ function isValidBody(body) {
51
+ return (
52
+ type.isObject(body) &&
53
+ isTraceId(body.session) &&
54
+ type.isArray(body.records) &&
55
+ body.records.length <= MAX_RECORDS &&
56
+ body.records.every((record) => type.isObject(record))
57
+ );
58
+ }
59
+
60
+ // The page names the build of the config it rendered when each interaction
61
+ // happened (jitPage.js serves it as `_buildId`). It is kept when this process
62
+ // served it, else (missing, unknown, from before a restart) replaced with the
63
+ // build served now. The cookie decides source and run, never the page: a
64
+ // headless journey run is marked by the dev server's own browser,
65
+ // and everything else records as dev.
66
+ function stampRecord({ record, currentBuild, cookie }) {
67
+ const rest = omit({ ...record }, ['source', 'run']);
68
+ const build = servedBuilds.has(record.build) ? record.build : currentBuild;
69
+ if (cookie === null) {
70
+ return { ...rest, build, source: 'dev' };
71
+ }
72
+ return { ...rest, build, source: cookie.source, run: cookie.run };
73
+ }
74
+
75
+ // POST /api/dev-recording: the dev recorder (client/Recorder.jsx) posts
76
+ // batches of interaction records here. Each keeps the build its page ran on
77
+ // (or the build served now) and is appended to the trace file of its tab
78
+ // session (dev) or run (journey).
79
+ async function devRecordingHandler(c) {
80
+ if (!isSameOrigin(c)) {
81
+ return c.json({ error: 'Forbidden' }, 403);
82
+ }
83
+ if (process.env.LOWDEFY_DEV_RECORD === 'false') {
84
+ return c.body(null, 204);
85
+ }
86
+ const cookie = readRecordingCookie(c.req.header('cookie'));
87
+ if (cookie === 'off' || cookie?.record === false) {
88
+ return c.body(null, 204);
89
+ }
90
+ const body = await readBody(c);
91
+ if (!isValidBody(body)) {
92
+ return c.json(
93
+ {
94
+ error: `Expected { session, records } with a trace id session and at most ${MAX_RECORDS} record objects.`,
95
+ },
96
+ 400
97
+ );
98
+ }
99
+ if (body.records.length === 0) {
100
+ return c.body(null, 204);
101
+ }
102
+ const currentBuild = getBuildId();
103
+ const records = body.records.map((record) => stampRecord({ record, currentBuild, cookie }));
104
+ appendRecords({
105
+ configDirectory: getConfigDirectory(),
106
+ source: cookie === null ? 'dev' : cookie.source,
107
+ id: cookie === null ? body.session : cookie.run.id,
108
+ records,
109
+ });
110
+ return c.body(null, 204);
111
+ }
112
+
113
+ export default devRecordingHandler;
@@ -0,0 +1,253 @@
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 fs from 'fs';
18
+ import os from 'os';
19
+ import path from 'path';
20
+ import { Hono } from 'hono';
21
+ import { bodyLimit } from 'hono/body-limit';
22
+ import { jest } from '@jest/globals';
23
+
24
+ jest.unstable_mockModule('../../lib/docs/getBuildId.js', () => ({
25
+ default: () => '2026-10-03T14:02:00.000Z',
26
+ }));
27
+
28
+ const { default: devRecordingHandler } = await import('./devRecording.js');
29
+ const { default: servedBuilds } = await import('../../lib/server/recording/servedBuilds.js');
30
+ const { default: localDevToolsOnly } = await import('../middleware/localDevToolsOnly.js');
31
+ const { JOURNEY_COOKIES, writeJourneyCookie } = await import('../../lib/server/journeyCookies.js');
32
+ const { default: recordingCookiePayload } = await import(
33
+ '../../lib/server/recording/recordingCookiePayload.js'
34
+ );
35
+
36
+ const SESSION = '20261003T140311Z-k3x9qa';
37
+ const RUN_ID = '20261003T151200Z-p0d4rm';
38
+ const origin = 'http://localhost:3001';
39
+ let configDirectory;
40
+
41
+ function createApp() {
42
+ const app = new Hono();
43
+ app.use('/api/dev-recording', localDevToolsOnly());
44
+ app.post('/api/dev-recording', bodyLimit({ maxSize: 1024 * 1024 }), devRecordingHandler);
45
+ return app;
46
+ }
47
+
48
+ function record(extra = {}) {
49
+ return {
50
+ v: 1,
51
+ session: SESSION,
52
+ t: '2026-10-03T14:03:12.000Z',
53
+ kind: 'click',
54
+ scope: 'page',
55
+ page_id: 'tickets',
56
+ event: null,
57
+ ...extra,
58
+ };
59
+ }
60
+
61
+ function cookieFor(payload) {
62
+ const cookie = writeJourneyCookie({ name: JOURNEY_COOKIES.recording.name, payload, origin });
63
+ return `${cookie.name}=${cookie.value}`;
64
+ }
65
+
66
+ function post({ body, headers = {} }) {
67
+ return createApp().request('/api/dev-recording', {
68
+ method: 'POST',
69
+ headers: {
70
+ host: 'localhost:3001',
71
+ origin,
72
+ 'content-type': 'application/json',
73
+ ...headers,
74
+ },
75
+ body: typeof body === 'string' ? body : JSON.stringify(body),
76
+ });
77
+ }
78
+
79
+ function readLines(...segments) {
80
+ const filePath = path.join(configDirectory, '.lowdefy', 'traces', ...segments);
81
+ return fs
82
+ .readFileSync(filePath, 'utf8')
83
+ .split('\n')
84
+ .filter((line) => line !== '')
85
+ .map((line) => JSON.parse(line));
86
+ }
87
+
88
+ function tracesExist() {
89
+ return fs.existsSync(path.join(configDirectory, '.lowdefy', 'traces'));
90
+ }
91
+
92
+ beforeEach(() => {
93
+ configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-dev-recording-'));
94
+ process.env.LOWDEFY_DIRECTORY_CONFIG = configDirectory;
95
+ delete process.env.LOWDEFY_DEV_RECORD;
96
+ });
97
+
98
+ afterEach(() => {
99
+ fs.rmSync(configDirectory, { recursive: true, force: true });
100
+ delete process.env.LOWDEFY_DIRECTORY_CONFIG;
101
+ delete process.env.LOWDEFY_DEV_RECORD;
102
+ });
103
+
104
+ test('POST /api/dev-recording refuses a cross-site request', async () => {
105
+ const res = await post({
106
+ body: { session: SESSION, records: [record()] },
107
+ headers: { origin: 'https://evil.example' },
108
+ });
109
+ expect(res.status).toBe(403);
110
+ expect(tracesExist()).toBe(false);
111
+ });
112
+
113
+ test('POST /api/dev-recording refuses a request with no Origin', async () => {
114
+ const res = await createApp().request('/api/dev-recording', {
115
+ method: 'POST',
116
+ headers: { host: 'localhost:3001', 'content-type': 'application/json' },
117
+ body: JSON.stringify({ session: SESSION, records: [record()] }),
118
+ });
119
+ expect(res.status).toBe(403);
120
+ expect(tracesExist()).toBe(false);
121
+ });
122
+
123
+ test('POST /api/dev-recording answers 400 for a session id that is not a trace id', async () => {
124
+ for (const session of ['../x', '', `${SESSION}/../../x`]) {
125
+ const res = await post({ body: { session, records: [record()] } });
126
+ expect(res.status).toBe(400);
127
+ }
128
+ expect(tracesExist()).toBe(false);
129
+ });
130
+
131
+ test('POST /api/dev-recording answers 400 for more than 500 records or non-object records', async () => {
132
+ const many = Array.from({ length: 501 }, () => record());
133
+ expect((await post({ body: { session: SESSION, records: many } })).status).toBe(400);
134
+ expect((await post({ body: { session: SESSION, records: ['x'] } })).status).toBe(400);
135
+ expect((await post({ body: 'not json' })).status).toBe(400);
136
+ expect(tracesExist()).toBe(false);
137
+ });
138
+
139
+ test('POST /api/dev-recording refuses a body over 1 MiB', async () => {
140
+ const big = 'x'.repeat(1024 * 1024 + 1);
141
+ const res = await post({ body: { session: SESSION, records: [record({ big })] } });
142
+ expect(res.status).toBe(413);
143
+ expect(tracesExist()).toBe(false);
144
+ });
145
+
146
+ test('POST /api/dev-recording writes nothing when LOWDEFY_DEV_RECORD is false', async () => {
147
+ process.env.LOWDEFY_DEV_RECORD = 'false';
148
+ const res = await post({ body: { session: SESSION, records: [record()] } });
149
+ expect(res.status).toBe(204);
150
+ expect(tracesExist()).toBe(false);
151
+ });
152
+
153
+ test('POST /api/dev-recording writes nothing when the recording cookie is off', async () => {
154
+ const res = await post({
155
+ body: { session: SESSION, records: [record()] },
156
+ headers: { cookie: cookieFor('off') },
157
+ });
158
+ expect(res.status).toBe(204);
159
+ expect(tracesExist()).toBe(false);
160
+ });
161
+
162
+ test('POST /api/dev-recording writes nothing for a journey run that records nothing', async () => {
163
+ const payload = recordingCookiePayload({
164
+ recording: {
165
+ source: 'journey',
166
+ run: { id: '20261003T160000Z-77abcd', by: 'agent', journey: 'save-ticket' },
167
+ record: false,
168
+ },
169
+ });
170
+ const res = await post({
171
+ body: { session: SESSION, records: [record()] },
172
+ headers: { cookie: cookieFor(payload) },
173
+ });
174
+ expect(res.status).toBe(204);
175
+ expect(tracesExist()).toBe(false);
176
+ });
177
+
178
+ test('POST /api/dev-recording appends lines to the dev session file, stamped with build and source', async () => {
179
+ await post({ body: { session: SESSION, records: [record(), record({ kind: 'change' })] } });
180
+ const res = await post({
181
+ body: { session: SESSION, records: [record({ source: 'journey', run: { id: RUN_ID } })] },
182
+ });
183
+ expect(res.status).toBe(204);
184
+ const lines = readLines('dev', '2026-10-03', `${SESSION}.jsonl`);
185
+ expect(lines).toHaveLength(3);
186
+ lines.forEach((line) => {
187
+ expect(line.build).toBe('2026-10-03T14:02:00.000Z');
188
+ expect(line.source).toBe('dev');
189
+ expect(line).not.toHaveProperty('run');
190
+ });
191
+ expect(lines[1].kind).toBe('change');
192
+ });
193
+
194
+ test('POST /api/dev-recording writes a journey cookie run to its run file, overriding a client source', async () => {
195
+ const run = {
196
+ id: RUN_ID,
197
+ by: 'test',
198
+ journey: 'tests/journeys/tickets.yaml#Assign a ticket',
199
+ actor: 'main',
200
+ };
201
+ const res = await post({
202
+ body: { session: SESSION, records: [record({ source: 'dev' })] },
203
+ headers: {
204
+ cookie: cookieFor(recordingCookiePayload({ recording: { source: 'journey', run } })),
205
+ },
206
+ });
207
+ expect(res.status).toBe(204);
208
+ const [line] = readLines('journey', '2026-10-03', `${RUN_ID}.jsonl`);
209
+ expect(line).toMatchObject({ source: 'journey', run, session: SESSION });
210
+ expect(fs.existsSync(path.join(configDirectory, '.lowdefy', 'traces', 'dev'))).toBe(false);
211
+ });
212
+
213
+ test('POST /api/dev-recording records a forged recording cookie as dev', async () => {
214
+ const payload = recordingCookiePayload({
215
+ recording: { source: 'journey', run: { id: RUN_ID, by: 'test' } },
216
+ });
217
+ const res = await post({
218
+ body: { session: SESSION, records: [record()] },
219
+ headers: { cookie: `lowdefy_recording=forged-token.${payload}` },
220
+ });
221
+ expect(res.status).toBe(204);
222
+ const [line] = readLines('dev', '2026-10-03', `${SESSION}.jsonl`);
223
+ expect(line.source).toBe('dev');
224
+ });
225
+
226
+ test('POST /api/dev-recording keeps a build the process served a page config under', async () => {
227
+ servedBuilds.add('2026-10-03T13:58:00.000Z');
228
+ const res = await post({
229
+ body: { session: SESSION, records: [record({ build: '2026-10-03T13:58:00.000Z' })] },
230
+ });
231
+ expect(res.status).toBe(204);
232
+ const [line] = readLines('dev', '2026-10-03', `${SESSION}.jsonl`);
233
+ expect(line.build).toBe('2026-10-03T13:58:00.000Z');
234
+ });
235
+
236
+ test('POST /api/dev-recording replaces a missing, unknown or non-string build with the build served now', async () => {
237
+ servedBuilds.add('2026-10-03T13:58:00.000Z');
238
+ const res = await post({
239
+ body: {
240
+ session: SESSION,
241
+ records: [
242
+ record(),
243
+ record({ build: null }),
244
+ record({ build: '2026-10-03T09:00:00.000Z' }),
245
+ record({ build: 42 }),
246
+ record({ build: { id: '2026-10-03T13:58:00.000Z' } }),
247
+ ],
248
+ },
249
+ });
250
+ expect(res.status).toBe(204);
251
+ const lines = readLines('dev', '2026-10-03', `${SESSION}.jsonl`);
252
+ expect(lines.map((line) => line.build)).toEqual(Array(5).fill('2026-10-03T14:02:00.000Z'));
253
+ });
@@ -15,9 +15,17 @@
15
15
  */
16
16
 
17
17
  import getBuildStatus from '../../../lib/docs/getBuildStatus.js';
18
+ import getBuildStatusAfterEdits from '../../../lib/docs/getBuildStatusAfterEdits.js';
19
+ import readProxyBuildWait from '../../../lib/docs/readProxyBuildWait.js';
18
20
 
19
- function docsBuildStatusHandler(c) {
20
- return c.json(getBuildStatus());
21
+ // ?wait=true answers once the dev server has processed the latest edits and
22
+ // built the pages they touched.
23
+ async function docsBuildStatusHandler(c) {
24
+ if (c.req.query('wait') === 'true') {
25
+ const proxyWait = readProxyBuildWait({ getHeader: (name) => c.req.header(name) });
26
+ return c.json(await getBuildStatusAfterEdits({ proxyWait }));
27
+ }
28
+ return c.json(await getBuildStatus());
21
29
  }
22
30
 
23
31
  export default docsBuildStatusHandler;
@@ -0,0 +1,46 @@
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
+ import { Hono } from 'hono';
19
+
20
+ jest.unstable_mockModule('../../../lib/docs/clientErrorStore.js', () => ({
21
+ default: { list: () => [] },
22
+ }));
23
+ jest.unstable_mockModule('../../../lib/docs/serverErrorStore.js', () => ({
24
+ default: { list: () => [] },
25
+ }));
26
+ jest.unstable_mockModule('../../../lib/docs/getBuildId.js', () => ({
27
+ default: () => '2026-10-03T08:00:00.000Z',
28
+ }));
29
+ jest.unstable_mockModule('../../../lib/docs/getPageBuildStatus.js', () => ({
30
+ default: () => ({ unbuilt: 0 }),
31
+ }));
32
+ jest.unstable_mockModule('../../../lib/docs/readBuildArtifact.js', () => ({
33
+ default: () => ({ status: 'ok', errors: [], warnings: [] }),
34
+ }));
35
+
36
+ const { default: docsBuildStatusHandler } = await import('./buildStatus.js');
37
+
38
+ test('GET /lowdefy-docs/build-status includes the buildId of the build being served', async () => {
39
+ const app = new Hono();
40
+ app.get('/lowdefy-docs/build-status', docsBuildStatusHandler);
41
+
42
+ const res = await app.request('/lowdefy-docs/build-status');
43
+
44
+ expect(res.status).toBe(200);
45
+ expect((await res.json()).buildId).toBe('2026-10-03T08:00:00.000Z');
46
+ });
@@ -14,14 +14,16 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import getCoreDoc from '../../../lib/docs/getCoreDoc.js';
17
+ import getDoc from '../../../lib/docs/getDoc.js';
18
18
 
19
19
  function docsContentHandler(c) {
20
20
  const slug = c.req.param('slug');
21
- const doc = getCoreDoc({ slug });
21
+ const doc = getDoc({ slug });
22
22
  if (doc === null) {
23
23
  return c.json(
24
- { error: `No doc found for slug "${slug}". Use GET /lowdefy-docs/search?q=... to find the right slug.` },
24
+ {
25
+ error: `No doc found for slug "${slug}". Use GET /lowdefy-docs/search?q=... to find the right slug.`,
26
+ },
25
27
  404
26
28
  );
27
29
  }
@@ -15,6 +15,7 @@
15
15
  */
16
16
 
17
17
  import evalOperator from '../../../lib/docs/evalOperator.js';
18
+ import parsePathParamsParam from './parsePathParamsParam.js';
18
19
  import parseUserParam from './parseUserParam.js';
19
20
 
20
21
  async function docsEvalOperatorHandler(c) {
@@ -42,7 +43,20 @@ async function docsEvalOperatorHandler(c) {
42
43
  return c.json({ error: userError }, 400);
43
44
  }
44
45
 
45
- const result = await evalOperator({ origin, pageId, expression, source, user });
46
+ const { pathParams, error: pathParamsError } = parsePathParamsParam({ value: body.pathParams });
47
+ if (pathParamsError) {
48
+ return c.json({ error: pathParamsError }, 400);
49
+ }
50
+
51
+ const result = await evalOperator({
52
+ origin,
53
+ pageId,
54
+ pathParams,
55
+ expression,
56
+ source,
57
+ user,
58
+ data: body.data,
59
+ });
46
60
  if (result.error) {
47
61
  // A contradictory call (`user` with `source: 'tab'`) is the caller's
48
62
  // mistake, not a failed render — 502 would read as "the renderer broke" and
@@ -0,0 +1,50 @@
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 { streamSSE } from 'hono/streaming';
18
+
19
+ import { bootedAt, subscribe } from '../../../lib/docs/devEventBus.js';
20
+
21
+ // SSE push channel for agents without MCP support: the same dev events the MCP
22
+ // endpoint delivers as notifications/message, one SSE frame each, named by
23
+ // event type. The first frame is always the restart event so a reconnecting
24
+ // client can tell a server restart from a dropped connection.
25
+ async function docsEventsHandler(c) {
26
+ return streamSSE(c, async (stream) => {
27
+ let open = true;
28
+ const writeEvent = (event) =>
29
+ stream.writeSSE({ event: event.type, data: JSON.stringify(event) });
30
+
31
+ // Subscribe and arm the abort hook before the first await: onAbort does not
32
+ // fire for a listener added after the client has already gone.
33
+ const unsubscribe = subscribe(writeEvent);
34
+ stream.onAbort(() => {
35
+ open = false;
36
+ unsubscribe();
37
+ });
38
+
39
+ await writeEvent({ type: 'restart', timestamp: new Date().toISOString(), bootedAt });
40
+
41
+ while (open) {
42
+ await stream.sleep(15000);
43
+ if (open) {
44
+ await stream.writeSSE({ event: 'ping', data: '' });
45
+ }
46
+ }
47
+ });
48
+ }
49
+
50
+ export default docsEventsHandler;