@lowdefy/server-dev 6.0.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (625) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +63 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +224 -39
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +67 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +679 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  104. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  105. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  106. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  107. package/lib/docs/dataSets/findCollectionCollisions.js +52 -0
  108. package/lib/docs/dataSets/getDataStore.js +106 -0
  109. package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
  110. package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
  111. package/lib/docs/dataSets/groupDataSetByCollection.js +44 -0
  112. package/lib/docs/dataSets/insertFixture.js +57 -0
  113. package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
  114. package/lib/docs/dataSets/isDataStoreGone.js +29 -0
  115. package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
  116. package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
  117. package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
  118. package/lib/docs/dataSets/openDataSession.js +119 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +343 -0
  120. package/lib/docs/dataSets/pickDataStorePort.js +58 -0
  121. package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
  122. package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
  123. package/lib/docs/dataSets/readDataSession.js +41 -0
  124. package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
  125. package/lib/docs/dataSets/readDataSet.js +68 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +220 -0
  127. package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
  128. package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
  129. package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
  130. package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
  131. package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
  132. package/lib/docs/dataSets/sweepOrphanDatabases.js +33 -0
  133. package/lib/docs/dataSets/test/app/lowdefy.yaml +203 -0
  134. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  135. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +8 -0
  136. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  137. package/lib/docs/devEventBus.js +136 -0
  138. package/lib/docs/devEventBus.test.mjs +269 -0
  139. package/lib/docs/devToolDefinitions.js +529 -0
  140. package/lib/docs/docSources.js +21 -0
  141. package/lib/docs/docs.test.mjs +171 -15
  142. package/lib/docs/docsIndex.test.mjs +377 -0
  143. package/lib/docs/evalOperator.js +5 -3
  144. package/lib/docs/evalOperatorHeadless.js +84 -12
  145. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  146. package/lib/docs/evalOperatorInTab.js +2 -1
  147. package/lib/docs/findConfig.js +95 -49
  148. package/lib/docs/fitResponse.js +81 -0
  149. package/lib/docs/fitResponse.test.mjs +128 -0
  150. package/lib/docs/formatFeedback.js +26 -5
  151. package/lib/docs/formatFeedback.test.mjs +48 -0
  152. package/lib/docs/getAppMap.js +7 -1
  153. package/lib/docs/getBrowser.js +150 -80
  154. package/lib/docs/getBrowser.test.mjs +407 -16
  155. package/lib/docs/getBuildId.js +61 -0
  156. package/lib/docs/getBuildId.test.mjs +84 -0
  157. package/lib/docs/getBuildStatus.js +54 -6
  158. package/lib/docs/getBuildStatus.test.mjs +133 -0
  159. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  160. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  161. package/lib/docs/getCoreDoc.js +9 -1
  162. package/lib/docs/getDoc.js +100 -0
  163. package/lib/docs/getDocsIndex.js +152 -0
  164. package/lib/docs/getHazards.js +115 -0
  165. package/lib/docs/getHazards.test.mjs +175 -0
  166. package/lib/docs/getMailOutboxDirectory.js +26 -0
  167. package/lib/docs/getOverview.js +79 -13
  168. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  169. package/lib/docs/getPageBuildStatus.js +46 -0
  170. package/lib/docs/getPluginDoc.js +21 -23
  171. package/lib/docs/getSchema.js +33 -1
  172. package/lib/docs/getStaleStatus.js +36 -0
  173. package/lib/docs/getStaleStatus.test.mjs +74 -0
  174. package/lib/docs/guardJourneyOrigin.js +39 -0
  175. package/lib/docs/inspectState.js +5 -3
  176. package/lib/docs/inspectStateFromTab.js +2 -2
  177. package/lib/docs/inspectStateHeadless.js +89 -27
  178. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  179. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  180. package/lib/docs/installHeadlessShell.js +123 -0
  181. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  182. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  183. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  184. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  185. package/lib/docs/isPageReady.js +77 -0
  186. package/lib/docs/isPageReady.test.mjs +235 -0
  187. package/lib/docs/isRunErrorEntry.js +25 -0
  188. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  189. package/lib/docs/journeyRunRecording.js +35 -0
  190. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  191. package/lib/docs/keepEventStreamAlive.js +68 -0
  192. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  193. package/lib/docs/launchBrowser.js +83 -0
  194. package/lib/docs/launchBrowser.test.mjs +175 -0
  195. package/lib/docs/listModuleDocEntries.js +69 -0
  196. package/lib/docs/listPackageDocFiles.js +67 -0
  197. package/lib/docs/listPluginDocEntries.js +111 -0
  198. package/lib/docs/listPlugins.js +9 -58
  199. package/lib/docs/listTypes.js +14 -6
  200. package/lib/docs/loadFrameworkHazards.js +40 -0
  201. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  202. package/lib/docs/loadState.js +103 -35
  203. package/lib/docs/makeModuleManifestDoc.js +174 -0
  204. package/lib/docs/matchExercisedPages.js +35 -0
  205. package/lib/docs/matchJourneyRoute.js +69 -0
  206. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  207. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  208. package/lib/docs/nextJourneySequence.js +30 -0
  209. package/lib/docs/noBrowserError.js +22 -0
  210. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  211. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  212. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  213. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  214. package/lib/docs/observe/evaluateInvariants.js +183 -0
  215. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  216. package/lib/docs/observe/findingKey.js +29 -0
  217. package/lib/docs/observe/hasNoEffect.js +31 -0
  218. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  219. package/lib/docs/observe/installStepObserver.js +72 -0
  220. package/lib/docs/observe/isAppApiUrl.js +33 -0
  221. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  222. package/lib/docs/observe/readStepObserver.js +42 -0
  223. package/lib/docs/observe/relativeSource.js +29 -0
  224. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  225. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  226. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  227. package/lib/docs/observe/usesSearchStage.js +45 -0
  228. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  229. package/lib/docs/observe/watchJourneyContext.js +75 -0
  230. package/lib/docs/observe/withAppErrors.js +31 -0
  231. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  232. package/lib/docs/openJourney.js +94 -0
  233. package/lib/docs/openJourneyEmail.js +41 -0
  234. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  235. package/lib/docs/pageBlocks.js +47 -0
  236. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  237. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  238. package/lib/docs/rails/authEngineActions.js +53 -0
  239. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  240. package/lib/docs/rails/authEngineMethods.js +52 -0
  241. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  242. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  243. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  244. package/lib/docs/rails/checkClickRails.js +56 -0
  245. package/lib/docs/rails/createDataSetRails.js +82 -0
  246. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  247. package/lib/docs/rails/findExternalBlocks.js +147 -0
  248. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  249. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  250. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  251. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  252. package/lib/docs/readConnectionPlugins.js +26 -0
  253. package/lib/docs/readDevAuthMode.js +32 -0
  254. package/lib/docs/readDocEntry.js +40 -0
  255. package/lib/docs/readDocTitle.js +26 -0
  256. package/lib/docs/readJourneyEmailMatch.js +57 -0
  257. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  258. package/lib/docs/readJourneySession.js +56 -0
  259. package/lib/docs/readMailOutbox.js +37 -0
  260. package/lib/docs/readPagePath.js +27 -0
  261. package/lib/docs/readPluginPackages.js +99 -0
  262. package/lib/docs/readProxyBuildWait.js +38 -0
  263. package/lib/docs/readRoutineSteps.js +45 -0
  264. package/lib/docs/registerShownTextEngine.js +118 -0
  265. package/lib/docs/requestRestart.js +30 -0
  266. package/lib/docs/requestRestart.test.mjs +49 -0
  267. package/lib/docs/resolvePageInstance.js +46 -0
  268. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  269. package/lib/docs/resolveSource.js +11 -7
  270. package/lib/docs/resolveSource.test.mjs +1 -1
  271. package/lib/docs/resolveToolCaller.js +62 -0
  272. package/lib/docs/reviewPage.js +51 -0
  273. package/lib/docs/reviewPageBuilds.js +56 -0
  274. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  275. package/lib/docs/runCheck.js +75 -0
  276. package/lib/docs/runCheck.test.mjs +91 -0
  277. package/lib/docs/runCheckChild.js +49 -0
  278. package/lib/docs/runEndpoint.js +185 -0
  279. package/lib/docs/runEndpoint.test.mjs +509 -0
  280. package/lib/docs/runErrorBuffers.js +99 -0
  281. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  282. package/lib/docs/runJourney.js +347 -0
  283. package/lib/docs/runJourney.test.mjs +2383 -0
  284. package/lib/docs/runJourneyAsUsers.js +44 -0
  285. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  286. package/lib/docs/runJourneySteps.js +1320 -0
  287. package/lib/docs/runRequest.js +100 -59
  288. package/lib/docs/runRequest.test.mjs +190 -0
  289. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  290. package/lib/docs/scaffoldPage.js +3 -1
  291. package/lib/docs/screenshotPage.js +163 -11
  292. package/lib/docs/screenshotPage.test.mjs +263 -1
  293. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  294. package/lib/docs/searchDocs.js +82 -30
  295. package/lib/docs/searchDocs.test.mjs +100 -0
  296. package/lib/docs/searchIcons.js +90 -0
  297. package/lib/docs/searchIcons.test.mjs +127 -0
  298. package/lib/docs/selectExercised.js +39 -0
  299. package/lib/docs/selectFinalState.js +66 -0
  300. package/lib/docs/selectFinalState.test.mjs +51 -0
  301. package/lib/docs/serverErrorStore.js +54 -0
  302. package/lib/docs/serverErrorStore.test.mjs +50 -0
  303. package/lib/docs/setupTestFixtures.mjs +133 -39
  304. package/lib/docs/shownTextSelector.js +28 -0
  305. package/lib/docs/snapshotState.js +13 -5
  306. package/lib/docs/splitSearchTerms.js +62 -0
  307. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  308. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  309. package/lib/docs/tabAvailable.js +6 -5
  310. package/lib/docs/tabChannel.js +127 -21
  311. package/lib/docs/tabChannel.test.mjs +187 -29
  312. package/lib/docs/unsettledPageNote.js +24 -0
  313. package/lib/docs/validateJourneyMail.js +36 -0
  314. package/lib/docs/validateJourneyTimeout.js +38 -0
  315. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  316. package/lib/docs/validateRecordingParam.js +42 -0
  317. package/lib/docs/validateStateSelection.js +33 -0
  318. package/lib/docs/validateStateSelection.test.mjs +33 -0
  319. package/lib/docs/validateViewport.js +51 -0
  320. package/lib/docs/validateViewport.test.mjs +54 -0
  321. package/lib/docs/waitForBuild.js +50 -0
  322. package/lib/docs/waitForBuild.test.mjs +75 -0
  323. package/lib/docs/waitForJourneyEmail.js +77 -0
  324. package/lib/docs/withBrowserSlot.js +45 -0
  325. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  326. package/lib/docs/withDataSession.js +53 -0
  327. package/lib/server/applyDataSetRedirect.js +83 -0
  328. package/lib/server/applyDataSetRedirect.test.mjs +340 -0
  329. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +204 -0
  330. package/lib/server/applyDataSetRedirectWalled.test.mjs +131 -0
  331. package/lib/server/auth/createSystemContext.js +73 -0
  332. package/lib/server/auth/getAuth.js +46 -0
  333. package/lib/server/auth/getHeadlessUser.js +9 -8
  334. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  335. package/lib/server/auth/getMockUser.js +18 -14
  336. package/lib/server/auth/{getAuthConfig.js → getStrategies.js} +12 -10
  337. package/lib/server/auth/headlessUser.js +3 -2
  338. package/lib/server/auth/journeyActor.js +35 -0
  339. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  340. package/lib/server/checkPageRecord.js +44 -0
  341. package/lib/server/contextMapBudget.js +24 -0
  342. package/lib/server/contextMapBudget.test.mjs +98 -0
  343. package/lib/server/continueJitKeys.js +28 -0
  344. package/lib/server/continueJitKeys.test.mjs +43 -0
  345. package/lib/server/createDataSessionEndedError.js +33 -0
  346. package/lib/server/createDataSetRefusal.js +26 -0
  347. package/lib/server/createLowdefyContext.js +174 -41
  348. package/lib/server/createLowdefyContext.test.mjs +437 -0
  349. package/lib/server/getClientAddress.js +51 -0
  350. package/lib/server/getClientAddress.test.mjs +56 -0
  351. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  352. package/lib/server/hashConfigContent.js +26 -0
  353. package/lib/server/importAppCode.test.mjs +57 -0
  354. package/lib/server/isRoutineStep.js +27 -0
  355. package/lib/server/jitPageBuilder.js +309 -126
  356. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  357. package/lib/server/jitPageBuilder.test.mjs +218 -0
  358. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  359. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  360. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  361. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  362. package/lib/server/journeyCookies.js +22 -0
  363. package/lib/server/journeyCookies.test.mjs +145 -0
  364. package/lib/server/loadDynamicJsMap.js +47 -0
  365. package/lib/server/log/createHandleError.js +38 -2
  366. package/lib/server/log/createHandleError.test.mjs +125 -2
  367. package/lib/server/log/createLogger.js +3 -0
  368. package/lib/server/log/createLogger.test.mjs +77 -0
  369. package/lib/server/log/logRequest.js +2 -3
  370. package/lib/server/mutantDataSetWrappers.test.mjs +112 -0
  371. package/lib/server/mutants/applyMutant.js +39 -0
  372. package/lib/server/mutants/artifactPatterns.js +25 -0
  373. package/lib/server/mutants/buildAnchor.js +45 -0
  374. package/lib/server/mutants/collectSharedIds.js +41 -0
  375. package/lib/server/mutants/configPathSegments.js +38 -0
  376. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  377. package/lib/server/mutants/describeEvent.js +22 -0
  378. package/lib/server/mutants/enumerateArtifact.js +56 -0
  379. package/lib/server/mutants/findNodeByKey.js +45 -0
  380. package/lib/server/mutants/findNodeByPointer.js +49 -0
  381. package/lib/server/mutants/getArtifactKind.js +32 -0
  382. package/lib/server/mutants/groupMutantCopies.js +56 -0
  383. package/lib/server/mutants/hashArtifact.js +25 -0
  384. package/lib/server/mutants/hashMutantTarget.js +34 -0
  385. package/lib/server/mutants/listMutants.js +122 -0
  386. package/lib/server/mutants/listMutants.test.mjs +502 -0
  387. package/lib/server/mutants/mutantId.js +30 -0
  388. package/lib/server/mutants/mutantRuns.js +57 -0
  389. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  390. package/lib/server/mutants/operators/dropAction.js +47 -0
  391. package/lib/server/mutants/operators/dropBlock.js +44 -0
  392. package/lib/server/mutants/operators/dropPayload.js +52 -0
  393. package/lib/server/mutants/operators/dropStep.js +44 -0
  394. package/lib/server/mutants/operators/flipVisible.js +53 -0
  395. package/lib/server/mutants/operators/index.js +37 -0
  396. package/lib/server/mutants/operators/retargetLink.js +72 -0
  397. package/lib/server/mutants/operators/skipValidate.js +77 -0
  398. package/lib/server/mutants/operators/swapIf.js +112 -0
  399. package/lib/server/mutants/operators.test.mjs +396 -0
  400. package/lib/server/mutants/positionFreePath.js +38 -0
  401. package/lib/server/mutants/removeFromParent.js +28 -0
  402. package/lib/server/mutants/test/fixtures.mjs +173 -0
  403. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  404. package/lib/server/mutants/validateMutantParam.js +69 -0
  405. package/lib/server/mutants/validateMutantsBody.js +88 -0
  406. package/lib/server/mutants/walkArtifact.js +179 -0
  407. package/lib/server/pageBuildRecords.js +131 -0
  408. package/lib/server/pageBuildRecords.test.mjs +258 -0
  409. package/lib/server/pageCache.mjs +22 -7
  410. package/lib/server/pageCache.test.mjs +22 -7
  411. package/lib/server/pageRebuilds.test.mjs +543 -0
  412. package/lib/server/pruneJitMaps.js +36 -0
  413. package/lib/server/pruneJitMaps.test.mjs +55 -0
  414. package/lib/server/readInstanceRecord.js +33 -0
  415. package/lib/server/readInstanceRecord.test.mjs +99 -0
  416. package/lib/server/readMergedMaps.js +86 -0
  417. package/lib/server/readMergedMaps.test.mjs +79 -0
  418. package/lib/server/recording/appendRecords.js +33 -0
  419. package/lib/server/recording/errorRecordingStamp.js +34 -0
  420. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  421. package/lib/server/recording/isRecordingEnabled.js +31 -0
  422. package/lib/server/recording/readRecordingCookie.js +71 -0
  423. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  424. package/lib/server/recording/recordingCookiePayload.js +33 -0
  425. package/lib/server/recording/servedBuilds.js +46 -0
  426. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  427. package/lib/server/scrubSecrets.js +23 -0
  428. package/lib/server/sinkExternalConnections.js +95 -0
  429. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  430. package/lib/server/skipStaleMapWrites.js +62 -0
  431. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  432. package/lib/server/startIdleGc.js +67 -0
  433. package/lib/server/startIdleGc.test.mjs +138 -0
  434. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  435. package/lib/server/trackSessionWork.js +30 -0
  436. package/lib/vite/createDevServerExclude.js +40 -0
  437. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  438. package/manager/getContext.mjs +24 -2
  439. package/manager/processes/initialBuild.mjs +14 -0
  440. package/manager/processes/initialBuild.test.mjs +101 -0
  441. package/manager/processes/lowdefyBuild.mjs +30 -7
  442. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
  443. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  444. package/manager/processes/optimizeDependencies.mjs +59 -0
  445. package/manager/processes/restartServer.mjs +34 -6
  446. package/manager/processes/restartServer.test.mjs +162 -0
  447. package/manager/processes/shutdownServer.mjs +11 -0
  448. package/manager/processes/shutdownServer.test.mjs +45 -0
  449. package/manager/processes/startFirstServer.mjs +46 -0
  450. package/manager/processes/startFirstServer.test.mjs +326 -0
  451. package/manager/processes/startMailSink.mjs +135 -0
  452. package/manager/processes/startMailSink.test.mjs +146 -0
  453. package/manager/processes/startProxy.mjs +129 -12
  454. package/manager/processes/startProxy.test.mjs +366 -0
  455. package/manager/processes/startServer.mjs +47 -6
  456. package/manager/processes/startServer.test.mjs +155 -0
  457. package/manager/processes/startWatchers.mjs +6 -4
  458. package/manager/processes/syncServer.mjs +69 -0
  459. package/manager/processes/syncServer.test.mjs +141 -0
  460. package/manager/processes/waitForDevServer.mjs +48 -0
  461. package/manager/processes/waitForDevServer.test.mjs +112 -0
  462. package/manager/run.mjs +128 -43
  463. package/manager/utils/BatchChanges.mjs +21 -5
  464. package/manager/utils/BatchChanges.test.mjs +77 -0
  465. package/manager/utils/acquireDevInstance.mjs +99 -0
  466. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  467. package/manager/utils/createBuildActivity.mjs +67 -0
  468. package/manager/utils/createBuildActivity.test.mjs +58 -0
  469. package/manager/utils/createChangeSignal.mjs +39 -0
  470. package/manager/utils/createChangeSignal.test.mjs +62 -0
  471. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  472. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  473. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  474. package/manager/utils/createDotPathIgnore.mjs +44 -0
  475. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  476. package/manager/utils/createIdleGc.mjs +51 -0
  477. package/manager/utils/createIdleGc.test.mjs +77 -0
  478. package/manager/utils/createRequestActivity.mjs +100 -0
  479. package/manager/utils/createRequestActivity.test.mjs +121 -0
  480. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  481. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  482. package/manager/utils/createServerEnv.mjs +39 -0
  483. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  485. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  486. package/manager/utils/importFresh.mjs +46 -0
  487. package/manager/utils/importFresh.test.mjs +72 -0
  488. package/manager/utils/importFreshWorker.mjs +20 -0
  489. package/manager/utils/killTaggedBrowser.mjs +35 -0
  490. package/manager/utils/pagePathChanged.mjs +56 -0
  491. package/manager/utils/pruneRecordings.mjs +85 -0
  492. package/manager/utils/pruneRecordings.test.mjs +117 -0
  493. package/manager/utils/publishBuildDirectory.mjs +121 -0
  494. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  495. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  496. package/manager/utils/readBasePath.mjs +27 -0
  497. package/manager/utils/readBasePath.test.mjs +43 -0
  498. package/manager/utils/readBuildStatusWait.mjs +67 -0
  499. package/manager/utils/readManagerVersion.mjs +30 -0
  500. package/manager/utils/readManagerVersion.test.mjs +35 -0
  501. package/manager/utils/readPluginDefinitions.mjs +36 -0
  502. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  503. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  504. package/manager/utils/resolvePorts.mjs +49 -0
  505. package/manager/utils/setupWatcher.mjs +19 -11
  506. package/manager/utils/startRecordingPruner.mjs +45 -0
  507. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  508. package/manager/utils/waitForChildExit.mjs +53 -0
  509. package/manager/utils/waitForChildExit.test.mjs +70 -0
  510. package/manager/utils/waitForServer.mjs +44 -0
  511. package/manager/utils/writeBuildStatus.mjs +1 -1
  512. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  513. package/manager/watchers/envWatcher.mjs +9 -2
  514. package/manager/watchers/envWatcher.test.mjs +87 -0
  515. package/manager/watchers/listServerSidePackages.mjs +47 -0
  516. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  517. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  518. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  519. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  520. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  521. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  522. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  523. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  524. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  525. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  526. package/package.json +61 -49
  527. package/package.original.json +61 -49
  528. package/src/app.js +131 -20
  529. package/src/html/renderDevPage.js +63 -11
  530. package/src/html/renderDevPage.test.mjs +138 -0
  531. package/src/lib/getRequestPath.js +29 -0
  532. package/src/middleware/createSameOriginGuard.js +37 -0
  533. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  534. package/src/middleware/errorHandler.js +51 -1
  535. package/src/middleware/errorHandler.test.mjs +196 -14
  536. package/src/middleware/isRebindingSafeHost.js +44 -0
  537. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  538. package/src/middleware/localDevToolsOnly.js +38 -0
  539. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  540. package/src/middleware/noStore.js +27 -0
  541. package/src/middleware/noStore.test.mjs +48 -0
  542. package/src/middleware/requestTimeout.js +38 -0
  543. package/src/middleware/staleFlag.js +76 -0
  544. package/src/middleware/staleFlag.test.mjs +127 -0
  545. package/src/middleware/stampCaller.js +38 -0
  546. package/src/middleware/stampCaller.test.mjs +64 -0
  547. package/src/routes/agent.js +15 -3
  548. package/src/routes/auth.js +29 -20
  549. package/src/routes/auth.test.mjs +30 -56
  550. package/src/routes/clientError.js +13 -13
  551. package/src/routes/cron.js +3 -1
  552. package/src/routes/cronForward.js +4 -2
  553. package/src/routes/detached.js +22 -9
  554. package/src/routes/devInspect.js +17 -8
  555. package/src/routes/devInspect.test.mjs +44 -7
  556. package/src/routes/devRecording.js +113 -0
  557. package/src/routes/devRecording.test.mjs +253 -0
  558. package/src/routes/docs/buildStatus.js +10 -2
  559. package/src/routes/docs/buildStatus.test.mjs +46 -0
  560. package/src/routes/docs/content.js +5 -3
  561. package/src/routes/docs/evalOperator.js +15 -1
  562. package/src/routes/docs/events.js +50 -0
  563. package/src/routes/docs/events.test.mjs +117 -0
  564. package/src/routes/docs/find.js +9 -1
  565. package/src/routes/docs/icons.js +30 -0
  566. package/src/routes/docs/inspectState.js +16 -1
  567. package/src/routes/docs/journey.js +194 -0
  568. package/src/routes/docs/journey.test.mjs +401 -0
  569. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  570. package/src/routes/docs/loadState.js +4 -2
  571. package/src/routes/docs/mcp.js +34 -3
  572. package/src/routes/docs/mcp.test.mjs +130 -0
  573. package/src/routes/docs/mutants.js +78 -0
  574. package/src/routes/docs/mutants.test.mjs +180 -0
  575. package/src/routes/docs/parsePathParamsParam.js +41 -0
  576. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  577. package/src/routes/docs/parseUserParam.js +27 -21
  578. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  579. package/src/routes/docs/restart.js +35 -0
  580. package/src/routes/docs/restart.test.mjs +64 -0
  581. package/src/routes/docs/runEndpoint.js +54 -0
  582. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  583. package/src/routes/docs/runRequest.js +25 -8
  584. package/src/routes/docs/runRequest.test.mjs +92 -0
  585. package/src/routes/docs/screenshot.js +50 -2
  586. package/src/routes/docs/screenshot.test.mjs +117 -0
  587. package/src/routes/docs/search.js +15 -2
  588. package/src/routes/docs/snapshotState.js +17 -3
  589. package/src/routes/endpoints.js +27 -26
  590. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  591. package/src/routes/feedback.js +10 -13
  592. package/src/routes/jitPage.js +99 -35
  593. package/src/routes/jitPage.test.mjs +248 -0
  594. package/src/routes/mcp.js +82 -5
  595. package/src/routes/mcp.test.mjs +215 -0
  596. package/src/routes/mcpMemberToken.test.mjs +125 -0
  597. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  598. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  599. package/src/routes/mountDevPageRoutes.js +25 -0
  600. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  601. package/src/routes/mountOauthDiscovery.js +44 -0
  602. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  604. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  605. package/src/routes/reload.js +16 -0
  606. package/src/routes/request.js +6 -4
  607. package/src/routes/request.test.mjs +9 -5
  608. package/src/routes/requestBody.test.mjs +159 -0
  609. package/src/routes/usage.js +6 -2
  610. package/src/routes/user.js +27 -0
  611. package/src/routes/websocket.js +29 -8
  612. package/src/routes/websocket.test.mjs +65 -0
  613. package/src/routes/wellKnownFallback.js +30 -0
  614. package/src/routes/wellKnownFallback.test.mjs +32 -0
  615. package/src/websocket/devWebSocket.js +41 -26
  616. package/src/websocket/devWebSocket.test.mjs +157 -0
  617. package/vite.config.js +53 -23
  618. package/lib/server/auth/getDevSession.js +0 -70
  619. package/lib/server/auth/getDevSession.test.mjs +0 -138
  620. package/lib/server/auth/session.js +0 -39
  621. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  622. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  623. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  624. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  625. package/src/routes/apiPage.js +0 -37
