@lowdefy/server-dev 6.1.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (623) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +2 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +222 -36
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +67 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +679 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  104. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  105. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  106. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  107. package/lib/docs/dataSets/findCollectionCollisions.js +52 -0
  108. package/lib/docs/dataSets/getDataStore.js +106 -0
  109. package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
  110. package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
  111. package/lib/docs/dataSets/groupDataSetByCollection.js +44 -0
  112. package/lib/docs/dataSets/insertFixture.js +57 -0
  113. package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
  114. package/lib/docs/dataSets/isDataStoreGone.js +29 -0
  115. package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
  116. package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
  117. package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
  118. package/lib/docs/dataSets/openDataSession.js +119 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +343 -0
  120. package/lib/docs/dataSets/pickDataStorePort.js +58 -0
  121. package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
  122. package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
  123. package/lib/docs/dataSets/readDataSession.js +41 -0
  124. package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
  125. package/lib/docs/dataSets/readDataSet.js +68 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +220 -0
  127. package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
  128. package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
  129. package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
  130. package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
  131. package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
  132. package/lib/docs/dataSets/sweepOrphanDatabases.js +33 -0
  133. package/lib/docs/dataSets/test/app/lowdefy.yaml +203 -0
  134. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  135. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +8 -0
  136. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  137. package/lib/docs/devEventBus.js +136 -0
  138. package/lib/docs/devEventBus.test.mjs +269 -0
  139. package/lib/docs/devToolDefinitions.js +529 -0
  140. package/lib/docs/docSources.js +21 -0
  141. package/lib/docs/docs.test.mjs +171 -15
  142. package/lib/docs/docsIndex.test.mjs +377 -0
  143. package/lib/docs/evalOperator.js +5 -3
  144. package/lib/docs/evalOperatorHeadless.js +84 -12
  145. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  146. package/lib/docs/evalOperatorInTab.js +2 -1
  147. package/lib/docs/findConfig.js +95 -49
  148. package/lib/docs/fitResponse.js +81 -0
  149. package/lib/docs/fitResponse.test.mjs +128 -0
  150. package/lib/docs/formatFeedback.js +26 -5
  151. package/lib/docs/formatFeedback.test.mjs +48 -0
  152. package/lib/docs/getAppMap.js +7 -1
  153. package/lib/docs/getBrowser.js +150 -80
  154. package/lib/docs/getBrowser.test.mjs +407 -16
  155. package/lib/docs/getBuildId.js +61 -0
  156. package/lib/docs/getBuildId.test.mjs +84 -0
  157. package/lib/docs/getBuildStatus.js +54 -6
  158. package/lib/docs/getBuildStatus.test.mjs +133 -0
  159. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  160. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  161. package/lib/docs/getCoreDoc.js +9 -1
  162. package/lib/docs/getDoc.js +100 -0
  163. package/lib/docs/getDocsIndex.js +152 -0
  164. package/lib/docs/getHazards.js +115 -0
  165. package/lib/docs/getHazards.test.mjs +175 -0
  166. package/lib/docs/getMailOutboxDirectory.js +26 -0
  167. package/lib/docs/getOverview.js +79 -13
  168. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  169. package/lib/docs/getPageBuildStatus.js +46 -0
  170. package/lib/docs/getPluginDoc.js +21 -23
  171. package/lib/docs/getSchema.js +33 -1
  172. package/lib/docs/getStaleStatus.js +36 -0
  173. package/lib/docs/getStaleStatus.test.mjs +74 -0
  174. package/lib/docs/guardJourneyOrigin.js +39 -0
  175. package/lib/docs/inspectState.js +5 -3
  176. package/lib/docs/inspectStateFromTab.js +2 -2
  177. package/lib/docs/inspectStateHeadless.js +89 -27
  178. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  179. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  180. package/lib/docs/installHeadlessShell.js +123 -0
  181. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  182. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  183. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  184. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  185. package/lib/docs/isPageReady.js +77 -0
  186. package/lib/docs/isPageReady.test.mjs +235 -0
  187. package/lib/docs/isRunErrorEntry.js +25 -0
  188. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  189. package/lib/docs/journeyRunRecording.js +35 -0
  190. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  191. package/lib/docs/keepEventStreamAlive.js +68 -0
  192. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  193. package/lib/docs/launchBrowser.js +83 -0
  194. package/lib/docs/launchBrowser.test.mjs +175 -0
  195. package/lib/docs/listModuleDocEntries.js +69 -0
  196. package/lib/docs/listPackageDocFiles.js +67 -0
  197. package/lib/docs/listPluginDocEntries.js +111 -0
  198. package/lib/docs/listPlugins.js +9 -58
  199. package/lib/docs/listTypes.js +14 -6
  200. package/lib/docs/loadFrameworkHazards.js +40 -0
  201. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  202. package/lib/docs/loadState.js +103 -35
  203. package/lib/docs/makeModuleManifestDoc.js +174 -0
  204. package/lib/docs/matchExercisedPages.js +35 -0
  205. package/lib/docs/matchJourneyRoute.js +69 -0
  206. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  207. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  208. package/lib/docs/nextJourneySequence.js +30 -0
  209. package/lib/docs/noBrowserError.js +22 -0
  210. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  211. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  212. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  213. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  214. package/lib/docs/observe/evaluateInvariants.js +183 -0
  215. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  216. package/lib/docs/observe/findingKey.js +29 -0
  217. package/lib/docs/observe/hasNoEffect.js +31 -0
  218. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  219. package/lib/docs/observe/installStepObserver.js +72 -0
  220. package/lib/docs/observe/isAppApiUrl.js +33 -0
  221. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  222. package/lib/docs/observe/readStepObserver.js +42 -0
  223. package/lib/docs/observe/relativeSource.js +29 -0
  224. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  225. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  226. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  227. package/lib/docs/observe/usesSearchStage.js +45 -0
  228. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  229. package/lib/docs/observe/watchJourneyContext.js +75 -0
  230. package/lib/docs/observe/withAppErrors.js +31 -0
  231. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  232. package/lib/docs/openJourney.js +94 -0
  233. package/lib/docs/openJourneyEmail.js +41 -0
  234. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  235. package/lib/docs/pageBlocks.js +47 -0
  236. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  237. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  238. package/lib/docs/rails/authEngineActions.js +53 -0
  239. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  240. package/lib/docs/rails/authEngineMethods.js +52 -0
  241. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  242. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  243. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  244. package/lib/docs/rails/checkClickRails.js +56 -0
  245. package/lib/docs/rails/createDataSetRails.js +82 -0
  246. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  247. package/lib/docs/rails/findExternalBlocks.js +147 -0
  248. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  249. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  250. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  251. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  252. package/lib/docs/readConnectionPlugins.js +26 -0
  253. package/lib/docs/readDevAuthMode.js +32 -0
  254. package/lib/docs/readDocEntry.js +40 -0
  255. package/lib/docs/readDocTitle.js +26 -0
  256. package/lib/docs/readJourneyEmailMatch.js +57 -0
  257. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  258. package/lib/docs/readJourneySession.js +56 -0
  259. package/lib/docs/readMailOutbox.js +37 -0
  260. package/lib/docs/readPagePath.js +27 -0
  261. package/lib/docs/readPluginPackages.js +99 -0
  262. package/lib/docs/readProxyBuildWait.js +38 -0
  263. package/lib/docs/readRoutineSteps.js +45 -0
  264. package/lib/docs/registerShownTextEngine.js +118 -0
  265. package/lib/docs/requestRestart.js +30 -0
  266. package/lib/docs/requestRestart.test.mjs +49 -0
  267. package/lib/docs/resolvePageInstance.js +46 -0
  268. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  269. package/lib/docs/resolveSource.js +11 -7
  270. package/lib/docs/resolveSource.test.mjs +1 -1
  271. package/lib/docs/resolveToolCaller.js +62 -0
  272. package/lib/docs/reviewPage.js +51 -0
  273. package/lib/docs/reviewPageBuilds.js +56 -0
  274. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  275. package/lib/docs/runCheck.js +75 -0
  276. package/lib/docs/runCheck.test.mjs +91 -0
  277. package/lib/docs/runCheckChild.js +49 -0
  278. package/lib/docs/runEndpoint.js +185 -0
  279. package/lib/docs/runEndpoint.test.mjs +509 -0
  280. package/lib/docs/runErrorBuffers.js +99 -0
  281. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  282. package/lib/docs/runJourney.js +347 -0
  283. package/lib/docs/runJourney.test.mjs +2383 -0
  284. package/lib/docs/runJourneyAsUsers.js +44 -0
  285. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  286. package/lib/docs/runJourneySteps.js +1320 -0
  287. package/lib/docs/runRequest.js +100 -59
  288. package/lib/docs/runRequest.test.mjs +190 -0
  289. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  290. package/lib/docs/scaffoldPage.js +3 -1
  291. package/lib/docs/screenshotPage.js +163 -11
  292. package/lib/docs/screenshotPage.test.mjs +263 -1
  293. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  294. package/lib/docs/searchDocs.js +82 -30
  295. package/lib/docs/searchDocs.test.mjs +100 -0
  296. package/lib/docs/searchIcons.js +90 -0
  297. package/lib/docs/searchIcons.test.mjs +127 -0
  298. package/lib/docs/selectExercised.js +39 -0
  299. package/lib/docs/selectFinalState.js +66 -0
  300. package/lib/docs/selectFinalState.test.mjs +51 -0
  301. package/lib/docs/serverErrorStore.js +54 -0
  302. package/lib/docs/serverErrorStore.test.mjs +50 -0
  303. package/lib/docs/setupTestFixtures.mjs +133 -39
  304. package/lib/docs/shownTextSelector.js +28 -0
  305. package/lib/docs/snapshotState.js +13 -5
  306. package/lib/docs/splitSearchTerms.js +62 -0
  307. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  308. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  309. package/lib/docs/tabAvailable.js +6 -5
  310. package/lib/docs/tabChannel.js +127 -21
  311. package/lib/docs/tabChannel.test.mjs +187 -29
  312. package/lib/docs/unsettledPageNote.js +24 -0
  313. package/lib/docs/validateJourneyMail.js +36 -0
  314. package/lib/docs/validateJourneyTimeout.js +38 -0
  315. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  316. package/lib/docs/validateRecordingParam.js +42 -0
  317. package/lib/docs/validateStateSelection.js +33 -0
  318. package/lib/docs/validateStateSelection.test.mjs +33 -0
  319. package/lib/docs/validateViewport.js +51 -0
  320. package/lib/docs/validateViewport.test.mjs +54 -0
  321. package/lib/docs/waitForBuild.js +50 -0
  322. package/lib/docs/waitForBuild.test.mjs +75 -0
  323. package/lib/docs/waitForJourneyEmail.js +77 -0
  324. package/lib/docs/withBrowserSlot.js +45 -0
  325. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  326. package/lib/docs/withDataSession.js +53 -0
  327. package/lib/server/applyDataSetRedirect.js +83 -0
  328. package/lib/server/applyDataSetRedirect.test.mjs +340 -0
  329. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +204 -0
  330. package/lib/server/applyDataSetRedirectWalled.test.mjs +131 -0
  331. package/lib/server/auth/createSystemContext.js +73 -0
  332. package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
  333. package/lib/server/auth/getHeadlessUser.js +9 -8
  334. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  335. package/lib/server/auth/getMockUser.js +18 -14
  336. package/lib/server/auth/{session.js → getStrategies.js} +17 -16
  337. package/lib/server/auth/headlessUser.js +3 -2
  338. package/lib/server/auth/journeyActor.js +35 -0
  339. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  340. package/lib/server/checkPageRecord.js +44 -0
  341. package/lib/server/contextMapBudget.js +24 -0
  342. package/lib/server/contextMapBudget.test.mjs +98 -0
  343. package/lib/server/continueJitKeys.js +28 -0
  344. package/lib/server/continueJitKeys.test.mjs +43 -0
  345. package/lib/server/createDataSessionEndedError.js +33 -0
  346. package/lib/server/createDataSetRefusal.js +26 -0
  347. package/lib/server/createLowdefyContext.js +168 -41
  348. package/lib/server/createLowdefyContext.test.mjs +328 -8
  349. package/lib/server/getClientAddress.js +51 -0
  350. package/lib/server/getClientAddress.test.mjs +56 -0
  351. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  352. package/lib/server/hashConfigContent.js +26 -0
  353. package/lib/server/importAppCode.test.mjs +57 -0
  354. package/lib/server/isRoutineStep.js +27 -0
  355. package/lib/server/jitPageBuilder.js +309 -126
  356. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  357. package/lib/server/jitPageBuilder.test.mjs +218 -0
  358. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  359. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  360. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  361. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  362. package/lib/server/journeyCookies.js +22 -0
  363. package/lib/server/journeyCookies.test.mjs +145 -0
  364. package/lib/server/loadDynamicJsMap.js +47 -0
  365. package/lib/server/log/createHandleError.js +38 -2
  366. package/lib/server/log/createHandleError.test.mjs +125 -2
  367. package/lib/server/log/createLogger.js +3 -0
  368. package/lib/server/log/createLogger.test.mjs +15 -0
  369. package/lib/server/log/logRequest.js +2 -3
  370. package/lib/server/mutantDataSetWrappers.test.mjs +112 -0
  371. package/lib/server/mutants/applyMutant.js +39 -0
  372. package/lib/server/mutants/artifactPatterns.js +25 -0
  373. package/lib/server/mutants/buildAnchor.js +45 -0
  374. package/lib/server/mutants/collectSharedIds.js +41 -0
  375. package/lib/server/mutants/configPathSegments.js +38 -0
  376. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  377. package/lib/server/mutants/describeEvent.js +22 -0
  378. package/lib/server/mutants/enumerateArtifact.js +56 -0
  379. package/lib/server/mutants/findNodeByKey.js +45 -0
  380. package/lib/server/mutants/findNodeByPointer.js +49 -0
  381. package/lib/server/mutants/getArtifactKind.js +32 -0
  382. package/lib/server/mutants/groupMutantCopies.js +56 -0
  383. package/lib/server/mutants/hashArtifact.js +25 -0
  384. package/lib/server/mutants/hashMutantTarget.js +34 -0
  385. package/lib/server/mutants/listMutants.js +122 -0
  386. package/lib/server/mutants/listMutants.test.mjs +502 -0
  387. package/lib/server/mutants/mutantId.js +30 -0
  388. package/lib/server/mutants/mutantRuns.js +57 -0
  389. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  390. package/lib/server/mutants/operators/dropAction.js +47 -0
  391. package/lib/server/mutants/operators/dropBlock.js +44 -0
  392. package/lib/server/mutants/operators/dropPayload.js +52 -0
  393. package/lib/server/mutants/operators/dropStep.js +44 -0
  394. package/lib/server/mutants/operators/flipVisible.js +53 -0
  395. package/lib/server/mutants/operators/index.js +37 -0
  396. package/lib/server/mutants/operators/retargetLink.js +72 -0
  397. package/lib/server/mutants/operators/skipValidate.js +77 -0
  398. package/lib/server/mutants/operators/swapIf.js +112 -0
  399. package/lib/server/mutants/operators.test.mjs +396 -0
  400. package/lib/server/mutants/positionFreePath.js +38 -0
  401. package/lib/server/mutants/removeFromParent.js +28 -0
  402. package/lib/server/mutants/test/fixtures.mjs +173 -0
  403. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  404. package/lib/server/mutants/validateMutantParam.js +69 -0
  405. package/lib/server/mutants/validateMutantsBody.js +88 -0
  406. package/lib/server/mutants/walkArtifact.js +179 -0
  407. package/lib/server/pageBuildRecords.js +131 -0
  408. package/lib/server/pageBuildRecords.test.mjs +258 -0
  409. package/lib/server/pageCache.mjs +22 -7
  410. package/lib/server/pageCache.test.mjs +22 -7
  411. package/lib/server/pageRebuilds.test.mjs +543 -0
  412. package/lib/server/pruneJitMaps.js +36 -0
  413. package/lib/server/pruneJitMaps.test.mjs +55 -0
  414. package/lib/server/readInstanceRecord.js +33 -0
  415. package/lib/server/readInstanceRecord.test.mjs +99 -0
  416. package/lib/server/readMergedMaps.js +86 -0
  417. package/lib/server/readMergedMaps.test.mjs +79 -0
  418. package/lib/server/recording/appendRecords.js +33 -0
  419. package/lib/server/recording/errorRecordingStamp.js +34 -0
  420. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  421. package/lib/server/recording/isRecordingEnabled.js +31 -0
  422. package/lib/server/recording/readRecordingCookie.js +71 -0
  423. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  424. package/lib/server/recording/recordingCookiePayload.js +33 -0
  425. package/lib/server/recording/servedBuilds.js +46 -0
  426. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  427. package/lib/server/sinkExternalConnections.js +95 -0
  428. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  429. package/lib/server/skipStaleMapWrites.js +62 -0
  430. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  431. package/lib/server/startIdleGc.js +67 -0
  432. package/lib/server/startIdleGc.test.mjs +138 -0
  433. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  434. package/lib/server/trackSessionWork.js +30 -0
  435. package/lib/vite/createDevServerExclude.js +40 -0
  436. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  437. package/manager/getContext.mjs +24 -2
  438. package/manager/processes/initialBuild.mjs +14 -0
  439. package/manager/processes/initialBuild.test.mjs +101 -0
  440. package/manager/processes/lowdefyBuild.mjs +30 -7
  441. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
  442. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  443. package/manager/processes/optimizeDependencies.mjs +59 -0
  444. package/manager/processes/restartServer.mjs +34 -6
  445. package/manager/processes/restartServer.test.mjs +162 -0
  446. package/manager/processes/shutdownServer.mjs +11 -0
  447. package/manager/processes/shutdownServer.test.mjs +45 -0
  448. package/manager/processes/startFirstServer.mjs +46 -0
  449. package/manager/processes/startFirstServer.test.mjs +326 -0
  450. package/manager/processes/startMailSink.mjs +135 -0
  451. package/manager/processes/startMailSink.test.mjs +146 -0
  452. package/manager/processes/startProxy.mjs +129 -12
  453. package/manager/processes/startProxy.test.mjs +366 -0
  454. package/manager/processes/startServer.mjs +47 -6
  455. package/manager/processes/startServer.test.mjs +155 -0
  456. package/manager/processes/startWatchers.mjs +6 -4
  457. package/manager/processes/syncServer.mjs +69 -0
  458. package/manager/processes/syncServer.test.mjs +141 -0
  459. package/manager/processes/waitForDevServer.mjs +48 -0
  460. package/manager/processes/waitForDevServer.test.mjs +112 -0
  461. package/manager/run.mjs +128 -43
  462. package/manager/utils/BatchChanges.mjs +21 -5
  463. package/manager/utils/BatchChanges.test.mjs +77 -0
  464. package/manager/utils/acquireDevInstance.mjs +99 -0
  465. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  466. package/manager/utils/createBuildActivity.mjs +67 -0
  467. package/manager/utils/createBuildActivity.test.mjs +58 -0
  468. package/manager/utils/createChangeSignal.mjs +39 -0
  469. package/manager/utils/createChangeSignal.test.mjs +62 -0
  470. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  471. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  472. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  473. package/manager/utils/createDotPathIgnore.mjs +44 -0
  474. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  475. package/manager/utils/createIdleGc.mjs +51 -0
  476. package/manager/utils/createIdleGc.test.mjs +77 -0
  477. package/manager/utils/createRequestActivity.mjs +100 -0
  478. package/manager/utils/createRequestActivity.test.mjs +121 -0
  479. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  480. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  481. package/manager/utils/createServerEnv.mjs +39 -0
  482. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  483. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  485. package/manager/utils/importFresh.mjs +46 -0
  486. package/manager/utils/importFresh.test.mjs +72 -0
  487. package/manager/utils/importFreshWorker.mjs +20 -0
  488. package/manager/utils/killTaggedBrowser.mjs +35 -0
  489. package/manager/utils/pagePathChanged.mjs +56 -0
  490. package/manager/utils/pruneRecordings.mjs +85 -0
  491. package/manager/utils/pruneRecordings.test.mjs +117 -0
  492. package/manager/utils/publishBuildDirectory.mjs +121 -0
  493. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  494. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  495. package/manager/utils/readBasePath.mjs +27 -0
  496. package/manager/utils/readBasePath.test.mjs +43 -0
  497. package/manager/utils/readBuildStatusWait.mjs +67 -0
  498. package/manager/utils/readManagerVersion.mjs +30 -0
  499. package/manager/utils/readManagerVersion.test.mjs +35 -0
  500. package/manager/utils/readPluginDefinitions.mjs +36 -0
  501. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  502. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  503. package/manager/utils/resolvePorts.mjs +49 -0
  504. package/manager/utils/setupWatcher.mjs +19 -11
  505. package/manager/utils/startRecordingPruner.mjs +45 -0
  506. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  507. package/manager/utils/waitForChildExit.mjs +53 -0
  508. package/manager/utils/waitForChildExit.test.mjs +70 -0
  509. package/manager/utils/waitForServer.mjs +44 -0
  510. package/manager/utils/writeBuildStatus.mjs +1 -1
  511. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  512. package/manager/watchers/envWatcher.mjs +9 -2
  513. package/manager/watchers/envWatcher.test.mjs +87 -0
  514. package/manager/watchers/listServerSidePackages.mjs +47 -0
  515. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  516. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  517. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  518. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  519. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  520. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  521. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  522. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  523. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  524. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  525. package/package.json +60 -47
  526. package/package.original.json +60 -47
  527. package/src/app.js +131 -20
  528. package/src/html/renderDevPage.js +63 -11
  529. package/src/html/renderDevPage.test.mjs +138 -0
  530. package/src/lib/getRequestPath.js +29 -0
  531. package/src/middleware/createSameOriginGuard.js +37 -0
  532. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  533. package/src/middleware/errorHandler.js +51 -1
  534. package/src/middleware/errorHandler.test.mjs +173 -3
  535. package/src/middleware/isRebindingSafeHost.js +44 -0
  536. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  537. package/src/middleware/localDevToolsOnly.js +38 -0
  538. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  539. package/src/middleware/noStore.js +27 -0
  540. package/src/middleware/noStore.test.mjs +48 -0
  541. package/src/middleware/requestTimeout.js +38 -0
  542. package/src/middleware/staleFlag.js +76 -0
  543. package/src/middleware/staleFlag.test.mjs +127 -0
  544. package/src/middleware/stampCaller.js +38 -0
  545. package/src/middleware/stampCaller.test.mjs +64 -0
  546. package/src/routes/agent.js +15 -3
  547. package/src/routes/auth.js +29 -20
  548. package/src/routes/auth.test.mjs +30 -56
  549. package/src/routes/clientError.js +13 -13
  550. package/src/routes/cron.js +3 -1
  551. package/src/routes/cronForward.js +3 -1
  552. package/src/routes/detached.js +22 -9
  553. package/src/routes/devInspect.js +17 -8
  554. package/src/routes/devInspect.test.mjs +44 -7
  555. package/src/routes/devRecording.js +113 -0
  556. package/src/routes/devRecording.test.mjs +253 -0
  557. package/src/routes/docs/buildStatus.js +10 -2
  558. package/src/routes/docs/buildStatus.test.mjs +46 -0
  559. package/src/routes/docs/content.js +5 -3
  560. package/src/routes/docs/evalOperator.js +15 -1
  561. package/src/routes/docs/events.js +50 -0
  562. package/src/routes/docs/events.test.mjs +117 -0
  563. package/src/routes/docs/find.js +9 -1
  564. package/src/routes/docs/icons.js +30 -0
  565. package/src/routes/docs/inspectState.js +16 -1
  566. package/src/routes/docs/journey.js +194 -0
  567. package/src/routes/docs/journey.test.mjs +401 -0
  568. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  569. package/src/routes/docs/loadState.js +4 -2
  570. package/src/routes/docs/mcp.js +34 -3
  571. package/src/routes/docs/mcp.test.mjs +130 -0
  572. package/src/routes/docs/mutants.js +78 -0
  573. package/src/routes/docs/mutants.test.mjs +180 -0
  574. package/src/routes/docs/parsePathParamsParam.js +41 -0
  575. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  576. package/src/routes/docs/parseUserParam.js +27 -21
  577. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  578. package/src/routes/docs/restart.js +35 -0
  579. package/src/routes/docs/restart.test.mjs +64 -0
  580. package/src/routes/docs/runEndpoint.js +54 -0
  581. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  582. package/src/routes/docs/runRequest.js +25 -8
  583. package/src/routes/docs/runRequest.test.mjs +92 -0
  584. package/src/routes/docs/screenshot.js +50 -2
  585. package/src/routes/docs/screenshot.test.mjs +117 -0
  586. package/src/routes/docs/search.js +15 -2
  587. package/src/routes/docs/snapshotState.js +17 -3
  588. package/src/routes/endpoints.js +27 -26
  589. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  590. package/src/routes/feedback.js +10 -13
  591. package/src/routes/jitPage.js +99 -35
  592. package/src/routes/jitPage.test.mjs +248 -0
  593. package/src/routes/mcp.js +82 -5
  594. package/src/routes/mcp.test.mjs +215 -0
  595. package/src/routes/mcpMemberToken.test.mjs +125 -0
  596. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  597. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  598. package/src/routes/mountDevPageRoutes.js +25 -0
  599. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  600. package/src/routes/mountOauthDiscovery.js +44 -0
  601. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  602. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  604. package/src/routes/reload.js +16 -0
  605. package/src/routes/request.js +6 -4
  606. package/src/routes/request.test.mjs +9 -5
  607. package/src/routes/requestBody.test.mjs +159 -0
  608. package/src/routes/usage.js +6 -2
  609. package/src/routes/user.js +27 -0
  610. package/src/routes/websocket.js +29 -8
  611. package/src/routes/websocket.test.mjs +65 -0
  612. package/src/routes/wellKnownFallback.js +30 -0
  613. package/src/routes/wellKnownFallback.test.mjs +32 -0
  614. package/src/websocket/devWebSocket.js +41 -26
  615. package/src/websocket/devWebSocket.test.mjs +157 -0
  616. package/vite.config.js +53 -23
  617. package/lib/server/auth/getDevSession.js +0 -70
  618. package/lib/server/auth/getDevSession.test.mjs +0 -138
  619. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  620. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  621. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  622. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  623. package/src/routes/apiPage.js +0 -37
