@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
@@ -0,0 +1,78 @@
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 path from 'node:path';
18
+
19
+ import buildPageIfNeeded from '../../../lib/server/jitPageBuilder.js';
20
+ import getBuildId from '../../../lib/docs/getBuildId.js';
21
+ import listMutants from '../../../lib/server/mutants/listMutants.js';
22
+ import mapPageBuildErrors from '../../../lib/docs/mapPageBuildErrors.js';
23
+ import readBuildArtifact from '../../../lib/docs/readBuildArtifact.js';
24
+ import readMergedMaps from '../../../lib/server/readMergedMaps.js';
25
+ import validateMutantsBody from '../../../lib/server/mutants/validateMutantsBody.js';
26
+
27
+ // Lists every config mutant on the artifacts a set of journey runs exercised
28
+ // (`lowdefy journeys harden`, step 3). Pages are built first, as the page
29
+ // route builds them, so the artifacts read are the current config's. Reads
30
+ // are never made through a mutant cookie: this route answers for the
31
+ // unmutated build. A page that fails to build is a 422 with its errors.
32
+ async function docsMutantsHandler(c) {
33
+ const parsed = validateMutantsBody(await c.req.json().catch(() => null));
34
+ if (parsed.error) {
35
+ return c.json({ error: parsed.error }, 400);
36
+ }
37
+ const { body } = parsed;
38
+ // Read before the pages build: an edit that lands while they build then
39
+ // gives the answer an older build id than the keys it lists, so the journey
40
+ // route refuses its mutants as stale instead of running them.
41
+ const buildId = getBuildId();
42
+ const buildDirectory = path.join(process.cwd(), 'build');
43
+ const configDirectory = process.env.LOWDEFY_DIRECTORY_CONFIG || process.cwd();
44
+ const pageIds = [...new Set([...body.pages, ...body.requests.map((request) => request.pageId)])];
45
+ // A page no longer in the app lists no mutants, even while an older build
46
+ // of it is still on disk: a harden run asks after the pages an earlier run
47
+ // listed, to drop the mutants that are gone.
48
+ const removedPages = new Set();
49
+ for (const pageId of pageIds) {
50
+ try {
51
+ const built = await buildPageIfNeeded({ pageId, buildDirectory, configDirectory });
52
+ if (built === false) {
53
+ removedPages.add(pageId);
54
+ }
55
+ } catch (error) {
56
+ return c.json(
57
+ {
58
+ error: `Page "${pageId}" fails to build, so its mutants cannot be listed until the errors are fixed.`,
59
+ buildErrors: mapPageBuildErrors(error),
60
+ },
61
+ 422
62
+ );
63
+ }
64
+ }
65
+ // Page builds write their keys to jitMaps/, not keyMap.json.
66
+ const { keyMap, refMap } = await readMergedMaps({ buildDirectory });
67
+ const result = await listMutants({
68
+ ...body,
69
+ pages: body.pages.filter((pageId) => !removedPages.has(pageId)),
70
+ requests: body.requests.filter(({ pageId }) => !removedPages.has(pageId)),
71
+ readConfigFile: async (name) => readBuildArtifact({ name, deserialize: true }),
72
+ keyMap,
73
+ refMap,
74
+ });
75
+ return c.json({ buildId, ...result });
76
+ }
77
+
78
+ export default docsMutantsHandler;
@@ -0,0 +1,180 @@
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 'node:fs';
18
+ import os from 'node:os';
19
+ import path from 'node:path';
20
+
21
+ import { jest } from '@jest/globals';
22
+ import { Hono } from 'hono';
23
+ import { serializer } from '@lowdefy/helpers';
24
+
25
+ import { endpointFixture, pageFixture } from '../../../lib/server/mutants/test/fixtures.mjs';
26
+
27
+ const mockBuildPageIfNeeded = jest.fn();
28
+ jest.unstable_mockModule('../../../lib/server/jitPageBuilder.js', () => ({
29
+ default: mockBuildPageIfNeeded,
30
+ }));
31
+
32
+ const { default: docsMutantsHandler } = await import('./mutants.js');
33
+
34
+ const originalCwd = process.cwd();
35
+ let serverDirectory;
36
+
37
+ function write(name, content) {
38
+ const filePath = path.join(serverDirectory, 'build', name);
39
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
40
+ fs.writeFileSync(filePath, typeof content === 'string' ? content : JSON.stringify(content));
41
+ }
42
+
43
+ beforeEach(() => {
44
+ jest.clearAllMocks();
45
+ mockBuildPageIfNeeded.mockResolvedValue(true);
46
+ serverDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-mutants-route-'));
47
+ const page = pageFixture();
48
+ const endpoint = endpointFixture();
49
+ write('pages/tickets.json', serializer.serializeToString(page.root));
50
+ write('api/notify.json', serializer.serializeToString(endpoint.root));
51
+ write('keyMap.json', { ...page.keyMap });
52
+ write('refMap.json', {});
53
+ write('buildStatus.json', { status: 'ok', timestamp: '2026-10-03T08:00:00.000Z' });
54
+ process.chdir(serverDirectory);
55
+ });
56
+
57
+ afterEach(() => {
58
+ process.chdir(originalCwd);
59
+ fs.rmSync(serverDirectory, { recursive: true, force: true });
60
+ });
61
+
62
+ async function post(body) {
63
+ const app = new Hono();
64
+ app.post('/lowdefy-docs/mutants', docsMutantsHandler);
65
+ const response = await app.request('/lowdefy-docs/mutants', {
66
+ method: 'POST',
67
+ headers: { 'content-type': 'application/json' },
68
+ body: JSON.stringify(body),
69
+ });
70
+ return { status: response.status, data: await response.json() };
71
+ }
72
+
73
+ test('POST /lowdefy-docs/mutants lists the mutants of the posted pages and endpoints with the build id and artifact hashes', async () => {
74
+ const { status, data } = await post({ pages: ['tickets'], endpoints: ['notify'] });
75
+ expect(status).toBe(200);
76
+ expect(mockBuildPageIfNeeded).toHaveBeenCalledWith(
77
+ expect.objectContaining({ pageId: 'tickets' })
78
+ );
79
+ expect(data.buildId).toEqual(expect.any(String));
80
+ expect(Object.keys(data.artifacts)).toEqual(['pages/tickets.json', 'api/notify.json']);
81
+ expect(data.artifacts['pages/tickets.json']).toMatch(/^[0-9a-f]{40}$/);
82
+ const operators = new Set(data.mutants.map((mutant) => mutant.operator));
83
+ expect(operators).toEqual(
84
+ new Set([
85
+ 'drop-action',
86
+ 'skip-validate',
87
+ 'flip-visible',
88
+ 'swap-if',
89
+ 'drop-payload',
90
+ 'retarget-link',
91
+ 'drop-block',
92
+ 'drop-step',
93
+ ])
94
+ );
95
+ const dropStep = data.mutants.find((mutant) => mutant.operator === 'drop-step');
96
+ expect(dropStep).toMatchObject({
97
+ artifact: 'api/notify.json',
98
+ anchor: { type: 'endpoint', endpointId: 'notify' },
99
+ copies: [],
100
+ copyTargets: [],
101
+ });
102
+ });
103
+
104
+ test('POST /lowdefy-docs/mutants resolves config paths from the keys a page build wrote to jitMaps', async () => {
105
+ const page = pageFixture();
106
+ write('keyMap.json', {});
107
+ write('jitMaps/1-1-1.json', { keyMap: { ...page.keyMap }, refMap: {} });
108
+ const { status, data } = await post({ pages: ['tickets'] });
109
+ expect(status).toBe(200);
110
+ expect(data.mutants.length).toBeGreaterThan(0);
111
+ data.mutants.forEach((mutant) => {
112
+ expect(mutant.config).toEqual(expect.any(String));
113
+ });
114
+ });
115
+
116
+ test('POST /lowdefy-docs/mutants keeps only the operators asked for', async () => {
117
+ const { data } = await post({ pages: ['tickets'], operators: ['drop-block'] });
118
+ expect(new Set(data.mutants.map((mutant) => mutant.operator))).toEqual(new Set(['drop-block']));
119
+ });
120
+
121
+ test('POST /lowdefy-docs/mutants builds the page of every posted request', async () => {
122
+ await post({ requests: [{ pageId: 'tickets', requestId: 'assign_ticket' }] });
123
+ expect(mockBuildPageIfNeeded.mock.calls.map(([args]) => args.pageId)).toEqual(['tickets']);
124
+ });
125
+
126
+ test.each([
127
+ [{ pages: 'tickets' }, '"pages" must be an array of page ids'],
128
+ [{ requests: [{ pageId: 'tickets' }] }, '"requests" must be an array of { pageId, requestId }'],
129
+ [{ endpoints: [1] }, '"endpoints" must be an array of endpoint ids'],
130
+ [{ appEvents: 'yes' }, '"appEvents" must be a boolean'],
131
+ [{ operators: ['drop-everything'] }, '"operators" must be an array of operator names'],
132
+ [{ pages: ['../../secrets'] }, '"pages" must be an array of page ids'],
133
+ [
134
+ { requests: [{ pageId: 'tickets', requestId: '../x' }] },
135
+ '"requests" must be an array of { pageId, requestId }',
136
+ ],
137
+ [{ endpoints: ['../notify'] }, '"endpoints" must be an array of endpoint ids'],
138
+ ])(
139
+ 'POST /lowdefy-docs/mutants answers a bad body %j with a 400 naming the field',
140
+ async (body, message) => {
141
+ const { status, data } = await post(body);
142
+ expect(status).toBe(400);
143
+ expect(data.error).toContain(message);
144
+ expect(mockBuildPageIfNeeded).not.toHaveBeenCalled();
145
+ }
146
+ );
147
+
148
+ test('POST /lowdefy-docs/mutants answers a page that fails to build with a 422 and its errors', async () => {
149
+ mockBuildPageIfNeeded.mockRejectedValue(new Error('Block type "Buton" not found.'));
150
+ const { status, data } = await post({ pages: ['tickets'] });
151
+ expect(status).toBe(422);
152
+ expect(data.buildErrors).toEqual([
153
+ { type: 'Error', message: 'Block type "Buton" not found.', source: null },
154
+ ]);
155
+ });
156
+
157
+ test('POST /lowdefy-docs/mutants answers with the build id read before the pages built', async () => {
158
+ // An edit lands while the page builds: its keys may be the new build's, so
159
+ // the answer must carry the older id and the journey route refuse it as stale.
160
+ mockBuildPageIfNeeded.mockImplementation(async () => {
161
+ write('buildStatus.json', { status: 'ok', timestamp: '2026-10-03T09:00:00.000Z' });
162
+ return true;
163
+ });
164
+ const { status, data } = await post({ pages: ['tickets'] });
165
+ expect(status).toBe(200);
166
+ expect(data.buildId).toEqual('2026-10-03T08:00:00.000Z');
167
+ });
168
+
169
+ test('POST /lowdefy-docs/mutants lists no mutants for a page no longer in the app, though its old build is on disk', async () => {
170
+ mockBuildPageIfNeeded.mockResolvedValue(false);
171
+ const { status, data } = await post({
172
+ pages: ['tickets'],
173
+ requests: [{ pageId: 'tickets', requestId: 'load' }],
174
+ endpoints: ['notify'],
175
+ });
176
+ expect(status).toBe(200);
177
+ expect(Object.keys(data.artifacts)).toEqual(['api/notify.json']);
178
+ expect(data.mutants.every(({ artifact }) => artifact === 'api/notify.json')).toBe(true);
179
+ expect(data.ids).toEqual(data.mutants.map(({ id }) => id));
180
+ });
@@ -0,0 +1,41 @@
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
+ // The docs routes take a page instance's path values as a JSON string on the
20
+ // GET routes (query params are always strings) and as an object in the POST
21
+ // bodies. Returns { pathParams } or { error }; the page tools check the values
22
+ // against the page's pattern.
23
+ function parsePathParamsParam({ value }) {
24
+ if (type.isNone(value)) {
25
+ return {};
26
+ }
27
+ if (!type.isString(value)) {
28
+ return { pathParams: value };
29
+ }
30
+ try {
31
+ return { pathParams: JSON.parse(value) };
32
+ } catch {
33
+ return {
34
+ error: `The "pathParams" param must be JSON, e.g. {"ticket_id":"1"}. Received ${JSON.stringify(
35
+ value
36
+ )}.`,
37
+ };
38
+ }
39
+ }
40
+
41
+ export default parsePathParamsParam;
@@ -0,0 +1,42 @@
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 parsePathParamsParam from './parsePathParamsParam.js';
18
+
19
+ test('parsePathParamsParam returns nothing when the param is absent', () => {
20
+ expect(parsePathParamsParam({ value: undefined })).toEqual({});
21
+ expect(parsePathParamsParam({ value: null })).toEqual({});
22
+ });
23
+
24
+ test('parsePathParamsParam parses a JSON string from a query param', () => {
25
+ expect(parsePathParamsParam({ value: '{"space":"s","ticket_id":"1"}' })).toEqual({
26
+ pathParams: { space: 's', ticket_id: '1' },
27
+ });
28
+ });
29
+
30
+ test('parsePathParamsParam passes an object from a JSON body through', () => {
31
+ expect(parsePathParamsParam({ value: { ticket_id: 1 } })).toEqual({
32
+ pathParams: { ticket_id: 1 },
33
+ });
34
+ });
35
+
36
+ test('parsePathParamsParam returns an error when the query param is not JSON', () => {
37
+ expect(parsePathParamsParam({ value: 'ticket_id=1' }).error).toMatch(/must be JSON/);
38
+ });
39
+
40
+ test('parsePathParamsParam leaves a parsed non-object for the page tools to refuse', () => {
41
+ expect(parsePathParamsParam({ value: '["1"]' })).toEqual({ pathParams: ['1'] });
42
+ });
@@ -16,36 +16,42 @@
16
16
 
