@lowdefy/server-dev 6.0.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 (625) 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 +63 -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 +224 -39
  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/getAuth.js +46 -0
  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/{getAuthConfig.js → getStrategies.js} +12 -10
  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 +174 -41
  348. package/lib/server/createLowdefyContext.test.mjs +437 -0
  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 +77 -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/scrubSecrets.js +23 -0
  428. package/lib/server/sinkExternalConnections.js +95 -0
  429. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  430. package/lib/server/skipStaleMapWrites.js +62 -0
  431. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  432. package/lib/server/startIdleGc.js +67 -0
  433. package/lib/server/startIdleGc.test.mjs +138 -0
  434. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  435. package/lib/server/trackSessionWork.js +30 -0
  436. package/lib/vite/createDevServerExclude.js +40 -0
  437. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  438. package/manager/getContext.mjs +24 -2
  439. package/manager/processes/initialBuild.mjs +14 -0
  440. package/manager/processes/initialBuild.test.mjs +101 -0
  441. package/manager/processes/lowdefyBuild.mjs +30 -7
  442. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
  443. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  444. package/manager/processes/optimizeDependencies.mjs +59 -0
  445. package/manager/processes/restartServer.mjs +34 -6
  446. package/manager/processes/restartServer.test.mjs +162 -0
  447. package/manager/processes/shutdownServer.mjs +11 -0
  448. package/manager/processes/shutdownServer.test.mjs +45 -0
  449. package/manager/processes/startFirstServer.mjs +46 -0
  450. package/manager/processes/startFirstServer.test.mjs +326 -0
  451. package/manager/processes/startMailSink.mjs +135 -0
  452. package/manager/processes/startMailSink.test.mjs +146 -0
  453. package/manager/processes/startProxy.mjs +129 -12
  454. package/manager/processes/startProxy.test.mjs +366 -0
  455. package/manager/processes/startServer.mjs +47 -6
  456. package/manager/processes/startServer.test.mjs +155 -0
  457. package/manager/processes/startWatchers.mjs +6 -4
  458. package/manager/processes/syncServer.mjs +69 -0
  459. package/manager/processes/syncServer.test.mjs +141 -0
  460. package/manager/processes/waitForDevServer.mjs +48 -0
  461. package/manager/processes/waitForDevServer.test.mjs +112 -0
  462. package/manager/run.mjs +128 -43
  463. package/manager/utils/BatchChanges.mjs +21 -5
  464. package/manager/utils/BatchChanges.test.mjs +77 -0
  465. package/manager/utils/acquireDevInstance.mjs +99 -0
  466. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  467. package/manager/utils/createBuildActivity.mjs +67 -0
  468. package/manager/utils/createBuildActivity.test.mjs +58 -0
  469. package/manager/utils/createChangeSignal.mjs +39 -0
  470. package/manager/utils/createChangeSignal.test.mjs +62 -0
  471. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  472. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  473. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  474. package/manager/utils/createDotPathIgnore.mjs +44 -0
  475. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  476. package/manager/utils/createIdleGc.mjs +51 -0
  477. package/manager/utils/createIdleGc.test.mjs +77 -0
  478. package/manager/utils/createRequestActivity.mjs +100 -0
  479. package/manager/utils/createRequestActivity.test.mjs +121 -0
  480. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  481. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  482. package/manager/utils/createServerEnv.mjs +39 -0
  483. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  485. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  486. package/manager/utils/importFresh.mjs +46 -0
  487. package/manager/utils/importFresh.test.mjs +72 -0
  488. package/manager/utils/importFreshWorker.mjs +20 -0
  489. package/manager/utils/killTaggedBrowser.mjs +35 -0
  490. package/manager/utils/pagePathChanged.mjs +56 -0
  491. package/manager/utils/pruneRecordings.mjs +85 -0
  492. package/manager/utils/pruneRecordings.test.mjs +117 -0
  493. package/manager/utils/publishBuildDirectory.mjs +121 -0
  494. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  495. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  496. package/manager/utils/readBasePath.mjs +27 -0
  497. package/manager/utils/readBasePath.test.mjs +43 -0
  498. package/manager/utils/readBuildStatusWait.mjs +67 -0
  499. package/manager/utils/readManagerVersion.mjs +30 -0
  500. package/manager/utils/readManagerVersion.test.mjs +35 -0
  501. package/manager/utils/readPluginDefinitions.mjs +36 -0
  502. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  503. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  504. package/manager/utils/resolvePorts.mjs +49 -0
  505. package/manager/utils/setupWatcher.mjs +19 -11
  506. package/manager/utils/startRecordingPruner.mjs +45 -0
  507. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  508. package/manager/utils/waitForChildExit.mjs +53 -0
  509. package/manager/utils/waitForChildExit.test.mjs +70 -0
  510. package/manager/utils/waitForServer.mjs +44 -0
  511. package/manager/utils/writeBuildStatus.mjs +1 -1
  512. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  513. package/manager/watchers/envWatcher.mjs +9 -2
  514. package/manager/watchers/envWatcher.test.mjs +87 -0
  515. package/manager/watchers/listServerSidePackages.mjs +47 -0
  516. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  517. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  518. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  519. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  520. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  521. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  522. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  523. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  524. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  525. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  526. package/package.json +61 -49
  527. package/package.original.json +61 -49
  528. package/src/app.js +131 -20
  529. package/src/html/renderDevPage.js +63 -11
  530. package/src/html/renderDevPage.test.mjs +138 -0
  531. package/src/lib/getRequestPath.js +29 -0
  532. package/src/middleware/createSameOriginGuard.js +37 -0
  533. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  534. package/src/middleware/errorHandler.js +51 -1
  535. package/src/middleware/errorHandler.test.mjs +196 -14
  536. package/src/middleware/isRebindingSafeHost.js +44 -0
  537. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  538. package/src/middleware/localDevToolsOnly.js +38 -0
  539. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  540. package/src/middleware/noStore.js +27 -0
  541. package/src/middleware/noStore.test.mjs +48 -0
  542. package/src/middleware/requestTimeout.js +38 -0
  543. package/src/middleware/staleFlag.js +76 -0
  544. package/src/middleware/staleFlag.test.mjs +127 -0
  545. package/src/middleware/stampCaller.js +38 -0
  546. package/src/middleware/stampCaller.test.mjs +64 -0
  547. package/src/routes/agent.js +15 -3
  548. package/src/routes/auth.js +29 -20
  549. package/src/routes/auth.test.mjs +30 -56
  550. package/src/routes/clientError.js +13 -13
  551. package/src/routes/cron.js +3 -1
  552. package/src/routes/cronForward.js +4 -2
  553. package/src/routes/detached.js +22 -9
  554. package/src/routes/devInspect.js +17 -8
  555. package/src/routes/devInspect.test.mjs +44 -7
  556. package/src/routes/devRecording.js +113 -0
  557. package/src/routes/devRecording.test.mjs +253 -0
  558. package/src/routes/docs/buildStatus.js +10 -2
  559. package/src/routes/docs/buildStatus.test.mjs +46 -0
  560. package/src/routes/docs/content.js +5 -3
  561. package/src/routes/docs/evalOperator.js +15 -1
  562. package/src/routes/docs/events.js +50 -0
  563. package/src/routes/docs/events.test.mjs +117 -0
  564. package/src/routes/docs/find.js +9 -1
  565. package/src/routes/docs/icons.js +30 -0
  566. package/src/routes/docs/inspectState.js +16 -1
  567. package/src/routes/docs/journey.js +194 -0
  568. package/src/routes/docs/journey.test.mjs +401 -0
  569. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  570. package/src/routes/docs/loadState.js +4 -2
  571. package/src/routes/docs/mcp.js +34 -3
  572. package/src/routes/docs/mcp.test.mjs +130 -0
  573. package/src/routes/docs/mutants.js +78 -0
  574. package/src/routes/docs/mutants.test.mjs +180 -0
  575. package/src/routes/docs/parsePathParamsParam.js +41 -0
  576. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  577. package/src/routes/docs/parseUserParam.js +27 -21
  578. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  579. package/src/routes/docs/restart.js +35 -0
  580. package/src/routes/docs/restart.test.mjs +64 -0
  581. package/src/routes/docs/runEndpoint.js +54 -0
  582. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  583. package/src/routes/docs/runRequest.js +25 -8
  584. package/src/routes/docs/runRequest.test.mjs +92 -0
  585. package/src/routes/docs/screenshot.js +50 -2
  586. package/src/routes/docs/screenshot.test.mjs +117 -0
  587. package/src/routes/docs/search.js +15 -2
  588. package/src/routes/docs/snapshotState.js +17 -3
  589. package/src/routes/endpoints.js +27 -26
  590. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  591. package/src/routes/feedback.js +10 -13
  592. package/src/routes/jitPage.js +99 -35
  593. package/src/routes/jitPage.test.mjs +248 -0
  594. package/src/routes/mcp.js +82 -5
  595. package/src/routes/mcp.test.mjs +215 -0
  596. package/src/routes/mcpMemberToken.test.mjs +125 -0
  597. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  598. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  599. package/src/routes/mountDevPageRoutes.js +25 -0
  600. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  601. package/src/routes/mountOauthDiscovery.js +44 -0
  602. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  604. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  605. package/src/routes/reload.js +16 -0
  606. package/src/routes/request.js +6 -4
  607. package/src/routes/request.test.mjs +9 -5
  608. package/src/routes/requestBody.test.mjs +159 -0
  609. package/src/routes/usage.js +6 -2
  610. package/src/routes/user.js +27 -0
  611. package/src/routes/websocket.js +29 -8
  612. package/src/routes/websocket.test.mjs +65 -0
  613. package/src/routes/wellKnownFallback.js +30 -0
  614. package/src/routes/wellKnownFallback.test.mjs +32 -0
  615. package/src/websocket/devWebSocket.js +41 -26
  616. package/src/websocket/devWebSocket.test.mjs +157 -0
  617. package/vite.config.js +53 -23
  618. package/lib/server/auth/getDevSession.js +0 -70
  619. package/lib/server/auth/getDevSession.test.mjs +0 -138
  620. package/lib/server/auth/session.js +0 -39
  621. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  622. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  623. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  624. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  625. package/src/routes/apiPage.js +0 -37