@@ -14,24 +14,72 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
+ import getLowdefyVersion from '../../manager/utils/getLowdefyVersion.mjs';
18
+ import checkAgentFiles from './checkAgentFiles.js';
17
19
  import clientErrorStore from './clientErrorStore.js';
20
+ import getBuildId from './getBuildId.js';
21
+ import getPageBuildStatus from './getPageBuildStatus.js';
22
+ import serverErrorStore from './serverErrorStore.js';
18
23
  import readBuildArtifact from './readBuildArtifact.js';
19
24
 
25
+ const EARLIER_ERRORS_NOTE =
26
+ 'Reported under an earlier build, before the latest config build or page edit, so they may already be fixed. Reload or re-run the page to see whether they still happen.';
27
+
28
+ function splitByBuild({ entries, buildId }) {
29
+ return {
30
+ current: entries.filter((entry) => entry.buildId === buildId),
31
+ earlier: entries.filter((entry) => entry.buildId !== buildId),
32
+ };
33
+ }
34
+
35
+ // Notices about Lowdefy's agent block (see checkAgentFiles), checked on every
36
+ // read: the root files are not watched, so a stored result would outlive a fix.
37
+ async function getAgentFiles() {
38
+ const configDirectory = process.env.LOWDEFY_DIRECTORY_CONFIG || process.cwd();
39
+ const lowdefyVersion = await getLowdefyVersion({
40
+ directories: { config: configDirectory },
41
+ }).catch(() => null);
42
+ return checkAgentFiles({ configDirectory, lowdefyVersion });
43
+ }
44
+
20
45
  // Feedback loop for agents: build status (written by the build manager to