@@ -15,7 +15,9 @@
15
15
  */
16
16
 
17
17
  import { type } from '@lowdefy/helpers';
18
+ import { JOURNEY_STEP_SCHEMAS, journeyStepSchema } from '@lowdefy/node-utils';
18
19
 
20
+ import getHazards from './getHazards.js';
19
21
  import normalizeTypeKind from './normalizeTypeKind.js';
20
22
  import readBuildArtifact from './readBuildArtifact.js';
21
23
 
@@ -27,11 +29,40 @@ const SCHEMA_ARTIFACTS = {
27
29
  requests: 'plugins/requestSchemas.json',
28
30
  };
29
31
 
32
+ const JOURNEY_STEP_KINDS = ['journey-step', 'journey-steps'];
33
+
34
+ // kind journey-step returns the journey step grammar: every step, or the one
35
+ // step `type` names.
36
+ function getJourneyStepSchema({ typeName }) {
37
+ if (type.isNone(typeName) || typeName === '' || typeName === 'all') {
38
+ return { kind: 'journey-step', schema: journeyStepSchema };
39
+ }
40
+ const schema = JOURNEY_STEP_SCHEMAS[typeName];
41
+ if (type.isNone(schema)) {
42
+ return null;
43
+ }
44
+ return { kind: 'journey-step', type: typeName, schema };
45
+ }
46
+
30
47
  function getSchema({ kind, type: typeName }) {
48
+ if (JOURNEY_STEP_KINDS.includes(String(kind ?? '').toLowerCase())) {
49
+ return getJourneyStepSchema({ typeName });
50
+ }
31
51
  const normalizedKind = normalizeTypeKind({ kind });
32
52
  if (type.isNone(SCHEMA_ARTIFACTS[normalizedKind])) {
33
53
  throw new Error(
34
- `No schemas available for type kind. Received ${JSON.stringify(kind)}. Use one of: blocks, operators, actions, connections, requests.`
54
+ `No schemas available for type kind. Received ${JSON.stringify(
55
+ kind
56
+ )}. Use one of: blocks, operators, actions, connections, requests, or journey-step for the journey step grammar.`
57
+ );
58
+ }
59
+ // type is optional only for journey-step, so a type kind without one is
60
+ // refused rather than looked up as "undefined".
61
+ if (type.isNone(typeName) || typeName === '') {
62
+ throw new Error(
63
+ `Getting a ${normalizedKind} schema requires a "type", the exact type name. Received ${JSON.stringify(
64
+ typeName
65
+ )}.`
35
66
  );
36
67
  }
37
68
  const schemas = readBuildArtifact({ name: SCHEMA_ARTIFACTS[normalizedKind] }) ?? {};
@@ -59,6 +90,7 @@ function getSchema({ kind, type: typeName }) {
59
90
  result.meta = blockMetas[typeName];
60
91
  }
61
92
  }