@@ -15,13 +15,14 @@
15
15
  */
16
16
 
17
17
  import { Hono } from 'hono';
18
+ import { HTTPException } from 'hono/http-exception';
18
19
  import { jest } from '@jest/globals';
19
20
 
20
21
  import createErrorHandler from './errorHandler.js';
21
22
 
22
- // This is the only test coverage of the hono 500 redaction path: the file here is
23
- // byte-identical to its `server` and `server-e2e` counterparts, and `server` has no
24
- // test script at all. Keep the three in sync when changing any of them.
23
+ // The handler here is byte-identical to its `server-e2e` counterpart (and to
24
+ // `server`'s apart from the Sentry capture, which `server` tests separately).
25
+ // Keep the three in sync when changing any of them.
25
26
 
26
27
  // A string that must never reach a client. It is put on `received` at every
27
28
  // level of the cause chain, so a single JSON.stringify search over the body
@@ -55,6 +56,27 @@ function createAuthenticationError(message) {
55
56
  return error;
56
57
  }
57
58
 
59
+ function createAuthorizationError(message) {
60
+ const error = new Error(message);
61
+ error.name = 'AuthorizationError';
62
+ error.received = SECRET;
63
+ return error;
64
+ }
65
+
66
+ function createTwoFactorEnrolmentRequiredError(message) {
67
+ const error = new Error(message);
68
+ error.name = 'TwoFactorEnrolmentRequiredError';
69
+ error.received = SECRET;
70
+ return error;
71
+ }
72
+
73
+ function createUserError(message) {
74
+ const error = new Error(message);
75
+ error.name = 'UserError';
76
+ error.received = SECRET;
77
+ return error;
78
+ }
79
+
58
80
  // Two Error levels, each with a stack (every Error has one) and a recognisable