21
- // build/buildStatus.json) plus recent browser errors reported via
22
- // POST /api/client-error. Lets an agent check "did my last edit work?"
23
- // without tailing terminal logs.
24
- function getBuildStatus() {
46
+ // build/buildStatus.json), the page builds (see getPageBuildStatus), plus
47
+ // recent browser errors reported via POST /api/client-error, plus recent server
48
+ // errors (request, endpoint, MCP and agent tool failures) collected by
49
+ // createHandleError. Lets an agent check "did my last edit work?" without
50
+ // tailing terminal logs. Errors reported under an earlier build are listed
51
+ // apart under earlierErrors. `buildId` names the build being served now.
52
+ // Notices about the agent files come under agentFiles, apart from the build's
53
+ // warnings, and only when there are any.
54
+ async function getBuildStatus({ checked } = {}) {
25
55
  const build = readBuildArtifact({ name: 'buildStatus.json' }) ?? {
26
56
  status: 'unknown',
27
57
  message:
28
58
  'No build status yet — the build manager has not written build/buildStatus.json. ' +
29
59
  'This is expected before the first build completes.',
30
60
  };
31
- return {
61
+ const buildId = getBuildId();
62
+ const clientErrors = splitByBuild({ entries: clientErrorStore.list(), buildId });
63
+ const serverErrors = splitByBuild({ entries: serverErrorStore.list(), buildId });
64
+ const status = {
65
+ buildId,
32
66
  build,
33
- clientErrors: clientErrorStore.list(),
67
+ pages: await getPageBuildStatus({ checked }),
68
+ clientErrors: clientErrors.current,
69
+ serverErrors: serverErrors.current,
34
70
  };
71
+ if (clientErrors.earlier.length > 0 || serverErrors.earlier.length > 0) {
72
+ status.earlierErrors = {
73
+ note: EARLIER_ERRORS_NOTE,
74
+ clientErrors: clientErrors.earlier,
75
+ serverErrors: serverErrors.earlier,
76
+ };
77
+ }
78
+ const agentFiles = await getAgentFiles();
79
+ if (agentFiles.length > 0) {
80
+ status.agentFiles = agentFiles;
81
+ }
82
+ return status;
35
83
  }
36
84
 
37
85
  export default getBuildStatus;
@@ -0,0 +1,133 @@
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 { jest } from '@jest/globals';
21
+
22
+ const mockClientErrors = jest.fn();
23
+ jest.unstable_mockModule('./clientErrorStore.js', () => ({ default: { list: mockClientErrors } }));
24
+ const mockServerErrors = jest.fn();
25
+ jest.unstable_mockModule('./serverErrorStore.js', () => ({ default: { list: mockServerErrors } }));
26
+ jest.unstable_mockModule('./getBuildId.js', () => ({ default: () => 'build-2' }));
27
+ const mockGetPageBuildStatus = jest.fn(() => ({ unbuilt: 0 }));
28
+ jest.unstable_mockModule('./getPageBuildStatus.js', () => ({ default: mockGetPageBuildStatus }));
29
+ jest.unstable_mockModule('./readBuildArtifact.js', () => ({
30
+ default: () => ({ status: 'ok', errors: [], warnings: ['a build warning'] }),
31
+ }));
32
+
33
+ const { default: getBuildStatus } = await import('./getBuildStatus.js');
34
+
35
+ test('getBuildStatus lists errors from earlier builds apart from the current ones', async () => {
36
+ mockClientErrors.mockReturnValue([
37
+ { message: 'old render error', buildId: 'build-1' },
38
+ { message: 'live render error', buildId: 'build-2' },
39
+ ]);
40
+ mockServerErrors.mockReturnValue([{ message: 'old request error', buildId: 'build-1' }]);
41
+
42
+ const status = await getBuildStatus();
43
+
44
+ expect(status.clientErrors).toEqual([{ message: 'live render error', buildId: 'build-2' }]);
45
+ expect(status.serverErrors).toEqual([]);
46
+ expect(status.earlierErrors).toEqual({
47
+ note: expect.stringContaining('may already be fixed'),
48
+ clientErrors: [{ message: 'old render error', buildId: 'build-1' }],
49
+ serverErrors: [{ message: 'old request error', buildId: 'build-1' }],
50
+ });
51
+ });
52
+
53
+ test('getBuildStatus leaves out earlierErrors when every error is current', async () => {
54
+ mockClientErrors.mockReturnValue([{ message: 'live', buildId: 'build-2' }]);
55
+ mockServerErrors.mockReturnValue([]);
56
+
57
+ expect((await getBuildStatus()).earlierErrors).toBeUndefined();
58
+ });
59
+
60
+ test('getBuildStatus passes the pages build status just built to the page status', async () => {
61
+ mockClientErrors.mockReturnValue([]);
62
+ mockServerErrors.mockReturnValue([]);
63
+
64
+ const status = await getBuildStatus({ checked: ['home'] });
65
+
66
+ expect(mockGetPageBuildStatus).toHaveBeenCalledWith({ checked: ['home'] });
67
+ expect(status.pages).toEqual({ unbuilt: 0 });
68
+ expect(status.build.status).toBe('ok');
69
+ });
70
+
71
+ test('getBuildStatus names the build being served as a top-level buildId', async () => {
72
+ mockClientErrors.mockReturnValue([]);
73
+ mockServerErrors.mockReturnValue([]);
74
+
75
+ expect((await getBuildStatus()).buildId).toBe('build-2');
76
+ });
77
+
78
+ describe('agentFiles', () => {
79
+ let root;
80
+ let previousConfigDirectory;
81
+
82
+ beforeEach(() => {
83
+ mockClientErrors.mockReturnValue([]);
84
+ mockServerErrors.mockReturnValue([]);
85
+ root = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-build-status-'));
86
+ fs.mkdirSync(path.join(root, '.git'));
87
+ fs.writeFileSync(path.join(root, 'lowdefy.yaml'), 'lowdefy: 7.1.0\n');
88
+ fs.writeFileSync(path.join(root, 'CLAUDE.md'), '@AGENTS.md\n');
89
+ previousConfigDirectory = process.env.LOWDEFY_DIRECTORY_CONFIG;
90
+ process.env.LOWDEFY_DIRECTORY_CONFIG = root;
91
+ });
92
+
93
+ afterEach(() => {
94
+ if (previousConfigDirectory === undefined) {
95
+ delete process.env.LOWDEFY_DIRECTORY_CONFIG;
96
+ } else {
97
+ process.env.LOWDEFY_DIRECTORY_CONFIG = previousConfigDirectory;
98
+ }
99
+ fs.rmSync(root, { recursive: true, force: true });
100
+ });
101
+
102
+ const writeBlock = (version) =>
103
+ fs.writeFileSync(
104
+ path.join(root, 'AGENTS.md'),
105
+ `<!-- BEGIN:lowdefy-agent-rules version=${version} -->\n<!-- END:lowdefy-agent-rules -->\n`
106
+ );
107
+
108
+ test('lists notices apart from the build warnings, and a fix shows on the next call', async () => {
109
+ writeBlock('7.0.0');
110
+
111
+ const before = await getBuildStatus();
112
+ expect(before.agentFiles).toEqual([
113
+ {
114
+ code: 'version_mismatch',
115
+ message: expect.stringContaining('npx lowdefy@7.1.0 agent-setup'),
116
+ },
117
+ ]);
118
+ expect(before.build.warnings).toEqual(['a build warning']);
119
+
120
+ writeBlock('7.1.2');
121
+
122
+ const after = await getBuildStatus();
123
+ expect(after).not.toHaveProperty('agentFiles');
124
+ expect(after.build.warnings).toEqual(['a build warning']);
125
+ });
126
+
127
+ test('leaves agentFiles out when lowdefy.yaml cannot be read', async () => {
128
+ writeBlock('6.0.0');
129
+ fs.rmSync(path.join(root, 'lowdefy.yaml'));
130
+
131
+ expect(await getBuildStatus()).not.toHaveProperty('agentFiles');
132
+ });
133
+ });
@@ -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 buildEditedPages from './buildEditedPages.js';
18
+ import getBuildStatus from './getBuildStatus.js';
19
+ import waitForBuild from './waitForBuild.js';
20
+
21
+ // Build status for wait: true. Waits until the dev server has processed the
22
+ // latest edits, then builds the pages they touched, so the answer covers
23
+ // those pages whether or not anything has requested them since. A request
24
+ // through the manager's proxy has been held there until the edits - and any
25
+ // restart they caused - were processed (proxyWait), so it does not wait again.
26
+ async function getBuildStatusAfterEdits({ proxyWait } = {}) {
27
+ const waited = proxyWait ?? (await waitForBuild());
28
+ if (!waited.settled) {
29
+ return { ...waited, ...(await getBuildStatus()) };
30
+ }
31
+ const checked = await buildEditedPages();
32
+ return { ...waited, ...(await getBuildStatus({ checked })) };
33
+ }
34
+
35
+ export default getBuildStatusAfterEdits;
@@ -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 { jest } from '@jest/globals';
18
+
19
+ const mockWaitForBuild = jest.fn();
20
+ jest.unstable_mockModule('./waitForBuild.js', () => ({ default: mockWaitForBuild }));
21
+ const mockBuildEditedPages = jest.fn();
22
+ jest.unstable_mockModule('./buildEditedPages.js', () => ({ default: mockBuildEditedPages }));
23
+ const mockGetBuildStatus = jest.fn(({ checked } = {}) => ({ build: { status: 'ok' }, checked }));
24
+ jest.unstable_mockModule('./getBuildStatus.js', () => ({ default: mockGetBuildStatus }));
25
+
26
+ const { default: getBuildStatusAfterEdits } = await import('./getBuildStatusAfterEdits.js');
27
+ const { default: readProxyBuildWait } = await import('./readProxyBuildWait.js');
28
+
29
+ beforeEach(() => {
30
+ jest.clearAllMocks();
31
+ });
32
+
33
+ test('getBuildStatusAfterEdits builds the edited pages once the edits are processed', async () => {
34
+ mockWaitForBuild.mockResolvedValue({ settled: true, sawBuild: false, waitedMs: 1000 });
35
+ mockBuildEditedPages.mockResolvedValue(['home']);
36
+
37
+ const result = await getBuildStatusAfterEdits();
38
+
39
+ expect(mockWaitForBuild.mock.invocationCallOrder[0]).toBeLessThan(
40
+ mockBuildEditedPages.mock.invocationCallOrder[0]
41
+ );
42
+ expect(result).toEqual({
43
+ settled: true,
44
+ sawBuild: false,
45
+ waitedMs: 1000,
46
+ build: { status: 'ok' },
47
+ checked: ['home'],
48
+ });
49
+ });
50
+
51
+ test('getBuildStatusAfterEdits builds no pages when the wait gave up', async () => {
52
+ mockWaitForBuild.mockResolvedValue({ settled: false, waitedMs: 60000 });
53
+
54
+ const result = await getBuildStatusAfterEdits();
55
+
56
+ expect(mockBuildEditedPages).not.toHaveBeenCalled();
57
+ expect(result.settled).toBe(false);
58
+ expect(result.checked).toBeUndefined();
59
+ });
60
+
61
+ test('getBuildStatusAfterEdits uses the wait the manager proxy made instead of waiting again', async () => {
62
+ mockBuildEditedPages.mockResolvedValue([]);
63
+
64
+ const result = await getBuildStatusAfterEdits({
65
+ proxyWait: readProxyBuildWait({
66
+ getHeader: () => 'settled=true&sawBuild=true&waitedMs=4200',
67
+ }),
68
+ });
69
+
70
+ expect(mockWaitForBuild).not.toHaveBeenCalled();
71
+ expect(result).toMatchObject({ settled: true, sawBuild: true, waitedMs: 4200, checked: [] });
72
+ });
73
+
74
+ test('readProxyBuildWait returns null for a request that did not come through the proxy wait', () => {
75
+ expect(readProxyBuildWait({ getHeader: () => undefined })).toBe(null);
76
+ });
@@ -19,6 +19,7 @@ import path from 'node:path';
19
19
  import { type } from '@lowdefy/helpers';
20
20
 
21
21
  import getDocsManifest from './getDocsManifest.js';
22
+ import getHazards from './getHazards.js';
22
23
 
23
24
  function findDoc({ manifest, slug, kind, typeName }) {
24
25
  if (!type.isNone(slug)) {
@@ -57,7 +58,14 @@ function getCoreDoc({ slug, kind, type: typeName }) {
57
58
  return null;
58
59
  }
59
60
  const markdown = fs.readFileSync(path.join(manifest.contentDir, doc.path), 'utf8');
60
- return { slug: doc.slug, title: doc.title, section: doc.section, markdown };
61
+ const result = { slug: doc.slug, title: doc.title, section: doc.section, markdown };
62
+ // Hazards belong to the type the caller asked about, not to the doc page:
63
+ // findDoc remaps requests onto their connection's page, and a request
64
+ // must carry its own hazards rather than inherit the connection's.
65
+ if (type.isNone(slug) && !type.isNone(typeName)) {
66
+ result.hazards = getHazards({ kind, type: typeName });
67
+ }
68
+ return result;
61
69
  }
62
70
 
63
71
  export default getCoreDoc;
@@ -0,0 +1,100 @@
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 getCoreDoc from './getCoreDoc.js';
20
+ import getDocsIndex from './getDocsIndex.js';
21
+ import readDocEntry from './readDocEntry.js';
22
+
23
+ function singularKind({ kind }) {
24
+ return type.isNone(kind) ? null : String(kind).toLowerCase().replace(/s$/, '');
25
+ }
26
+
27
+ function findTypeSlug({ typeDocs, kind, typeName, anyKind }) {
28
+ const exactKind = singularKind({ kind });
29
+ if (!type.isNone(exactKind) && typeDocs.has(`${exactKind}:${typeName}`)) {
30
+ return typeDocs.get(`${exactKind}:${typeName}`);
31
+ }
32
+ if (!type.isNone(exactKind) && !anyKind) {
33
+ return null;
34
+ }
35
+ for (const [key, slug] of typeDocs) {
36
+ if (key.slice(key.indexOf(':') + 1) === typeName) {
37
+ return slug;
38
+ }
39
+ }
40
+ return null;
41
+ }
42
+
43
+ // With no kind, a core page for exactly this type name belongs to a Lowdefy
44
+ // type, unless a plugin registers the name in that same kind and so replaces
45
+ // it. A plugin type of another kind must not take that page's place.
46
+ function namesOtherCoreType({ entries, typeDocs, kind, typeName }) {
47
+ if (!type.isNone(kind)) {
48
+ return false;
49
+ }
50
+ return entries.some(
51
+ (entry) =>
52
+ entry.source === 'core' &&
53
+ entry.typeName === typeName &&
54
+ !typeDocs.has(`${entry.kind}:${typeName}`)
55
+ );
56
+ }
57
+
58
+ function makePluginDoc({ entries, slug }) {
59
+ const entry = entries.find((item) => item.source !== 'core' && item.slug === slug);
60
+ if (type.isUndefined(entry)) {
61
+ return null;
62
+ }
63
+ return {
64
+ slug: entry.slug,
65
+ title: entry.title,
66
+ section: entry.section,
67
+ source: entry.source,
68
+ package: entry.package,
69
+ version: entry.version,
70
+ markdown: readDocEntry({ entry }),
71
+ };
72
+ }
73
+
74
+ // A doc page by slug or by type: the core docs, and the docs the app's own
75
+ // plugins and modules ship. A type a plugin registers is looked up in its
76
+ // plugin's docs before the core docs, whose type lookup falls back to a
77
+ // prefix match (MongoDB covers MongoDBFind) that would claim a plugin type
78
+ // named like a core one. Plugin types the caller names under another kind
79
+ // (the kinds get_doc takes do not cover every kind) come last.
80
+ function getDoc({ slug, kind, type: typeName }) {
81
+ const { entries, typeDocs } = getDocsIndex();
82
+ if (!type.isNone(slug)) {
83
+ return getCoreDoc({ slug }) ?? makePluginDoc({ entries, slug });
84
+ }
85
+ if (type.isNone(typeName)) {
86
+ return null;
87
+ }
88
+ const pluginSlug = findTypeSlug({ typeDocs, kind, typeName, anyKind: false });
89
+ if (!type.isNone(pluginSlug) && !namesOtherCoreType({ entries, typeDocs, kind, typeName })) {
90
+ return makePluginDoc({ entries, slug: pluginSlug });
91
+ }
92
+ const coreDoc = getCoreDoc({ kind, type: typeName });
93
+ if (!type.isNone(coreDoc)) {
94
+ return coreDoc;
95
+ }
96
+ const otherKindSlug = findTypeSlug({ typeDocs, kind, typeName, anyKind: true });
97
+ return type.isNone(otherKindSlug) ? null : makePluginDoc({ entries, slug: otherKindSlug });
98
+ }
99
+
100
+ export default getDoc;
@@ -0,0 +1,152 @@
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 path from 'node:path';
19
+ import { type } from '@lowdefy/helpers';
20
+
21
+ import getDocsManifest from './getDocsManifest.js';
22
+ import listModuleDocEntries from './listModuleDocEntries.js';
23
+ import listPackageDocFiles from './listPackageDocFiles.js';
24
+ import listPluginDocEntries from './listPluginDocEntries.js';
25
+ import readBuildArtifact from './readBuildArtifact.js';
26
+
27
+ // The build artifacts that change when the app's plugins change. The dev
28
+ // build leaves an artifact whose bytes are unchanged with its old mtime, so a
29
+ // config-only edit leaves their stats alone.
30
+ const PLUGIN_ARTIFACTS = [
31
+ 'plugins/availableTypes.json',
32
+ 'customTypesMap.json',
33
+ 'installedPluginPackages.json',
34
+ ];
35
+
36
+ let coreEntries;
37
+ let index = null;
38
+ let indexKey = null;
39
+ let localDirs = [];
40
+ let modulesStat = null;
41
+ let modules = null;
42
+ let modulesKey = null;
43
+
44
+ // The core docs change only with a release, so their entries are read once.
45
+ function getCoreEntries() {
46
+ if (!type.isUndefined(coreEntries)) {
47
+ return coreEntries;
48
+ }
49
+ const manifest = getDocsManifest();
50
+ if (type.isNone(manifest)) {
51
+ coreEntries = [];
52
+ return coreEntries;
53
+ }
54
+ coreEntries = manifest.docs.map((doc) => ({
55
+ ...doc,
56
+ source: 'core',
57
+ package: '@lowdefy/docs-content',
58
+ version: manifest.version,
59
+ filePath: path.join(manifest.contentDir, doc.path),
60
+ }));
61
+ return coreEntries;
62
+ }
63
+
64
+ function statKey(filePath) {
65
+ try {
66
+ const stat = fs.statSync(filePath);
67
+ return `${stat.mtimeMs}:${stat.size}`;
68
+ } catch {
69
+ return 'none';
70
+ }
71
+ }
72
+
73
+ // Component bodies are left out: only the ids and descriptions are shown.
74
+ function componentsKey({ components }) {
75
+ if (!type.isArray(components)) {
76
+ return components;
77
+ }
78
+ return components.map((item) => (type.isObject(item) ? [item.id, item.description] : item));
79
+ }
80
+
81
+ // modules.json also holds what a config edit changes (resolved var values,
82
+ // page bodies), so only the parts the docs read are compared.
83
+ function readModules() {
84
+ const stat = statKey(path.join(process.cwd(), 'build', 'modules.json'));
85
+ if (stat === modulesStat) {
86
+ return;
87
+ }
88
+ modulesStat = stat;
89
+ modules = readBuildArtifact({ name: 'modules.json', deserialize: true }) ?? {};
90
+ modulesKey = JSON.stringify(
91
+ Object.values(modules).map((moduleEntry) => [
92
+ moduleEntry.id,
93
+ moduleEntry.source,
94
+ moduleEntry.moduleRoot,
95
+ moduleEntry.isLocal,
96
+ moduleEntry.varDefs,
97
+ moduleEntry.manifest?.name,
98
+ moduleEntry.manifest?.description,
99
+ moduleEntry.manifest?.exports,
100
+ componentsKey({ components: moduleEntry.manifest?.components }),
101
+ ])
102
+ );
103
+ }
104
+
105
+ // Doc files of local plugins and modules can be added, removed or edited
106
+ // while the server runs.
107
+ function localDocsKey() {
108
+ return localDirs
109
+ .map((dir) => {
110
+ const { readme, docs } = listPackageDocFiles({ dir });
111
+ return [readme, ...docs]
112
+ .filter((filePath) => !type.isNone(filePath))
113
+ .map((filePath) => `${filePath}@${statKey(filePath)}`);
114
+ })
115
+ .join('|');
116
+ }
117
+
118
+ function createIndexKey() {
119
+ readModules();
120
+ const buildDirectory = path.join(process.cwd(), 'build');
121
+ return [
122
+ ...PLUGIN_ARTIFACTS.map((name) => statKey(path.join(buildDirectory, name))),
123
+ modulesKey,
124
+ localDocsKey(),
125
+ ].join('\n');
126
+ }
127
+
128
+ function buildIndex() {
129
+ const plugins = listPluginDocEntries();
130
+ const moduleDocs = listModuleDocEntries({ modules });
131
+ localDirs = [...plugins.localDirs, ...moduleDocs.localDirs];
132
+ return {
133
+ entries: [...getCoreEntries(), ...plugins.entries, ...moduleDocs.entries],
134
+ typeDocs: plugins.typeDocs,
135
+ };
136
+ }
137
+
138
+ // Every doc the app can use: the core docs, and the docs its own plugins and
139
+ // modules ship. Rebuilt on the first call after the plugins or modules change.
140
+ // The key of a rebuilt index names the local doc files the build found, so it
141
+ // is taken again after the build.
142
+ function getDocsIndex() {
143
+ const key = createIndexKey();
144
+ if (key === indexKey) {
145
+ return index;
146
+ }
147
+ index = buildIndex();
148
+ indexKey = createIndexKey();
149
+ return index;
150
+ }
151
+
152
+ export default getDocsIndex;