93
+ result.hazards = getHazards({ kind: normalizedKind, type: typeName });
62
94
  return result;
63
95
  }
64
96
 
@@ -0,0 +1,36 @@
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 readBuildArtifact from './readBuildArtifact.js';
18
+
19
+ // When a dev rebuild fails the manager writes status "error" to
20
+ // build/buildStatus.json but leaves the previous build's artifacts in place, so
21
+ // every docs read keeps answering from a build that predates the caller's
22
+ // edits. Returns null — not { stale: false } — when the build is ok or unknown:
23
+ // a response with no stale fields means not stale, and a false flag on every
24
+ // response costs tokens on every call.
25
+ function getStaleStatus() {
26
+ const build = readBuildArtifact({ name: 'buildStatus.json' });
27
+ if (build?.status !== 'error') return null;
28
+ return {
29
+ stale: true,
30
+ staleSince: build.timestamp ?? null,
31
+ staleReason:
32
+ 'The last build failed. The dev server is still serving the previous successful build, so this result may not reflect your latest edits. Call lowdefy_build_status (or GET /lowdefy-docs/build-status) for the errors.',
33
+ };
34
+ }
35
+
36
+ export default getStaleStatus;
@@ -0,0 +1,74 @@
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
+ // getStaleStatus reads build/buildStatus.json from process.cwd() — point it at
22
+ // a throwaway server directory before the module is imported.
23
+ const fixtureDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-stale-test-'));
24
+ fs.mkdirSync(path.join(fixtureDir, 'build'));
25
+ process.chdir(fixtureDir);
26
+
27
+ const statusPath = path.join(fixtureDir, 'build', 'buildStatus.json');
28
+
29
+ function writeStatus(status) {
30
+ fs.writeFileSync(statusPath, JSON.stringify(status));
31
+ }
32
+
33
+ function removeStatus() {
34
+ if (fs.existsSync(statusPath)) fs.rmSync(statusPath);
35
+ }
36
+
37
+ const { default: getStaleStatus } = await import('./getStaleStatus.js');
38
+
39
+ test('getStaleStatus returns null when buildStatus.json is missing', () => {
40
+ removeStatus();
41
+ expect(getStaleStatus()).toBe(null);
42
+ });
43
+
44
+ test('getStaleStatus returns null when the build status is ok', () => {
45
+ writeStatus({ status: 'ok', timestamp: '2026-01-01T00:00:00.000Z', errors: [], warnings: [] });
46
+ expect(getStaleStatus()).toBe(null);
47
+ });
48
+
49
+ test('getStaleStatus returns null when the build status is unknown', () => {
50
+ writeStatus({ status: 'unknown' });
51
+ expect(getStaleStatus()).toBe(null);
52
+ });
53
+
54
+ test('getStaleStatus returns the stale fields with the artifact timestamp when the build failed', () => {
55
+ writeStatus({
56
+ status: 'error',
57
+ timestamp: '2026-02-03T04:05:06.000Z',
58
+ errors: [{ message: 'Block type "Buton" not found.' }],
59
+ });
60
+ const status = getStaleStatus();
61
+ expect(status.stale).toBe(true);
62
+ expect(status.staleSince).toEqual('2026-02-03T04:05:06.000Z');
63
+ expect(status.staleReason).toContain('The last build failed.');
64
+ expect(status.staleReason).toContain('lowdefy_build_status');
65
+ });
66
+
67
+ test('getStaleStatus returns a null staleSince when the failed build has no timestamp', () => {
68
+ writeStatus({ status: 'error', errors: [] });
69
+ expect(getStaleStatus()).toEqual({
70
+ stale: true,
71
+ staleSince: null,
72
+ staleReason: expect.any(String),
73
+ });
74
+ });
@@ -0,0 +1,39 @@
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
+ const DEFAULT_PORTS = { 'http:': '80', 'https:': '443' };
18
+
19
+ function portOf(url) {
20
+ return url.port === '' ? DEFAULT_PORTS[url.protocol] : url.port;
21
+ }
22
+
23
+ // The data cookie is set for the journey's origin only, so a request to the dev server's port on
24
+ // another host (127.0.0.1 against localhost, [::1], a LAN address) would carry none and read the
25
+ // real database as a signed-out caller. Such requests are aborted before they leave the browser and
26
+ // handed to onLeave; requests to other ports and other sites are untouched.
27
+ async function guardJourneyOrigin({ context, origin, onLeave }) {
28
+ const originUrl = new URL(origin);
29
+ const originPort = portOf(originUrl);
30
+ await context.route(
31
+ (url) => portOf(url) === originPort && url.hostname !== originUrl.hostname,
32
+ async (route) => {
33
+ onLeave(route.request().url());
34
+ await route.abort('blockedbyclient');
35
+ }
36
+ );
37
+ }
38
+
39
+ export default guardJourneyOrigin;
@@ -22,25 +22,27 @@ import resolveSource from './resolveSource.js';
22
22
  // headless-only. Falls back to headless whenever the tab path isn't usable
