@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,77 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { type } from '@lowdefy/helpers';
18
+
19
+ import readRoutineSteps from './readRoutineSteps.js';
20
+
21
+ // A request writes when its type declares meta.checkWrite: true. An endpoint
22
+ // writes when a step of its routine has such a type, or when an endpoint it
23
+ // reaches through CallApi writes, so a caller that writes only through a
24
+ // nested endpoint still reads as a write.
25
+ async function addWriteFlags({ requests, endpoints, readConfigFile, requestSchemas }) {
26
+ function isWriteType(stepType) {
27
+ return requestSchemas[stepType]?.meta?.checkWrite === true;
28
+ }
29
+
30
+ const routines = new Map();
31
+ async function readEndpoint(endpointId) {
32
+ if (!routines.has(endpointId)) {
33
+ const artifact = await readConfigFile(`api/${endpointId}.json`);
34
+ const steps = readRoutineSteps({ routine: artifact.routine });
35
+ routines.set(endpointId, {
36
+ writes: steps.some((step) => isWriteType(step.type)),
37
+ targets: steps
38
+ .filter((step) => step.type === 'CallApi' && type.isString(step.properties?.endpointId))
39
+ .map((step) => step.properties.endpointId),
40
+ });
41
+ }
42
+ return routines.get(endpointId);
43
+ }
44
+
45
+ async function reachesWrite(endpointId) {
46
+ const visited = new Set([endpointId]);
47
+ const queue = [endpointId];
48
+ while (queue.length > 0) {
49
+ const { writes, targets } = await readEndpoint(queue.shift());
50
+ if (writes) {
51
+ return true;
52
+ }
53
+ targets
54
+ .filter((target) => !visited.has(target))
55
+ .forEach((target) => {
56
+ visited.add(target);
57
+ queue.push(target);
58
+ });
59
+ }
60
+ return false;
61
+ }
62
+
63
+ const requestsWithWrite = [];
64
+ for (const request of requests) {
65
+ const artifact = await readConfigFile(
66
+ `pages/${request.pageId}/requests/${request.requestId}.json`
67
+ );
68
+ requestsWithWrite.push({ ...request, write: isWriteType(artifact?.type) });
69
+ }
70
+ const endpointsWithWrite = [];
71
+ for (const endpoint of endpoints) {
72
+ endpointsWithWrite.push({ ...endpoint, write: await reachesWrite(endpoint.endpointId) });
73
+ }
74
+ return { requests: requestsWithWrite, endpoints: endpointsWithWrite };
75
+ }
76
+
77
+ export default addWriteFlags;
@@ -0,0 +1,50 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import path from 'node:path';
18
+
19
+ import buildPageIfNeeded from '../server/jitPageBuilder.js';
20
+ import reviewPageBuilds from './reviewPageBuilds.js';
21
+
22
+ // Page builds share one build context, and most of a build is waiting on file
23
+ // reads, so a few run at once; more would only compete for the same process.
24
+ const CONCURRENT_BUILDS = 4;
25
+
26
+ // Builds every page an edit touched (see reviewPageBuilds), the same build a
27
+ // page request runs (with its per-page lock), so build status covers pages
28
+ // nobody has opened since the edit. A failed build is recorded by the page
29
+ // builder and reported from there.
30
+ async function buildEditedPages() {
31
+ const { edited } = await reviewPageBuilds();
32
+ const buildDirectory = path.join(process.cwd(), 'build');
33
+ const configDirectory = process.env.LOWDEFY_DIRECTORY_CONFIG || process.cwd();
34
+ const queue = [...edited];
35
+ async function buildNext() {
36
+ while (queue.length > 0) {
37
+ const pageId = queue.shift();
38
+ try {
39
+ await buildPageIfNeeded({ pageId, buildDirectory, configDirectory });
40
+ } catch {
41
+ // Reported by getPageBuildStatus from the page's build record.
42
+ }
43
+ }
44
+ }
45
+ await Promise.all(Array.from({ length: Math.min(CONCURRENT_BUILDS, queue.length) }, buildNext));
46
+ return edited;
47
+ }
48
+
49
+ export { CONCURRENT_BUILDS };
50
+ export default buildEditedPages;
@@ -0,0 +1,21 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ // The header the manager's proxy adds to a build-status wait it has already
18
+ // waited out (see readProxyBuildWait).
19
+ const buildWaitHeader = 'x-lowdefy-build-wait';
20
+
21
+ export default buildWaitHeader;
@@ -17,34 +17,40 @@
17
17
  import fs from 'node:fs';