17
17
  import { type } from '@lowdefy/helpers';
18
18
 
19
- // The docs routes take the headless caller as a JSON string on the GET routes
20
- // (query params are always strings) and as an object in the POST bodies, so both
21
- // arrive here and every route answers a bad `user` with the same message.
22
- // Returns { user } or { error }.
19
+ const SHAPE =
20
+ 'The "user" param must be a user object (JSON on a query string), e.g. {"roles":["admin"]}, "none" to act signed out, or the name of a user in the data set the "data" param names.';
21
+
22
+ // The docs routes take the caller in the forms the MCP tools take (see
23
+ // lib/docs/resolveToolCaller.js): a user object - a JSON string on the GET
24
+ // routes, since query params are always strings, and an object in the POST
25
+ // bodies - "none", or a data set user name. Both arrive here, so every route
26
+ // answers a bad `user` with the same message. Returns { user } or { error }.
23
27
  function parseUserParam({ value }) {
24
28
  if (type.isNone(value)) {
25
29
  return {};
26
30
  }
27
-
28
- let user = value;
29
- if (type.isString(value)) {
30
- try {
31
- user = JSON.parse(value);
32
- } catch {
33
- return {
34
- error: `The "user" param must be JSON, e.g. {"roles":["admin"]}. Received ${JSON.stringify(
35
- value
36
- )}.`,
37
- };
38
- }
31
+ if (type.isObject(value)) {
32
+ return { user: value };
39
33
  }
40
-
41
- if (!type.isObject(user)) {
34
+ if (!type.isString(value) || value === '') {
35
+ return { error: `${SHAPE} Received ${JSON.stringify(value)}.` };
36
+ }
37
+ // A plain string is "none" or a data set user name; one that opens like JSON
38
+ // is a user object written as a query param.
39
+ if (!value.startsWith('{') && !value.startsWith('[')) {
40
+ return { user: value };
41
+ }
42
+ let user;
43
+ try {
44
+ user = JSON.parse(value);
45
+ } catch {
42
46
  return {
43
- error: `The "user" param must be an object, e.g. {"roles":["admin"]}. Received ${JSON.stringify(
44
- user
47
+ error: `The "user" param must be JSON, e.g. {"roles":["admin"]}. Received ${JSON.stringify(
48
+ value
45
49
  )}.`,
46
50
  };
47
51
  }
48
-
52
+ if (!type.isObject(user)) {
53
+ return { error: `${SHAPE} Received ${JSON.stringify(user)}.` };
54
+ }
49
55
  return { user };
50
56
  }
51
57
 
@@ -32,14 +32,26 @@ test('parseUserParam passes an object from a JSON body through', () => {
32
32
  });
33
33
  });
34
34
 
35
- test('parseUserParam returns an error when the query param is not JSON', () => {
36
- expect(parseUserParam({ value: 'admin' }).error).toMatch(/must be JSON/);
35
+ test('parseUserParam passes a data set user name through', () => {
36
+ expect(parseUserParam({ value: 'member' })).toEqual({ user: 'member' });
37
+ });
38
+
39
+ test('parseUserParam passes "none" through', () => {
40
+ expect(parseUserParam({ value: 'none' })).toEqual({ user: 'none' });
41
+ });
42
+
43
+ test('parseUserParam returns an error when a query param that opens like JSON is not JSON', () => {
44
+ expect(parseUserParam({ value: '{roles: [admin]}' }).error).toMatch(/must be JSON/);
37
45
  });
38
46
 
39
47
  test('parseUserParam returns an error when the param parses to a non-object', () => {
40
- expect(parseUserParam({ value: '["admin"]' }).error).toMatch(/must be an object/);
48
+ expect(parseUserParam({ value: '["admin"]' }).error).toMatch(/must be a user object/);
49
+ });
50
+
51
+ test('parseUserParam returns an error when a body value is neither an object nor a string', () => {
52
+ expect(parseUserParam({ value: 42 }).error).toMatch(/must be a user object/);
41
53
  });
42
54
 
43
- test('parseUserParam returns an error when a body value is not an object', () => {
44
- expect(parseUserParam({ value: 42 }).error).toMatch(/must be an object/);
55
+ test('parseUserParam returns an error for an empty string', () => {
56
+ expect(parseUserParam({ value: '' }).error).toMatch(/must be a user object/);
45
57
  });
@@ -0,0 +1,35 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { type } from '@lowdefy/helpers';
18
+ import requestRestart from '../../../lib/docs/requestRestart.js';
19
+
20
+ async function docsRestartHandler(c) {
21
+ let body = {};
22
+ try {
23
+ body = await c.req.json();
24
+ } catch {
25
+ // An empty body is a valid request with no reason.
26
+ }
27
+ const reason = type.isString(body?.reason) ? body.reason : undefined;
28
+ const result = requestRestart({ reason });
29
+ return c.json({
30
+ ...result,
31
+ note: 'The dev server is restarting. Call GET /lowdefy-docs/build-status?wait=true (lowdefy_build_status with wait: true) before your next call; it answers once the server is back.',
32
+ });
33
+ }
34
+
35
+ export default docsRestartHandler;
@@ -0,0 +1,64 @@
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
+
22
+ const originalCwd = process.cwd();
23
+ const fixtureDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-restart-route-test-'));
24
+ fs.mkdirSync(path.join(fixtureDir, 'build'), { recursive: true });
25
+ process.chdir(fixtureDir);
26
+
27
+ const { default: docsRestartHandler } = await import('./restart.js');
28
+
29
+ afterAll(() => {
30
+ process.chdir(originalCwd);
31
+ fs.rmSync(fixtureDir, { recursive: true, force: true });
32
+ });
33
+
34
+ function createApp() {
35
+ const app = new Hono();
36
+ app.post('/lowdefy-docs/restart', docsRestartHandler);
37
+ return app;
38
+ }
39
+
40
+ test('POST /lowdefy-docs/restart writes the sentinel and answers with the polling note', async () => {
41
+ const res = await createApp().request('/lowdefy-docs/restart', {
42
+ method: 'POST',
43
+ headers: { 'content-type': 'application/json' },
44
+ body: JSON.stringify({ reason: 'stale cache' }),
45
+ });
46
+
47
+ expect(res.status).toBe(200);
48
+ expect(await res.json()).toEqual({
49
+ requested: true,
50
+ reason: 'stale cache',
51
+ note: 'The dev server is restarting. Call GET /lowdefy-docs/build-status?wait=true (lowdefy_build_status with wait: true) before your next call; it answers once the server is back.',
52
+ });
53
+ const sentinel = JSON.parse(fs.readFileSync(path.join(fixtureDir, 'build', '.restart'), 'utf8'));
54
+ expect(sentinel.reason).toBe('stale cache');
55
+ });
56
+
57
+ test('POST /lowdefy-docs/restart accepts an empty body', async () => {
58
+ const res = await createApp().request('/lowdefy-docs/restart', { method: 'POST' });
59
+
60
+ expect(res.status).toBe(200);
61
+ const body = await res.json();
62
+ expect(body.requested).toBe(true);
63
+ expect(body).not.toHaveProperty('reason');
64
+ });
@@ -0,0 +1,54 @@
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 { ConfigError } from '@lowdefy/errors';
18
+
19
+ import parseUserParam from './parseUserParam.js';
20
+ import runEndpoint from '../../../lib/docs/runEndpoint.js';
21
+
22
+ // Refusals, routine rejects and endpoint errors are returned as 200 data (see
23
+ // runEndpoint.js) - only malformed input (missing endpointId or a bad user,
24
+ // thrown as a ConfigError) is a 400. Anything else is a fault and propagates to
25
+ // the error handler.
26
+ async function docsRunEndpointHandler(c) {
27
+ // Parse the body from a clone: runEndpoint builds a Lowdefy context whose
28
+ // resolveAuthentication reads c.req.raw (headers) to resolve the caller,
29
+ // so leave the original request body intact and read our own copy here.
30
+ const { endpointId, payload, user, data, system, saveResponse } = await c.req.raw.clone().json();
31
+ const { user: parsedUser, error: userError } = parseUserParam({ value: user });
32
+ if (userError) {
33
+ return c.json({ error: userError }, 400);
34
+ }
35
+ try {
36
+ const result = await runEndpoint({
37
+ endpointId,
38
+ payload,
39
+ user: parsedUser,
40
+ data,
41
+ system,
42
+ saveResponse,
43
+ honoContext: c,
44
+ });
45
+ return c.json(result);
46
+ } catch (error) {
47
+ if (error instanceof ConfigError) {
48
+ return c.json({ error: error.message }, 400);
49
+ }
50
+ throw error;
51
+ }
52
+ }
53
+
54
+ export default docsRunEndpointHandler;