23
23
  // (no tab connected, or `source: 'tab'` was requested but it errored) so
24
24
  // agents always get an answer.
25
- async function inspectState({ origin, pageId, source, user }) {
25
+ async function inspectState({ origin, pageId, pathParams, source, user, data }) {
26
26
  const { tryTab, error, invalidInput } = resolveSource({
27
27
  name: 'inspectState',
28
28
  pageId,
29
+ pathParams,
29
30
  source,
30
31
  user,
32
+ data,
31
33
  });
32
34
  if (error) {
33
35
  return { error, invalidInput };
34
36
  }
35
37
 
36
38
  if (tryTab) {
37
- const result = await inspectStateFromTab({ pageId });
39
+ const result = await inspectStateFromTab({ pageId, pathParams });
38
40
  if (!result?.error) {
39
41
  return { ...result, source: 'tab' };
40
42
  }
41
43
  }
42
44
 
43
- const result = await inspectStateHeadless({ origin, pageId, user });
45
+ const result = await inspectStateHeadless({ origin, pageId, pathParams, user, data });
44
46
  return { ...result, source: 'headless' };
45
47
  }
46
48
 
@@ -26,8 +26,8 @@ import { requestFromTab } from './tabChannel.js';
26
26
  // (see tabChannel.js) — a serialized snapshot string on success, or an
27
27
  // `{ error }` object if no tab is connected, the request timed out, or
28
28
  // Inspector.jsx itself failed to build the snapshot.
29
- async function inspectStateFromTab({ pageId } = {}) {
30
- const response = await requestFromTab({ pageId, event: 'inspect-request' });
29
+ async function inspectStateFromTab({ pageId, pathParams } = {}) {
30
+ const response = await requestFromTab({ pageId, pathParams, event: 'inspect-request' });
31
31
  if (response?.error) {
32
32
  return { error: response.error };
33
33
  }
@@ -17,18 +17,27 @@
17
17
  import { type } from '@lowdefy/helpers';
18
18
 
19
19
  import { getBrowser, openPage, buildPageUrl } from './getBrowser.js';
20
+ import noBrowserError from './noBrowserError.js';
21
+ import resolvePageInstance from './resolvePageInstance.js';
22
+ import resolveToolCaller from './resolveToolCaller.js';
23
+ import unsettledPageNote from './unsettledPageNote.js';
24
+ import withBrowserSlot from './withBrowserSlot.js';
25
+ import withDataSession from './withDataSession.js';
20
26
 
21
27
  // Collects a state snapshot from a headless Chromium tab navigated to the
22
- // page's own route. Mirrors Inspector.jsx's buildSnapshot (the live-tab
28
+ // page instance's own route (`pathParams` fill a patterned page's placeholders),
29
+ // read under the instance key. Mirrors Inspector.jsx's buildSnapshot (the live-tab
23
30
  // equivalent), but window.lowdefy does not expose the app's serializer, so
24
31
  // the snapshot is round-tripped through JSON.parse(JSON.stringify(...))
25
32
  // inside the page instead. That strips functions/undefined and turns Dates
26
33
  // into plain ISO strings — good enough for agent inspection, just not a
27
34
  // byte-for-byte match of the app's own `~d`-tagged serialization.
28
- async function inspectStateHeadless({ origin, pageId, user, timeout = 15000 }) {
35
+ async function inspectStateHeadless({ origin, pageId, pathParams, user, data, timeout = 15000 }) {
29
36
  if (type.isNone(origin) || !type.isString(origin)) {
30
37
  return {
31
- error: `inspectStateHeadless requires an "origin" string. Received ${JSON.stringify(origin)}.`,
38
+ error: `inspectStateHeadless requires an "origin" string. Received ${JSON.stringify(
39
+ origin
40
+ )}.`,
32
41
  };
33
42
  }
34
43
  if (type.isNone(pageId) || !type.isString(pageId)) {
@@ -37,40 +46,93 @@ async function inspectStateHeadless({ origin, pageId, user, timeout = 15000 }) {
37
46
  };
38
47
  }
39
48
 
49
+ const instance = resolvePageInstance({ pageId, pathParams });
50
+ if (!type.isUndefined(instance.error)) {
51
+ return { error: instance.error, invalidInput: true };
52
+ }
53
+
54
+ const caller = await resolveToolCaller({ user, data });
55
+ if (!type.isUndefined(caller.error)) {
56
+ return caller;
57
+ }
58
+
59
+ return withBrowserSlot({
60
+ task: () =>
61
+ withDataSession({
62
+ dataSet: caller.dataSet,
63
+ task: ({ dataCookie }) =>
64
+ inspectStateInBrowser({
65
+ origin,
66
+ pageId,
67
+ pathParams,
68
+ instance,
69
+ user: caller.user,
70
+ dataCookie,
71
+ timeout,
72
+ }),
73
+ }),
74
+ });
75
+ }
76
+
77
+ // The part of inspectStateHeadless that runs in the browser, inside a browser slot.
78
+ async function inspectStateInBrowser({
79
+ origin,
80
+ pageId,
81
+ pathParams,
82
+ instance,
83
+ user,
84
+ dataCookie,
85
+ timeout,
86
+ }) {
40
87
  let browser;
41
88
  try {
42
89
  browser = await getBrowser();
43
90
  } catch (error) {
44
- return {
45
- error: `No Chromium available. Run: npx playwright install chromium (${error.message})`,
46
- };
91
+ return { error: noBrowserError(error) };
47
92
  }
48
93
 
49
- const url = buildPageUrl({ origin, pageId });
94
+ const url = buildPageUrl({ origin, pageId, path: instance.path, pathParams });
50
95
 
51
96
  let context;
52
97
  try {
53
- const opened = await openPage({ browser, origin, pageId, user, timeout });
98
+ const opened = await openPage({
99
+ browser,
100
+ origin,
101
+ pageId,
102
+ path: instance.path,
103
+ pathParams,
104
+ user,
105
+ dataCookie,
106
+ timeout,
107
+ });
54
108
  context = opened.context;
55
- const snapshot = await opened.page.evaluate((id) => {
56
- const lowdefy = window.lowdefy;
57
- const pageContext = lowdefy?.contexts?.[`page:${id}`];
58
- if (!pageContext) {
59
- return { error: `No live context for page "${id}".` };
60
- }
61
- return JSON.parse(
62
- JSON.stringify({
63
- pageId: id,
64
- state: pageContext.state,
65
- requests: pageContext.requests,
66
- eventLog: (pageContext.eventLog ?? []).slice(-50),
67
- global: lowdefy.lowdefyGlobal,
68
- user: lowdefy.user,
69
- input: lowdefy.inputs?.[`page:${id}`],
70
- urlQuery: window.location.search,
71
- })
72
- );
73
- }, pageId);
109
+ const snapshot = await opened.page.evaluate(
110
+ ({ id, instanceKey, path }) => {
111
+ const lowdefy = window.lowdefy;
112
+ const pageContext = lowdefy?.contexts?.[instanceKey];
113
+ if (!pageContext) {
114
+ return { error: `No live context for page "${id}" at "/${path}".` };
115
+ }
116
+ return JSON.parse(
117
+ JSON.stringify({
118
+ pageId: id,
119
+ pathParams: pageContext.pathParams,
120
+ instanceKey,
121
+ state: pageContext.state,
122
+ requests: pageContext.requests,
123
+ eventLog: (pageContext.eventLog ?? []).slice(-50),
124
+ global: lowdefy.lowdefyGlobal,
125
+ user: lowdefy.user,
126
+ input: lowdefy.inputs?.[instanceKey],
127
+ urlQuery: window.location.search,
128
+ })
129
+ );
130
+ },
131
+ { id: pageId, instanceKey: instance.instanceKey, path: instance.path }
132
+ );
133
+ if (!opened.ready) {
134
+ return { ...snapshot, ready: false, note: unsettledPageNote({ timeout }) };
135
+ }
74
136
  return snapshot;
75
137
  } catch (error) {
76
138
  return { error: `Failed to inspect state at "${url}": ${error.message}` };
@@ -25,7 +25,10 @@ import { jest } from '@jest/globals';
25
25
  // screenshotPage.test.mjs, which exercises the same getBrowser.js singleton.
26
26
  jest.unstable_mockModule('playwright-core', () => ({
27
27
  chromium: { launch: jest.fn().mockRejectedValue(new Error("Executable doesn't exist")) },
28
+ selectors: { register: jest.fn(async () => {}) },
28
29
  }));
30
+ // A missing shell would otherwise start a real download.
31
+ jest.unstable_mockModule('./installHeadlessShell.js', () => ({ default: () => null }));
29
32
 
30
33
  // lib/build/config.js reads build/config.json from process.cwd() at import
31
34
  // time — chdir into a fixture that has one before getBrowser.js (which
@@ -35,6 +38,10 @@ const originalCwd = process.cwd();
35
38
  const fixtureDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-inspect-state-test-'));
36
39
  fs.mkdirSync(path.join(fixtureDir, 'build'), { recursive: true });
37
40
  fs.writeFileSync(path.join(fixtureDir, 'build', 'config.json'), JSON.stringify({ basePath: '' }));
41
+ fs.writeFileSync(
42
+ path.join(fixtureDir, 'build', 'routes.json'),
43
+ JSON.stringify([{ pageId: 'ticket', path: 'tickets/{space}/{ticket_id}' }])
44
+ );
38
45
  process.chdir(fixtureDir);
39
46
 
40
47
  const { default: inspectStateHeadless } = await import('./inspectStateHeadless.js');
@@ -56,5 +63,17 @@ test('inspectStateHeadless returns an error when pageId is missing', async () =>
56
63
 
57
64
  test('inspectStateHeadless returns an actionable error when no browser is available', async () => {
58
65
  const result = await inspectStateHeadless({ origin: 'http://localhost:3001', pageId: 'home' });
59
- expect(result.error).toMatch(/No Chromium available. Run: npx playwright install chromium/);
66
+ expect(result.error).toMatch(
67
+ /No Chromium available. Run: npx playwright install chromium-headless-shell/
68
+ );
69
+ });
70
+
71
+ test('inspectStateHeadless returns the error naming a missing placeholder before opening a browser', async () => {
72
+ const result = await inspectStateHeadless({
73
+ origin: 'http://localhost:3001',
74
+ pageId: 'ticket',
75
+ pathParams: { space: 's' },
76
+ });
77
+ expect(result.invalidInput).toBe(true);
78
+ expect(result.error).toMatch(/missing a value for path placeholder "ticket_id"/);
60
79
  });
@@ -0,0 +1,45 @@
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 { EventEmitter } from 'node:events';
18
+
19
+ import { jest } from '@jest/globals';
20
+
21
+ const mockSpawn = jest.fn();
22
+ jest.unstable_mockModule('child_process', () => ({ spawn: mockSpawn, spawnSync: jest.fn() }));
23
+
24
+ // A file of its own: installHeadlessShell keeps its one install per process
25
+ // in module state.
26
+ const { default: installHeadlessShell } = await import('./installHeadlessShell.js');
27
+
28
+ test('installHeadlessShell logs a failed install once and does not retry it in this process', async () => {
29
+ const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
30
+ jest.spyOn(console, 'info').mockImplementation(() => {});
31
+ const installer = Object.assign(new EventEmitter(), { stderr: new EventEmitter() });
32
+ mockSpawn.mockReturnValue(installer);
33
+
34
+ const install = installHeadlessShell();
35
+ installer.stderr.emit('data', Buffer.from('Download failed: server returned code 503'));
36
+ installer.emit('exit', 1);
37
+ // An error event after exit (or before it) must not log twice.
38
+ installer.emit('error', new Error('spawn EPIPE'));
39
+
40
+ expect(await install).toMatchObject({ installed: false });
41
+ expect(installHeadlessShell()).toBe(install);
42
+ expect(mockSpawn).toHaveBeenCalledTimes(1);
43
+ expect(warn).toHaveBeenCalledTimes(1);
44
+ expect(warn.mock.calls[0][0]).toContain('Download failed: server returned code 503');
45
+ });
@@ -0,0 +1,123 @@
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 { spawn, spawnSync } from 'child_process';
18
+ import { createRequire } from 'module';
19
+ import path from 'path';
20
+
21
+ import { type } from '@lowdefy/helpers';
22
+
23
+ // Read the way Playwright's own installer reads it, so a value that turns
24
+ // Playwright's download off turns this one off too.
25
+ function isDownloadSkipped() {
26
+ const value = process.env.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD;
27
+ if (type.isNone(value) || value === '' || value === 'false' || value === '0') {
28
+ return false;
29
+ }
30
+ return true;
31
+ }
32
+
33
+ // The download is about 100 MB. Playwright 1.59's installer can stall for
34
+ // good while unzipping on Node 26, so an install that has not finished by
35
+ // then is treated as failed.
36
+ const INSTALL_TIMEOUT_MS = 3 * 60 * 1000;
37
+
38
+ // The installer forks a download process of its own, which holds the zip
39
+ // open while it hangs, so its children go too. Only processes this server
40
+ // started are signalled.
41
+ function killInstaller(installer) {
42
+ if (process.platform === 'win32') {
43
+ spawnSync('taskkill', ['/pid', String(installer.pid), '/T', '/F'], { stdio: 'ignore' });
44
+ return;
45
+ }
46
+ spawnSync('pkill', ['-KILL', '-P', String(installer.pid)], { stdio: 'ignore' });
47
+ installer.kill('SIGKILL');
48
+ }
49
+
50
+ function runInstall() {
51
+ // cli.js is not in playwright-core's exports, so it is found next to the
52
+ // package.json, which is. Running the installer of the playwright-core this
53
+ // server loads fetches exactly the shell revision it launches.
54
+ const require = createRequire(import.meta.url);
55
+ const cliPath = path.join(
56
+ path.dirname(require.resolve('playwright-core/package.json')),
57
+ 'cli.js'
58
+ );
59
+ console.info('Installing chromium-headless-shell for the dev server browser tools.');
60
+ return new Promise((resolve) => {
61
+ let settled = false;
62
+ let stderr = '';
63
+ let timer = null;
64
+ function settle({ installed, reason }) {
65
+ if (settled) return;
66
+ settled = true;
67
+ clearTimeout(timer);
68
+ if (!installed) {
69
+ console.warn(
70
+ `Could not install chromium-headless-shell (${reason}). The dev server browser tools use system Chrome when it is installed. Run: npx playwright install chromium-headless-shell`
71
+ );
72
+ }
73
+ resolve({ installed, reason });
74
+ }
75
+ // A short-lived process: the download and unzip never touch this
76
+ // server's memory. Playwright's registry lock serialises installs from
77
+ // several dev servers on one machine.
78
+ const installer = spawn(process.execPath, [cliPath, 'install', 'chromium-headless-shell'], {
79
+ stdio: ['ignore', 'ignore', 'pipe'],
80
+ });
81
+ timer = setTimeout(() => {
82
+ killInstaller(installer);
83
+ settle({
84
+ installed: false,
85
+ reason: `the install did not finish within ${
86
+ INSTALL_TIMEOUT_MS / 60000
87
+ } minutes; Playwright's installer can stall while unzipping on Node 26, where installing with Node 22 works`,
88
+ });
89
+ }, INSTALL_TIMEOUT_MS);
90
+ // Never the reason the dev server stays up.
91
+ timer.unref();
92
+ installer.stderr.on('data', (data) => {
93
+ stderr = `${stderr}${data.toString('utf8')}`.slice(-1000);
94
+ });
95
+ installer.on('error', (error) => settle({ installed: false, reason: error.message }));
96
+ installer.on('exit', (code) => {
97
+ if (code === 0) {
98
+ settle({ installed: true });
99
+ return;
100
+ }
101
+ settle({ installed: false, reason: stderr.trim() || `exit code ${code}` });
102
+ });
103
+ });
104
+ }
105
+
106
+ // One install per dev server process, shared by every caller. A failed
107
+ // install is not retried in this process: system Chrome stays the fallback.
108
+ let installPromise = null;
109
+
110
+ // Resolves { installed: true } once the shell is installed, or
111
+ // { installed: false, reason } when the install failed or timed out. Returns
112
+ // null when downloads are turned off.
113
+ function installHeadlessShell() {
114
+ if (isDownloadSkipped()) {
115
+ return null;
116
+ }
117
+ if (type.isNone(installPromise)) {
118
+ installPromise = runInstall();
119
+ }
120
+ return installPromise;
121
+ }
122
+
123
+ export default installHeadlessShell;