18
18
  import path from 'node:path';
19
19
 
20
+ import { urlQuery as urlQueryFn } from '@lowdefy/helpers';
21
+
20
22
  import drawAnnotationsSvg from '../../client/feedback/drawAnnotationsSvg.js';
21
23
  import { getBrowser, openPage } from './getBrowser.js';
24
+ import noBrowserError from './noBrowserError.js';
25
+ import readPagePath from './readPagePath.js';
26
+ import withBrowserSlot from './withBrowserSlot.js';
22
27
 
23
- // Renders the page headless at the batch's recorded viewport/scroll, injects
24
- // the developer's drawn shapes and element outlines as an SVG layer inside
25
- // the page, screenshots the composite, and saves it under the config dir's
26
- // .lowdefy/annotations/ (gitignored). The saved path goes into the formatted
27
- // feedback text so an agent can read the image. Never throws — returns
28
- // { path } or { error }.
29
- async function captureAnnotatedScreenshot({ origin, batch, fileName }) {
28
+ // Renders the batch's page instance (pageId, pathParams, urlQuery) headless at
29
+ // the batch's recorded viewport/scroll, injects the developer's drawn shapes
30
+ // and element outlines as an SVG layer inside the page, screenshots the
31
+ // composite, and saves it under the config dir's .lowdefy/annotations/
32
+ // (gitignored). The saved path goes into the formatted feedback text so an
33
+ // agent can read the image. Never throws — returns { path } or { error }.
34
+ async function captureInBrowser({ origin, batch, fileName }) {
30
35
  let browser;
31
36
  try {
32
37
  browser = await getBrowser();
33
38
  } catch (error) {
34
- return { error: `No Chromium available for the annotated screenshot (${error.message}).` };
39
+ return { error: noBrowserError(error) };
35
40
  }
36
41
 
37
42
  const viewport = batch.viewport ?? {};
38
43
  const width = viewport.width || 1280;
39
44
  const height = viewport.height || 800;
40
- const urlQuery = batch.urlQuery && batch.urlQuery !== '?' ? batch.urlQuery : '';
41
-
42
45
  let context;
43
46
  try {
44
47
  const opened = await openPage({
45
48
  browser,
46
49
  origin,
47
- pageId: `${batch.pageId}${urlQuery}`,
50
+ pageId: batch.pageId,
51
+ path: readPagePath({ pageId: batch.pageId }),
52
+ pathParams: batch.pathParams,
53
+ urlQuery: urlQueryFn.parse(batch.urlQuery ?? ''),
48
54
  width,
49
55
  height,
50
56
  });
@@ -75,7 +81,7 @@ async function captureAnnotatedScreenshot({ origin, batch, fileName }) {
75
81
  fileName ?? `${batch.pageId}-${new Date().toISOString().replace(/[:.]/g, '-')}.png`;
76
82
  fs.writeFileSync(path.join(dir, name), buffer);
77
83
 
78
- return { path: path.join('.lowdefy', 'annotations', name) };
84
+ return { path: path.posix.join('.lowdefy', 'annotations', name) };
79
85
  } catch (error) {
80
86
  return { error: `Failed to capture annotated screenshot: ${error.message}` };
81
87
  } finally {
@@ -85,4 +91,9 @@ async function captureAnnotatedScreenshot({ origin, batch, fileName }) {
85
91
  }
86
92
  }
87
93
 
94
+ // One browser slot for the whole capture.
95
+ async function captureAnnotatedScreenshot({ origin, batch, fileName }) {
96
+ return withBrowserSlot({ task: () => captureInBrowser({ origin, batch, fileName }) });
97
+ }
98
+
88
99
  export default captureAnnotatedScreenshot;
@@ -0,0 +1,148 @@
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 path from 'path';
19
+
20
+ const BLOCK_BEGIN = '<!-- BEGIN:lowdefy-agent-rules';
21
+ const MARKER = /<!-- BEGIN:lowdefy-agent-rules\b([^>]*?)-->/;
22
+
23
+ // The nearest folder holding .git (a directory in a checkout, a file in a
24
+ // linked worktree), walking up from the config directory; outside git, the
25
+ // config directory. The same rule as the CLI's agentSetup/findProjectRoot.js,
26
+ // kept here because server-dev does not depend on the CLI.
27
+ function findProjectRoot(configDirectory) {
28
+ let directory = configDirectory;
29
+ for (;;) {
30
+ if (fs.existsSync(path.join(directory, '.git'))) {
31
+ return directory;
32
+ }
33
+ const parent = path.dirname(directory);
34
+ if (parent === directory) {
35
+ return configDirectory;
36
+ }
37
+ directory = parent;
38
+ }
39
+ }
40
+
41
+ function readText(filePath) {
42
+ try {
43
+ return fs.readFileSync(filePath, 'utf8');
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ // The nearest AGENTS.md, then CLAUDE.md, holding Lowdefy's block, from the
50
+ // config directory up to the project root.
51
+ function findBlock({ configDirectory, projectRoot }) {
52
+ let directory = configDirectory;
53
+ for (;;) {
54
+ for (const name of ['AGENTS.md', 'CLAUDE.md']) {
55
+ const text = readText(path.join(directory, name));
56
+ if (text?.includes(BLOCK_BEGIN)) {
57
+ const attributes = text.match(MARKER)?.[1] ?? '';
58
+ return {
59
+ directory,
60
+ name,
61
+ version: attributes.match(/\bversion=(\S+)/)?.[1] ?? null,
62
+ };
63
+ }
64
+ }
65
+ if (directory === projectRoot) {
66
+ return null;
67
+ }
68
+ const parent = path.dirname(directory);
69
+ if (parent === directory) {
70
+ return null;
71
+ }
72
+ directory = parent;
73
+ }
74
+ }
75
+
76
+ function majorMinor(version) {
77
+ const match = /^v?(\d+)\.(\d+)/.exec(version ?? '');
78
+ return match ? `${match[1]}.${match[2]}` : null;
79
+ }
80
+
81
+ function hasClaudeImport(directory) {
82
+ const text = readText(path.join(directory, 'CLAUDE.md'));
83
+ return text !== null && text.split(/\r?\n/).some((line) => line.trim() === '@AGENTS.md');
84
+ }
85
+
86
+ function hasLowdefyMcpServer(projectRoot) {
87
+ const text = readText(path.join(projectRoot, '.mcp.json'));
88
+ if (text === null) {
89
+ return false;
90
+ }
91
+ try {
92
+ return Boolean(JSON.parse(text)?.mcpServers?.lowdefy);
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
97
+
98
+ // Checks Lowdefy's agent block that `lowdefy agent-setup` writes: missing,
99
+ // from a different major.minor version than the app runs, or in AGENTS.md
100
+ // while the CLAUDE.md beside it lacks its import. Returns notices for build status's
101
+ // agentFiles field, never build warnings. Only reads; computed on every call,
102
+ // since the root files are not watched.
103
+ function checkAgentFiles({ configDirectory, lowdefyVersion }) {
104
+ if (typeof lowdefyVersion !== 'string' || lowdefyVersion === '') {
105
+ return [];
106
+ }
107
+ const start = path.resolve(configDirectory);
108
+ const projectRoot = findProjectRoot(start);
109
+ const block = findBlock({ configDirectory: start, projectRoot });
110
+ // agent-setup refuses `lowdefy: local` apps, so neither fix can run there.
111
+ const local = lowdefyVersion === 'local';
112
+ const notices = [];
113
+
114
+ if (!block) {
115
+ if (!local && hasLowdefyMcpServer(projectRoot)) {
116
+ notices.push({
117
+ code: 'block_missing',
118
+ message: `Lowdefy's agent instructions are missing from AGENTS.md. Tell the developer; don't run agent-setup yourself. They add them with \`npx lowdefy@${lowdefyVersion} agent-setup\`.`,
119
+ });
120
+ }
121
+ return notices;
122
+ }
123
+
124
+ const appMinor = majorMinor(lowdefyVersion);
125
+ if (!local && appMinor !== null && majorMinor(block.version) !== appMinor) {
126
+ const from = majorMinor(block.version)
127
+ ? `Lowdefy ${block.version}`
128
+ : 'an unknown Lowdefy version';
129
+ notices.push({
130
+ code: 'version_mismatch',
131
+ message: `Agent instructions are from ${from}, this app runs ${lowdefyVersion}, so some of their guidance may not apply. Tell the developer; don't run agent-setup yourself. They fix it with \`npx lowdefy@${lowdefyVersion} agent-setup\`, unless another app in this repository runs a different version.`,
132
+ });
133
+ }
134
+
135
+ // agent-setup writes CLAUDE.md beside the AGENTS.md it chose, and `@AGENTS.md`
136
+ // resolves relative to the file holding it, so the import is looked for
137
+ // there: the project root, or a --project-directory below it.
138
+ if (block.name === 'AGENTS.md' && !hasClaudeImport(block.directory)) {
139
+ notices.push({
140
+ code: 'claude_import_missing',
141
+ message:
142
+ 'Claude Code may not read AGENTS.md. Tell the developer: add `@AGENTS.md` to the top of CLAUDE.md, or run agent-setup.',
143
+ });
144
+ }
145
+ return notices;
146
+ }
147
+
148
+ export default checkAgentFiles;
@@ -0,0 +1,225 @@
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
+
21
+ import checkAgentFiles from './checkAgentFiles.js';
22
+
23
+ let root;
24
+
25
+ beforeEach(() => {
26
+ root = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-agent-files-'));
27
+ });
28
+
29
+ afterEach(() => {
30
+ fs.rmSync(root, { recursive: true, force: true });
31
+ });
32
+
33
+ function write(relativePath, text) {
34
+ const filePath = path.join(root, relativePath);
35
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
36
+ fs.writeFileSync(filePath, text);
37
+ }
38
+
39
+ function block(version) {
40
+ const attribute = version ? ` version=${version}` : '';
41
+ return `# Project\n\n<!-- BEGIN:lowdefy-agent-rules${attribute} -->\n## Lowdefy\n<!-- END:lowdefy-agent-rules -->\n`;
42
+ }
43
+
44
+ // A git checkout with the app at its root, AGENTS.md holding the block and
45
+ // CLAUDE.md importing it.
46
+ function checkout({ version = '7.1.4' } = {}) {
47
+ fs.mkdirSync(path.join(root, '.git'));
48
+ write('AGENTS.md', block(version));
49
+ write('CLAUDE.md', '@AGENTS.md\n');
50
+ }
51
+
52
+ const check = (lowdefyVersion, configDirectory = root) =>
53
+ checkAgentFiles({ configDirectory, lowdefyVersion });
54
+
55
+ describe('version', () => {
56
+ test('a patch difference is no notice', () => {
57
+ checkout({ version: '7.1.4' });
58
+ expect(check('7.1.0')).toEqual([]);
59
+ });
60
+
61
+ test('a minor difference is a version_mismatch with the fix', () => {
62
+ checkout({ version: '7.0.3' });
63
+ expect(check('7.1.0')).toEqual([
64
+ {
65
+ code: 'version_mismatch',
66
+ message:
67
+ "Agent instructions are from Lowdefy 7.0.3, this app runs 7.1.0, so some of their guidance may not apply. Tell the developer; don't run agent-setup yourself. They fix it with `npx lowdefy@7.1.0 agent-setup`, unless another app in this repository runs a different version.",
68
+ },
69
+ ]);
70
+ });
71
+
72
+ test('a marker without a version is a version_mismatch from an unknown version', () => {
73
+ checkout({ version: null });
74
+ const notices = check('7.1.0');
75
+ expect(notices).toHaveLength(1);
76
+ expect(notices[0].code).toBe('version_mismatch');
77
+ expect(notices[0].message).toMatch(/^Agent instructions are from an unknown Lowdefy version, /);
78
+ });
79
+
80
+ test('an app on local is not version-checked', () => {
81
+ checkout({ version: '6.0.0' });
82
+ expect(check('local')).toEqual([]);
83
+ });
84
+
85
+ test('no notices when the app version cannot be read', () => {
86
+ checkout({ version: '6.0.0' });
87
+ expect(check(null)).toEqual([]);
88
+ });
89
+ });
90
+
91
+ describe('Claude import', () => {
92
+ test('block in AGENTS.md and no root CLAUDE.md is claude_import_missing', () => {
93
+ checkout();
94
+ fs.rmSync(path.join(root, 'CLAUDE.md'));
95
+ expect(check('7.1.0')).toEqual([
96
+ {
97
+ code: 'claude_import_missing',
98
+ message:
99
+ 'Claude Code may not read AGENTS.md. Tell the developer: add `@AGENTS.md` to the top of CLAUDE.md, or run agent-setup.',
100
+ },
101
+ ]);
102
+ });
103
+
104
+ test('the import on any line of the root CLAUDE.md counts', () => {
105
+ checkout();
106
+ write('CLAUDE.md', '# Notes\n\n @AGENTS.md \nMore.\n');
107
+ expect(check('7.1.0')).toEqual([]);
108
+ });
109
+
110
+ test('a line that only mentions @AGENTS.md is not the import', () => {
111
+ checkout();
112
+ write('CLAUDE.md', 'See @AGENTS.md for rules.\n');
113
+ expect(check('7.1.0').map((notice) => notice.code)).toEqual(['claude_import_missing']);
114
+ });
115
+
116
+ test('only .claude/CLAUDE.md importing it is claude_import_missing', () => {
117
+ checkout();
118
+ fs.rmSync(path.join(root, 'CLAUDE.md'));
119
+ write('.claude/CLAUDE.md', '@AGENTS.md\n');
120
+ expect(check('7.1.0').map((notice) => notice.code)).toEqual(['claude_import_missing']);
121
+ });
122
+
123
+ test('block in CLAUDE.md needs no import', () => {
124
+ fs.mkdirSync(path.join(root, '.git'));
125
+ write('CLAUDE.md', block('7.1.0'));
126
+ expect(check('7.1.0')).toEqual([]);
127
+ });
128
+
129
+ test('the import is looked for beside an AGENTS.md below the git root', () => {
130
+ fs.mkdirSync(path.join(root, '.git'));
131
+ write('CLAUDE.md', '@AGENTS.md\n');
132
+ write('apps/web/AGENTS.md', block('7.1.0'));
133
+ expect(check('7.1.0', path.join(root, 'apps/web')).map((notice) => notice.code)).toEqual([
134
+ 'claude_import_missing',
135
+ ]);
136
+ write('apps/web/CLAUDE.md', '@AGENTS.md\n');
137
+ expect(check('7.1.0', path.join(root, 'apps/web'))).toEqual([]);
138
+ });
139
+
140
+ test('applies to an app on local', () => {
141
+ checkout();
142
+ fs.rmSync(path.join(root, 'CLAUDE.md'));
143
+ expect(check('local').map((notice) => notice.code)).toEqual(['claude_import_missing']);
144
+ });
145
+ });
146
+
147
+ describe('finding the block', () => {
148
+ test('a block in the app folder below the git root is found', () => {
149
+ fs.mkdirSync(path.join(root, '.git'));
150
+ write('apps/web/CLAUDE.md', '@AGENTS.md\n');
151
+ write('.mcp.json', JSON.stringify({ mcpServers: { lowdefy: {} } }));
152
+ write('apps/web/AGENTS.md', block('7.0.0'));
153
+ expect(check('7.1.0', path.join(root, 'apps/web')).map((notice) => notice.code)).toEqual([
154
+ 'version_mismatch',
155
+ ]);
156
+ });
157
+
158
+ test('the nearest block wins, AGENTS.md before CLAUDE.md', () => {
159
+ checkout({ version: '6.0.0' });
160
+ write('apps/web/CLAUDE.md', block('7.1.0'));
161
+ write('apps/web/AGENTS.md', 'No block here.\n');
162
+ expect(check('7.1.0', path.join(root, 'apps/web'))).toEqual([]);
163
+ });
164
+
165
+ test('a nested worktree never reads the outer checkout', () => {
166
+ checkout({ version: '6.0.0' });
167
+ write('.mcp.json', JSON.stringify({ mcpServers: { lowdefy: {} } }));
168
+ write('worktrees/feature/.git', 'gitdir: ../../.git/worktrees/feature\n');
169
+ write('worktrees/feature/app/lowdefy.yaml', 'lowdefy: 7.1.0\n');
170
+ expect(check('7.1.0', path.join(root, 'worktrees/feature/app'))).toEqual([]);
171
+ });
172
+
173
+ test('outside git, only the config directory is read', () => {
174
+ write('AGENTS.md', block('6.0.0'));
175
+ fs.mkdirSync(path.join(root, 'app'));
176
+ expect(check('7.1.0', path.join(root, 'app'))).toEqual([]);
177
+ });
178
+ });
179
+
180
+ describe('missing block', () => {
181
+ test('.mcp.json naming the lowdefy server is block_missing', () => {
182
+ fs.mkdirSync(path.join(root, '.git'));
183
+ write('.mcp.json', JSON.stringify({ mcpServers: { lowdefy: { command: 'npx' } } }));
184
+ expect(check('7.1.0')).toEqual([
185
+ {
186
+ code: 'block_missing',
187
+ message:
188
+ "Lowdefy's agent instructions are missing from AGENTS.md. Tell the developer; don't run agent-setup yourself. They add them with `npx lowdefy@7.1.0 agent-setup`.",
189
+ },
190
+ ]);
191
+ });
192
+
193
+ test('no .mcp.json is no notice', () => {
194
+ fs.mkdirSync(path.join(root, '.git'));
195
+ expect(check('7.1.0')).toEqual([]);
196
+ });
197
+
198
+ test('.mcp.json with only other servers is no notice', () => {
199
+ fs.mkdirSync(path.join(root, '.git'));
200
+ write('.mcp.json', JSON.stringify({ mcpServers: { other: {} } }));
201
+ expect(check('7.1.0')).toEqual([]);
202
+ });
203
+
204
+ test('an unparsable .mcp.json is no notice', () => {
205
+ fs.mkdirSync(path.join(root, '.git'));
206
+ write('.mcp.json', '{ not json');
207
+ expect(check('7.1.0')).toEqual([]);
208
+ });
209
+
210
+ test('an app on local is no notice', () => {
211
+ fs.mkdirSync(path.join(root, '.git'));
212
+ write('.mcp.json', JSON.stringify({ mcpServers: { lowdefy: {} } }));
213
+ expect(check('local')).toEqual([]);
214
+ });
215
+ });
216
+
217
+ test('never writes a file', () => {
218
+ fs.mkdirSync(path.join(root, '.git'));
219
+ write('AGENTS.md', block('6.0.0'));
220
+ write('.mcp.json', JSON.stringify({ mcpServers: { lowdefy: {} } }));
221
+ const before = fs.readdirSync(root).sort();
222
+ check('7.1.0');
223
+ expect(fs.readdirSync(root).sort()).toEqual(before);
224
+ expect(fs.readFileSync(path.join(root, 'AGENTS.md'), 'utf8')).toBe(block('6.0.0'));
225
+ });