59
81
  // `received`, plus a configKey the policy keeps.
60
82
  function createErrorWithCause() {
@@ -105,6 +127,154 @@ test('errorHandler returns text Unauthorized at 401 for an AuthenticationError o
105
127
  expect(logger.warn).toHaveBeenCalledTimes(1);
106
128
  });
107
129
 
130
+ test('errorHandler returns 403 with only name and message for an AuthorizationError on an api path', async () => {
131
+ const logger = createLogger();
132
+ const context = { handleError: jest.fn() };
133
+ const res = await createApp({
134
+ context,
135
+ error: createAuthorizationError('Forbidden.'),
136
+ logger,
137
+ }).request('/api/request/getUsers');
138
+
139
+ expect(res.status).toEqual(403);
140
+ expect(await res.json()).toEqual({ name: 'AuthorizationError', message: 'Forbidden.' });
141
+ expect(logger.warn).toHaveBeenCalledTimes(1);
142
+ expect(logger.warn.mock.calls[0][0]).toMatch(/Forbidden: GET \/api\/request\/getUsers/);
143
+ expect(logger.error).not.toHaveBeenCalled();
144
+ expect(context.handleError).not.toHaveBeenCalled();
145
+ });
146
+
147
+ test('errorHandler does not send an AuthorizationError through the redactor', async () => {
148
+ const res = await createApp({
149
+ error: createAuthorizationError('Forbidden.'),
150
+ logger: createLogger(),
151
+ }).request('/api/request/getUsers');
152
+
153
+ const body = await res.json();
154
+ expect(body['~e']).toBeUndefined();
155
+ expect(JSON.stringify(body)).not.toContain(SECRET);
156
+ });
157
+
158
+ test('errorHandler returns text Forbidden at 403 for an AuthorizationError on a page path', async () => {
159
+ const logger = createLogger();
160
+ const res = await createApp({
161
+ error: createAuthorizationError('Forbidden.'),
162
+ logger,
163
+ }).request('/home');
164
+
165
+ expect(res.status).toEqual(403);
166
+ expect(await res.text()).toEqual('Forbidden');
167
+ expect(logger.warn).toHaveBeenCalledTimes(1);
168
+ expect(logger.error).not.toHaveBeenCalled();
169
+ });
170
+
171
+ test('errorHandler returns 403 with only name and message for a TwoFactorEnrolmentRequiredError on an api path', async () => {
172
+ const logger = createLogger();
173
+ const context = { handleError: jest.fn() };
174
+ const res = await createApp({
175
+ context,
176
+ error: createTwoFactorEnrolmentRequiredError('Two-factor enrolment required.'),
177
+ logger,
178
+ }).request('/api/request/getUsers');
179
+
180
+ expect(res.status).toEqual(403);
181
+ expect(await res.json()).toEqual({
182
+ name: 'TwoFactorEnrolmentRequiredError',
183
+ message: 'Two-factor enrolment required.',
184
+ });
185
+ expect(logger.warn).toHaveBeenCalledTimes(1);
186
+ expect(logger.warn.mock.calls[0][0]).toMatch(/Two-factor enrolment required/);
187
+ expect(logger.error).not.toHaveBeenCalled();
188
+ expect(context.handleError).not.toHaveBeenCalled();
189
+ });
190
+
191
+ test('errorHandler does not send a TwoFactorEnrolmentRequiredError through the redactor', async () => {
192
+ const res = await createApp({
193
+ error: createTwoFactorEnrolmentRequiredError('Two-factor enrolment required.'),
194
+ logger: createLogger(),
195
+ }).request('/api/request/getUsers');
196
+
197
+ const body = await res.json();
198
+ expect(body['~e']).toBeUndefined();
199
+ });
200
+
201
+ test('errorHandler returns text at 403 for a TwoFactorEnrolmentRequiredError on a page path', async () => {
202
+ const logger = createLogger();
203
+ const context = { handleError: jest.fn() };
204
+ const res = await createApp({
205
+ context,
206
+ error: createTwoFactorEnrolmentRequiredError('Two-factor enrolment required.'),
207
+ logger,
208
+ }).request('/home');
209
+
210
+ expect(res.status).toEqual(403);
211
+ expect(await res.text()).toEqual('Two-factor enrolment required');
212
+ expect(logger.warn).toHaveBeenCalledTimes(1);
213
+ expect(context.handleError).not.toHaveBeenCalled();
214
+ });
215
+
216
+ test('errorHandler returns 400 with only name and message for a UserError on an api path', async () => {
217
+ const logger = createLogger();
218
+ const context = { handleError: jest.fn() };
219
+ const res = await createApp({
220
+ context,
221
+ error: createUserError('Payload for endpoint "x" does not match its payloadSchema.'),
222
+ logger,
223
+ }).request('/api/endpoints/x', { method: 'POST' });
224
+
225
+ expect(res.status).toEqual(400);
226
+ expect(await res.json()).toEqual({
227
+ name: 'UserError',
228
+ message: 'Payload for endpoint "x" does not match its payloadSchema.',
229
+ });
230
+ expect(logger.warn).toHaveBeenCalledTimes(1);
231
+ expect(logger.warn.mock.calls[0][0]).toMatch(/Bad request: POST \/api\/endpoints\/x/);
232
+ expect(logger.error).not.toHaveBeenCalled();
233
+ expect(context.handleError).not.toHaveBeenCalled();
234
+ });
235
+
236
+ test('errorHandler does not send a UserError through the redactor', async () => {
237
+ const res = await createApp({
238
+ error: createUserError('Bad payload.'),
239
+ logger: createLogger(),
240
+ }).request('/api/endpoints/x');
241
+
242
+ const body = await res.json();
243
+ expect(body['~e']).toBeUndefined();
244
+ expect(JSON.stringify(body)).not.toContain(SECRET);
245
+ });
246
+
247
+ test('errorHandler returns text Bad Request at 400 for a UserError on a page path', async () => {
248
+ const logger = createLogger();
249
+ const res = await createApp({
250
+ error: createUserError('Bad payload.'),
251
+ logger,
252
+ }).request('/home');
253
+
254
+ expect(res.status).toEqual(400);
255
+ expect(await res.text()).toEqual('Bad Request');
256
+ expect(logger.warn).toHaveBeenCalledTimes(1);
257
+ expect(logger.error).not.toHaveBeenCalled();
258
+ });
259
+
260
+ test('errorHandler sends an HTTPException response as-is with one warning and no error log', async () => {
261
+ const logger = createLogger();
262
+ const error = new HTTPException(404, {
263
+ res: Response.json(
264
+ { jsonrpc: '2.0', error: { code: -32000, message: 'Unsupported protocol version' } },
265
+ { status: 404 }
266
+ ),
267
+ });
268
+ const res = await createApp({ error, logger }).request('/api/mcp', { method: 'POST' });
269
+ expect(res.status).toEqual(404);
270
+ expect(await res.json()).toEqual({
271
+ jsonrpc: '2.0',
272
+ error: { code: -32000, message: 'Unsupported protocol version' },
273
+ });
274
+ expect(logger.warn).toHaveBeenCalledWith('404 answered by the route: POST /api/mcp');
275
+ expect(logger.error).not.toHaveBeenCalled();
276
+ });
277
+
108
278
  test('errorHandler returns 500 with the serialized error envelope on an api path', async () => {
109
279
  const res = await createApp({
110
280
  error: createErrorWithCause(),
@@ -114,8 +284,8 @@ test('errorHandler returns 500 with the serialized error envelope on an api path
114
284
  expect(res.status).toEqual(500);
115
285
  const body = await res.json();
116
286
  expect(body['~e'].name).toEqual('Error');
117
- expect(body['~e'].message).toEqual('Top message.');
118
- expect(body['~e'].cause.message).toEqual('Cause message.');
287
+ expect(body['~e'].message).toEqual('Something went wrong.');
288
+ expect(body['~e'].cause).toBeUndefined();
119
289
  });
120
290
 
121
291
  test('errorHandler omits stack and received from the 500 envelope at the top level', async () => {
@@ -129,19 +299,31 @@ test('errorHandler omits stack and received from the 500 envelope at the top lev
129
299
  expect(body['~e'].received).toBeUndefined();
130
300
  });
131
301
 
132
- // The regression test for the real bug: the four inline `delete` statements this
133
- // replaces only ever reached depth 0, so a `received` or `stack` on the cause
134
- // still crossed the wire. The policy is now applied at every error node.
135
- test('errorHandler omits stack and received from the 500 envelope on the cause at depth 1', async () => {
302
+ test('errorHandler omits the cause chain from the 500 envelope', async () => {
136
303
  const res = await createApp({
137
304
  error: createErrorWithCause(),
138
305
  logger: createLogger(),
139
306
  }).request('/api/request/getUsers');
140
307
 
141
308
  const body = await res.json();
142
- expect(body['~e'].cause.stack).toBeUndefined();
143
- expect(body['~e'].cause.received).toBeUndefined();
309
+ expect(body['~e'].cause).toBeUndefined();
144
310
  expect(JSON.stringify(body)).not.toContain(SECRET);
311
+ expect(JSON.stringify(body)).not.toContain('Cause message.');
312
+ });
313
+
314
+ test('errorHandler adds devError with the full error to the 500 envelope in dev mode', async () => {
315
+ const context = { handleError: jest.fn(), mode: 'dev', rid: 'rid-1' };
316
+ const res = await createApp({
317
+ context,
318
+ error: createErrorWithCause(),
319
+ logger: createLogger(),
320
+ }).request('/api/request/getUsers');
321
+
322
+ const body = await res.json();
323
+ expect(body['~e'].message).toEqual('Something went wrong.');
324
+ expect(body.devError['~e'].message).toEqual('Top message.');
325
+ expect(body.devError['~e'].requestId).toEqual('rid-1');
326
+ expect(body.devError['~e'].cause.message).toEqual('Cause message.');
145
327
  });
146
328
 
147
329
  test('errorHandler keeps configKey on the 500 envelope so the fault can be located in config', async () => {
@@ -168,7 +350,7 @@ test('errorHandler awaits context.handleError before serializing the response',
168
350
  const context = {
169
351
  handleError: jest.fn(async (error) => {
170
352
  await new Promise((resolve) => setTimeout(resolve, 10));
171
- error.awaited = true;
353
+ error.handled = true;
172
354
  }),
173
355
  };
174
356
  const error = createErrorWithCause();
@@ -179,7 +361,7 @@ test('errorHandler awaits context.handleError before serializing the response',
179
361
  expect(context.handleError).toHaveBeenCalledTimes(1);
180
362
  expect(context.handleError).toHaveBeenCalledWith(error);
181
363
  const body = await res.json();
182
- expect(body['~e'].awaited).toBe(true);
364
+ expect(body['~e'].handled).toBe(true);
183
365
  });
184
366
 
185
367
  // createHandleError sets error.handled once it has logged. The client reads it
@@ -220,7 +402,7 @@ test('errorHandler treats a basePath prefixed api path as an api path', async ()
220
402
 
221
403
  expect(res.status).toEqual(500);
222
404
  const body = await res.json();
223
- expect(body['~e'].message).toEqual('Top message.');
405
+ expect(body['~e'].message).toEqual('Something went wrong.');
224
406
  });
225
407
 
226
408
  test('errorHandler treats a basePath prefixed page path as a page path', async () => {
@@ -0,0 +1,44 @@
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 net from 'net';
18
+
19
+ import { type } from '@lowdefy/helpers';
20
+
21
+ function getHostname(host) {
22
+ const trimmed = host.trim();
23
+ if (trimmed.startsWith('[')) {
24
+ return trimmed.slice(1, trimmed.indexOf(']'));
25
+ }
26
+ return trimmed.split(':')[0];
27
+ }
28
+
29
+ // Whether a Host header is one DNS rebinding cannot produce - the rule Vite's
30
+ // dev server applies to every HTTP request: an IP address, localhost or a
31
+ // *.localhost name. A page that rebinds its own domain to this machine still
32
+ // sends that domain as the Host. A request with no Host is not a browser's.
33
+ function isRebindingSafeHost({ host }) {
34
+ if (type.isNone(host)) {
35
+ return true;
36
+ }
37
+ const hostname = getHostname(host).toLowerCase();
38
+ if (net.isIP(hostname) !== 0) {
39
+ return true;
40
+ }
41
+ return hostname === 'localhost' || hostname.endsWith('.localhost');
42
+ }
43
+
44
+ export default isRebindingSafeHost;
@@ -0,0 +1,32 @@
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 isRebindingSafeHost from './isRebindingSafeHost.js';
18
+
19
+ test.each([
20
+ ['localhost:4100', true],
21
+ ['LOCALHOST', true],
22
+ ['app.localhost:4100', true],
23
+ ['127.0.0.1:4100', true],
24
+ ['192.168.1.20', true],
25
+ ['[::1]:4100', true],
26
+ [undefined, true],
27
+ ['rebound.test:4100', false],
28
+ ['localhost.rebound.test', false],
29
+ ['127.0.0.1.rebound.test', false],
30
+ ])('isRebindingSafeHost(%s) is %s', (host, expected) => {
31
+ expect(isRebindingSafeHost({ host })).toBe(expected);
32
+ });
@@ -0,0 +1,38 @@
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 createSameOriginGuard from './createSameOriginGuard.js';
18
+
19
+ // The dev tools under /lowdefy-docs run requests, endpoints and journeys
20
+ // against real connections, and several accept a CORS-simple request (a
21
+ // text/plain POST, a GET with query parameters) - so without this any website
22
+ // the developer visits could drive them. The guard refuses a request a
23
+ // browser marks cross-site or same-site, and one whose Origin is not this
24
+ // server. Agents and curl send neither header and pass. DNS rebinding never
25
+ // reaches here: Vite's host check refuses a Host that is not local first.
26
+ const guardSameOrigin = createSameOriginGuard({ allowNoOrigin: true });
27
+
28
+ function localDevToolsOnly() {
29
+ return async function localDevToolsOnlyMiddleware(c, next) {
30
+ const refusal = guardSameOrigin(c);
31
+ if (refusal !== null) {
32
+ return refusal;
33
+ }
34
+ await next();
35
+ };
36
+ }
37
+
38
+ export default localDevToolsOnly;
@@ -0,0 +1,61 @@
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 { Hono } from 'hono';
18
+
19
+ import localDevToolsOnly from './localDevToolsOnly.js';
20
+
21
+ function request({ headers, method = 'POST' }) {
22
+ const app = new Hono();
23
+ app.use('/lowdefy-docs/*', localDevToolsOnly());
24
+ app.all('/lowdefy-docs/run-endpoint', (c) => c.json({ allowed: true }));
25
+ return app.request('/lowdefy-docs/run-endpoint', { headers, method });
26
+ }
27
+
28
+ test.each([
29
+ ['an agent or curl calls with no origin', { host: 'localhost:4102' }],
30
+ [
31
+ 'the dev app page calls its own server',
32
+ { host: 'localhost:4102', origin: 'http://localhost:4102', 'sec-fetch-site': 'same-origin' },
33
+ ],
34
+ ])('localDevToolsOnly allows the request when %s', async (_, headers) => {
35
+ const res = await request({ headers });
36
+ expect(res.status).toEqual(200);
37
+ });
38
+
39
+ test.each([
40
+ [
41
+ 'a page on another site posts text/plain to localhost',
42
+ {
43
+ host: 'localhost:4102',
44
+ origin: 'https://attacker.example',
45
+ 'sec-fetch-site': 'cross-site',
46
+ 'content-type': 'text/plain',
47
+ },
48
+ ],
49
+ [
50
+ 'a page on another site loads a GET with no origin',
51
+ { host: 'localhost:4102', 'sec-fetch-site': 'cross-site' },
52
+ ],
53
+ [
54
+ 'a page on another localhost port calls in',
55
+ { host: 'localhost:4102', origin: 'http://localhost:3000', 'sec-fetch-site': 'same-site' },
56
+ ],
57
+ ['the origin is opaque', { host: 'localhost:4102', origin: 'null' }],
58
+ ])('localDevToolsOnly refuses the request when %s', async (_, headers) => {
59
+ const res = await request({ headers });
60
+ expect(res.status).toEqual(403);
61
+ });
@@ -0,0 +1,27 @@
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
+ // Pages and API responses carry the caller's data, so neither the browser nor a
18
+ // shared proxy may store them. Set after next() so redirects and error responses
19
+ // carry it too.
20
+ function noStore() {
21
+ return async function noStoreMiddleware(c, next) {
22
+ await next();
23
+ c.header('Cache-Control', 'private, no-store');
24
+ };
25
+ }
26
+
27
+ export default noStore;
@@ -0,0 +1,48 @@
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 { Hono } from 'hono';
18
+
19
+ import noStore from './noStore.js';
20
+
21
+ function createApp() {
22
+ const app = new Hono();
23
+ app.use('/api/page/*', noStore());
24
+ app.get('/api/page/:pageId', (c) => c.json({ pageId: c.req.param('pageId') }));
25
+ app.use('/api/fail/*', noStore());
26
+ app.get('/api/fail/:id', () => {
27
+ throw new Error('Request failed.');
28
+ });
29
+ app.get('/icon.svg', (c) => c.body('<svg></svg>'));
30
+ app.onError((error, c) => c.json({ error: error.message }, 500));
31
+ return app;
32
+ }
33
+
34
+ test('noStore sets Cache-Control private, no-store on the routes it is registered for', async () => {
35
+ const res = await createApp().request('/api/page/home');
36
+ expect(res.headers.get('cache-control')).toEqual('private, no-store');
37
+ });
38
+
39
+ test('noStore sets Cache-Control on an error response', async () => {
40
+ const res = await createApp().request('/api/fail/1');
41
+ expect(res.status).toEqual(500);
42
+ expect(res.headers.get('cache-control')).toEqual('private, no-store');
43
+ });
44
+
45
+ test('noStore leaves other routes alone', async () => {
46
+ const res = await createApp().request('/icon.svg');
47
+ expect(res.headers.get('cache-control')).toBeNull();
48
+ });
@@ -0,0 +1,38 @@
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 { HTTPException } from 'hono/http-exception';
18
+ import { timeout } from 'hono/timeout';
19
+
20
+ // Caps how long a request may run, and cancels the work it leaves behind. The 504 is
21
+ // answered while the handler is still running; the TimeoutError on the request's
22
+ // timeout signal (which apiContext folds into context.signal) cancels the upstream
23
+ // calls the handler is waiting on, such as an AI provider call, instead of letting
24
+ // them run, and bill, after the response has gone.
25
+ function requestTimeout({ timeoutMs }) {
26
+ return function requestTimeoutMiddleware(c, next) {
27
+ const controller = new AbortController();
28
+ c.set('requestTimeoutSignal', controller.signal);
29
+ return timeout(timeoutMs, () => {
30
+ controller.abort(
31
+ new DOMException(`The request timeout of ${timeoutMs}ms was exceeded.`, 'TimeoutError')
32
+ );
33
+ return new HTTPException(504, { message: 'Gateway Timeout' });
34
+ })(c, next);
35
+ };
36
+ }
37
+
38
+ export default requestTimeout;
@@ -0,0 +1,76 @@
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 getStaleStatus from '../../lib/docs/getStaleStatus.js';
20
+
21
+ // Marks every /lowdefy-docs response while the last dev build failed, so an
22
+ // agent reading a page config or a schema is told the answer comes from the
23
+ // previous build rather than from its own edits. One mechanical hook for the
24
+ // whole route group — the MCP surface has its own in createDocsMcpServer.
25
+ function staleFlag() {
26
+ return async function staleFlagMiddleware(c, next) {
27
+ await next();
28
+ // The build manager rewrites buildStatus.json while the server is
29
+ // answering, so a half-written artifact is a real race — flagging must
30
+ // never be the reason a docs response fails.
31
+ let status;
32
+ try {
33
+ status = getStaleStatus();
34
+ } catch {
35
+ return;
36
+ }
37
+ if (type.isNone(status)) return;
38
+
39
+ // The headers carry the flag on every content type, including the ones
40
+ // whose body is left alone below.
41
+ c.res.headers.set('X-Lowdefy-Stale', 'true');
42
+ if (!type.isNone(status.staleSince)) {
43
+ c.res.headers.set('X-Lowdefy-Stale-Since', status.staleSince);
44
+ }
45
+
46
+ const contentType = c.res.headers.get('Content-Type') ?? '';
47
+
48
+ if (contentType.includes('application/json')) {
49
+ const body = await c.res.clone().json();
50
+ // A JSON array body would have to change shape to carry a field, and
51
+ // shape stability beats uniformity here — the headers still flag it.
52
+ if (!type.isObject(body)) return;
53
+ const merged = { ...body };
54
+ // Never overwrite a key the handler already set.
55
+ for (const key of ['stale', 'staleSince', 'staleReason']) {
56
+ if (!(key in merged)) merged[key] = status[key];
57
+ }
58
+ c.res = new Response(JSON.stringify(merged), {
59
+ status: c.res.status,
60
+ headers: c.res.headers,
61
+ });
62
+ return;
63
+ }
64
+
65
+ if (contentType.includes('text/markdown')) {
66
+ const body = await c.res.clone().text();
67
+ c.res = new Response(`> STALE: ${status.staleReason}\n\n${body}`, {
68
+ status: c.res.status,
69
+ headers: c.res.headers,
70
+ });
71
+ }
72
+ // Every other content type (PNG screenshots, plain text) is left untouched.
73
+ };
74
+ }
75
+
76
+ export default staleFlag;