@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,187 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import fs from 'node:fs';
18
+ import os from 'node:os';
19
+ import path from 'node:path';
20
+
21
+ import { jest } from '@jest/globals';
22
+
23
+ import JourneyStepError from './JourneyStepError.js';
24
+ import openJourneyEmail from './openJourneyEmail.js';
25
+
26
+ const STARTED_AT = Date.parse('2026-09-27T10:00:00.000Z');
27
+
28
+ let configDirectory;
29
+ let outbox;
30
+
31
+ beforeEach(() => {
32
+ configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-journey-email-'));
33
+ outbox = path.join(configDirectory, '.lowdefy', 'mail');
34
+ fs.mkdirSync(outbox, { recursive: true });
35
+ });
36
+
37
+ afterEach(() => {
38
+ fs.rmSync(configDirectory, { recursive: true, force: true });
39
+ });
40
+
41
+ function writeMessage({ id, to, subject, secondsAfterStart = 1, html, text = null }) {
42
+ const message = {
43
+ id,
44
+ receivedAt: new Date(STARTED_AT + secondsAfterStart * 1000).toISOString(),
45
+ from: 'app@localhost',
46
+ to,
47
+ subject,
48
+ html:
49
+ html === undefined ? `<a href="http://localhost/${id}" target="_blank">Open ${id}</a>` : html,
50
+ text,
51
+ };
52
+ fs.writeFileSync(path.join(outbox, `${id}.json`), JSON.stringify(message));
53
+ }
54
+
55
+ function createPage({ onWait = () => {} } = {}) {
56
+ const page = {
57
+ opened: [],
58
+ goto: jest.fn(async (url) => {
59
+ const base64 = url.slice('data:text/html;charset=utf-8;base64,'.length);
60
+ page.opened.push(Buffer.from(base64, 'base64').toString());
61
+ }),
62
+ evaluate: jest.fn(async () => {}),
63
+ waitForTimeout: jest.fn(async () => onWait()),
64
+ };
65
+ return page;
66
+ }
67
+
68
+ test.each([
69
+ ['the newest message to the address', { to: 'ada@example.test' }, '000003'],
70
+ ['an address in any case', { to: 'ADA@Example.Test' }, '000003'],
71
+ [
72
+ 'the newest subject containing the text',
73
+ { to: 'ada@example.test', subject: 'Verify' },
74
+ '000001',
75
+ ],
76
+ ['a later recipient of a message sent to several', { to: 'bob@example.test' }, '000002'],
77
+ ])('openJourneyEmail opens %s', async (_, params, expectedId) => {
78
+ writeMessage({
79
+ id: '000000',
80
+ to: ['ada@example.test'],
81
+ subject: 'Verify',
82
+ secondsAfterStart: -5,
83
+ });
84
+ writeMessage({ id: '000001', to: ['ada@example.test'], subject: 'Verify your email' });
85
+ writeMessage({
86
+ id: '000002',
87
+ to: ['ada@example.test', 'bob@example.test'],
88
+ subject: 'Your sign-in link',
89
+ });
90
+ writeMessage({ id: '000003', to: ['ada@example.test'], subject: 'Invited' });
91
+ const page = createPage();
92
+
93
+ await openJourneyEmail({ page, params, since: STARTED_AT, configDirectory, timeout: 1000 });
94
+
95
+ expect(page.opened).toEqual([
96
+ `<a href="http://localhost/${expectedId}" target="_blank">Open ${expectedId}</a>`,
97
+ ]);
98
+ // Links open where the email is, since each actor drives one tab.
99
+ const link = { removeAttribute: jest.fn() };
100
+ global.document = { querySelectorAll: jest.fn(() => [link]) };
101
+ await page.evaluate.mock.calls[0][0]();
102
+ delete global.document;
103
+ expect(link.removeAttribute).toHaveBeenCalledWith('target');
104
+ });
105
+
106
+ test('openJourneyEmail waits for a message that arrives after the step starts', async () => {
107
+ const page = createPage({
108
+ onWait: () => writeMessage({ id: '000001', to: ['ada@example.test'], subject: 'Verify' }),
109
+ });
110
+
111
+ await openJourneyEmail({
112
+ page,
113
+ params: { to: 'ada@example.test' },
114
+ since: STARTED_AT,
115
+ configDirectory,
116
+ timeout: 5000,
117
+ });
118
+
119
+ expect(page.waitForTimeout).toHaveBeenCalledTimes(1);
120
+ expect(page.opened).toHaveLength(1);
121
+ });
122
+
123
+ test('openJourneyEmail shows a text-only message escaped in a pre block', async () => {
124
+ writeMessage({
125
+ id: '000001',
126
+ to: ['ada@example.test'],
127
+ subject: 'Code',
128
+ html: null,
129
+ text: 'Your code is <1234> & "5"',
130
+ });
131
+ const page = createPage();
132
+
133
+ await openJourneyEmail({
134
+ page,
135
+ params: { to: 'ada@example.test' },
136
+ since: STARTED_AT,
137
+ configDirectory,
138
+ timeout: 1000,
139
+ });
140
+
141
+ expect(page.opened).toEqual(['<pre>Your code is &#60;1234&#62; &#38; &#34;5&#34;</pre>']);
142
+ });
143
+
144
+ const OLD_MESSAGE = {
145
+ id: '000000',
146
+ to: ['ada@example.test'],
147
+ subject: 'Old',
148
+ secondsAfterStart: -5,
149
+ };
150
+ const OTHER_MESSAGE = { id: '000001', to: ['bob@example.test'], subject: 'Verify your email' };
151
+
152
+ test.each([
153
+ [
154
+ 'names the messages this journey received',
155
+ [OLD_MESSAGE, OTHER_MESSAGE],
156
+ { to: 'ada@example.test', subject: 'Verify' },
157
+ 'an email to "ada@example.test" with a subject containing "Verify"',
158
+ ['"Verify your email" to bob@example.test'],
159
+ ],
160
+ [
161
+ 'says so when the journey received no mail at all',
162
+ [OLD_MESSAGE],
163
+ { to: 'ada@example.test' },
164
+ 'an email to "ada@example.test"',
165
+ 'no email received during this journey',
166
+ ],
167
+ ])(
168
+ 'openJourneyEmail fails when none matches and %s',
169
+ async (_, messages, params, expected, actual) => {
170
+ messages.forEach(writeMessage);
171
+ const page = createPage();
172
+
173
+ const error = await openJourneyEmail({
174
+ page,
175
+ params,
176
+ since: STARTED_AT,
177
+ configDirectory,
178
+ timeout: 0,
179
+ }).catch((caught) => caught);
180
+
181
+ expect(error).toBeInstanceOf(JourneyStepError);
182
+ expect(error.message).toEqual(`Timed out after 0ms waiting for ${expected}.`);
183
+ expect(error.expected).toEqual(expected);
184
+ expect(error.actual).toEqual(actual);
185
+ expect(page.opened).toEqual([]);
186
+ }
187
+ );
@@ -0,0 +1,47 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { type } from '@lowdefy/helpers';
18
+
19
+ // The actions of every event of a block, flattened: a built event is
20
+ // { try: [actions], catch: [actions] }.
21
+ function eventActions(block) {
22
+ return Object.values(block.events ?? {}).flatMap((event) => {
23
+ if (type.isArray(event)) return event;
24
+ return [...(event?.try ?? []), ...(event?.catch ?? [])];
25
+ });
26
+ }
27
+
28
+ // Every block of a built page below its root, with the actions its events
29
+ // run. The page root's own events (onInit, onMount) ran when the page opened;
30
+ // no candidate triggers them.
31
+ function pageBlocks(page) {
32
+ const blocks = [];
33
+ function visit(block) {
34
+ blocks.push({ blockId: block.blockId, actions: eventActions(block) });
35
+ childBlocks(block).forEach(visit);
36
+ }
37
+ childBlocks(page).forEach(visit);
38
+ return blocks;
39
+ }
40
+
41
+ function childBlocks(block) {
42
+ return ['slots', 'areas'].flatMap((key) =>
43
+ Object.values(block[key] ?? {}).flatMap((container) => container?.blocks ?? [])
44
+ );
45
+ }
46
+
47
+ export default pageBlocks;
@@ -0,0 +1,103 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { jest } from '@jest/globals';
18
+
19
+ const mockReviewPageBuilds = jest.fn();
20
+ jest.unstable_mockModule('./reviewPageBuilds.js', () => ({
21
+ default: mockReviewPageBuilds,
22
+ }));
23
+ const mockBuildPageIfNeeded = jest.fn();
24
+ jest.unstable_mockModule('../server/jitPageBuilder.js', () => ({
25
+ default: mockBuildPageIfNeeded,
26
+ }));
27
+
28
+ const { default: buildEditedPages, CONCURRENT_BUILDS } = await import('./buildEditedPages.js');
29
+ const { default: getPageBuildStatus } = await import('./getPageBuildStatus.js');
30
+
31
+ beforeEach(() => {
32
+ jest.clearAllMocks();
33
+ });
34
+
35
+ test('buildEditedPages builds every edited page and returns their ids', async () => {
36
+ mockReviewPageBuilds.mockResolvedValue({ edited: ['a', 'b'], unbuilt: ['c'], failed: [] });
37
+ mockBuildPageIfNeeded.mockRejectedValueOnce(new Error('Page "a" build failed.'));
38
+ mockBuildPageIfNeeded.mockResolvedValueOnce(true);
39
+
40
+ const checked = await buildEditedPages();
41
+
42
+ expect(checked).toEqual(['a', 'b']);
43
+ expect(mockBuildPageIfNeeded.mock.calls.map(([args]) => args.pageId)).toEqual(['a', 'b']);
44
+ });
45
+
46
+ test('buildEditedPages builds at most a few pages at once', async () => {
47
+ const pageIds = Array.from({ length: 10 }, (_, index) => `page-${index}`);
48
+ mockReviewPageBuilds.mockResolvedValue({ edited: pageIds, unbuilt: [], failed: [] });
49
+ let running = 0;
50
+ let mostRunning = 0;
51
+ mockBuildPageIfNeeded.mockImplementation(async () => {
52
+ running += 1;
53
+ mostRunning = Math.max(mostRunning, running);
54
+ await new Promise((resolve) => setTimeout(resolve, 5));
55
+ running -= 1;
56
+ return true;
57
+ });
58
+
59
+ const checked = await buildEditedPages();
60
+
61
+ expect(checked).toEqual(pageIds);
62
+ expect(mockBuildPageIfNeeded).toHaveBeenCalledTimes(10);
63
+ expect(mostRunning).toBe(CONCURRENT_BUILDS);
64
+ });
65
+
66
+ test('buildEditedPages builds nothing when no page was edited', async () => {
67
+ mockReviewPageBuilds.mockResolvedValue({ edited: [], unbuilt: ['a'], failed: [] });
68
+
69
+ expect(await buildEditedPages()).toEqual([]);
70
+ expect(mockBuildPageIfNeeded).not.toHaveBeenCalled();
71
+ });
72
+
73
+ test('getPageBuildStatus reports failed and unbuilt pages', async () => {
74
+ const failed = [
75
+ { pageId: 'a', errors: [{ type: 'ConfigError', message: 'Bad.', source: null }] },
76
+ ];
77
+ mockReviewPageBuilds.mockResolvedValue({ edited: [], unbuilt: ['c', 'd'], failed });
78
+
79
+ expect(await getPageBuildStatus()).toEqual({
80
+ failed,
81
+ unbuilt: 2,
82
+ unbuiltNote:
83
+ '2 page(s) have not been built since the dev server started, so this status does not cover them. lowdefy_check validates every page.',
84
+ });
85
+ });
86
+
87
+ test('getPageBuildStatus leaves out empty lists and notes', async () => {
88
+ mockReviewPageBuilds.mockResolvedValue({ edited: [], unbuilt: [], failed: [] });
89
+
90
+ expect(await getPageBuildStatus({ checked: [] })).toEqual({ checked: [], unbuilt: 0 });
91
+ });
92
+
93
+ test('getPageBuildStatus says an edited page is not rebuilt yet, or not seen after a wait', async () => {
94
+ mockReviewPageBuilds.mockResolvedValue({ edited: ['a'], unbuilt: [], failed: [] });
95
+
96
+ const before = await getPageBuildStatus();
97
+ expect(before.changedSinceBuild).toEqual(['a']);
98
+ expect(before.changedSinceBuildNote).toContain('Call with wait: true');
99
+
100
+ const after = await getPageBuildStatus({ checked: ['a'] });
101
+ expect(after.checked).toEqual(['a']);
102
+ expect(after.changedSinceBuildNote).toContain('has not rebuilt them');
103
+ });
@@ -14,19 +14,18 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- // location comes from the custom router (@lowdefy/client/adapters):
18
- // { pageId, pathname, search }. pageId is null at the root path.
19
- function setPageId(location, rootConfig) {
20
- if (location.pageId === '404') {
21
- return { redirect: false, pageId: '404' };
17
+ // A module's source names its package and version: "github:owner/repo/path@ref"
18
+ // is package "github:owner/repo/path" at version "ref"; a local "file:" source
19
+ // is version "local".
20
+ function parseModuleSource({ source }) {
21
+ if (source.startsWith('file:')) {
22
+ return { package: source, version: 'local' };
22
23
  }
23
- if (!location.pageId) {
24
- if (rootConfig.home.configured === false) {
25
- return { redirect: true, pageId: rootConfig.home.pageId };
26
- }
27
- return { redirect: false, pageId: rootConfig.home.pageId };
24
+ const at = source.lastIndexOf('@');
25
+ if (at <= 0) {
26
+ return { package: source, version: null };
28
27
  }
29
- return { redirect: false, pageId: location.pageId };
28
+ return { package: source.slice(0, at), version: source.slice(at + 1) };
30
29
  }
31
30
 
32
- export default setPageId;
31
+ export default parseModuleSource;
@@ -0,0 +1,53 @@
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 actions-core action types that call the auth engine. A data-set journey
18
+ // acts as an injected caller, which has no auth engine session, so these fail
19
+ // every time for a harness reason, not the app's: a click that runs one fails
20
+ // its step (see checkAuthActionsRan). A new actions-core action must be
21
+ // classified here or in the test's list of the rest.
22
+ const authEngineActions = new Set([
23
+ 'AcceptInvitation',
24
+ 'ChangePassword',
25
+ 'EmailOtpSend',
26
+ 'EmailOtpVerify',
27
+ 'LeaveOrganization',
28
+ 'ListOrganizations',
29
+ 'Login',
30
+ 'Logout',
31
+ 'MagicLinkVerify',
32
+ 'OAuthConsent',
33
+ 'OAuthContinue',
34
+ 'PasskeyDelete',
35
+ 'PasskeyRegister',
36
+ 'PasskeySignIn',
37
+ 'PasskeyUpdate',
38
+ 'PhoneNumberSendOtp',
39
+ 'PhoneNumberVerify',
40
+ 'RequestPasswordReset',
41
+ 'ResetPassword',
42
+ 'RevokeOtherSessions',
43
+ 'SendVerificationEmail',
44
+ 'SetActiveOrganization',
45
+ 'SignUp',
46
+ 'TwoFactorDisable',
47
+ 'TwoFactorEnable',
48
+ 'TwoFactorGenerateBackupCodes',
49
+ 'TwoFactorVerify',
50
+ 'UpdateSession',
51
+ ]);
52
+
53
+ export default authEngineActions;
@@ -0,0 +1,69 @@
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 actionsCoreTypes from '@lowdefy/actions-core/types';
18
+
19
+ import authEngineActions from './authEngineActions.js';
20
+
21
+ // Every actions-core action that does not call the auth engine. A new action
22
+ // type fails the test below until it is added here or to authEngineActions.
23
+ const NOT_AUTH_ENGINE = [
24
+ 'CallAPI',
25
+ 'CallMethod',
26
+ 'CopyToClipboard',
27
+ 'DisplayMessage',
28
+ 'Fetch',
29
+ 'GeolocationCurrentPosition',
30
+ 'GetLocalStorage',
31
+ 'Link',
32
+ 'Publish',
33
+ 'RemoveLocalStorage',
34
+ 'Request',
35
+ 'Reset',
36
+ 'ResetValidation',
37
+ 'ScrollTo',
38
+ 'SetDarkMode',
39
+ 'SetFocus',
40
+ 'SetGlobal',
41
+ 'SetLocale',
42
+ 'SetLocalStorage',
43
+ 'SetState',
44
+ 'Subscribe',
45
+ 'Throw',
46
+ 'Unsubscribe',
47
+ 'Validate',
48
+ 'Wait',
49
+ ];
50
+
51
+ test('every actions-core action type is classified as calling the auth engine or not', () => {
52
+ const unclassified = actionsCoreTypes.actions.filter(
53
+ (actionType) => !authEngineActions.has(actionType) && !NOT_AUTH_ENGINE.includes(actionType)
54
+ );
55
+ expect(unclassified).toEqual([]);
56
+ });
57
+
58
+ test('authEngineActions and the list of the rest name only actions-core action types, once', () => {
59
+ const known = new Set(actionsCoreTypes.actions);
60
+ expect([...authEngineActions].filter((actionType) => !known.has(actionType))).toEqual([]);
61
+ expect(NOT_AUTH_ENGINE.filter((actionType) => !known.has(actionType))).toEqual([]);
62
+ expect(NOT_AUTH_ENGINE.filter((actionType) => authEngineActions.has(actionType))).toEqual([]);
63
+ });
64
+
65
+ test('authEngineActions holds the sign-in, sign-up and session actions', () => {
66
+ ['Login', 'Logout', 'SignUp', 'TwoFactorVerify', 'PasskeySignIn', 'EmailOtpSend'].forEach(
67
+ (actionType) => expect(authEngineActions.has(actionType)).toBe(true)
68
+ );
69
+ });
@@ -0,0 +1,52 @@
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 client auth method (lowdefy._internal.auth[method], the one the engine's
18
+ // action methods call) behind each auth-engine action type, so a data-set run
19
+ // can say which action ran when it sees a method called. The action types are
20
+ // authEngineActions; authEngineMethods.test.mjs keeps the two in step.
21
+ const authEngineMethods = {
22
+ acceptInvitation: 'AcceptInvitation',
23
+ changePassword: 'ChangePassword',
24
+ emailOtpSend: 'EmailOtpSend',
25
+ emailOtpVerify: 'EmailOtpVerify',
26
+ leaveOrganization: 'LeaveOrganization',
27
+ listOrganizations: 'ListOrganizations',
28
+ login: 'Login',
29
+ logout: 'Logout',
30
+ magicLinkVerify: 'MagicLinkVerify',
31
+ oauth2Consent: 'OAuthConsent',
32
+ oauth2Continue: 'OAuthContinue',
33
+ passkeyDelete: 'PasskeyDelete',
34
+ passkeyRegister: 'PasskeyRegister',
35
+ passkeySignIn: 'PasskeySignIn',
36
+ passkeyUpdate: 'PasskeyUpdate',
37
+ phoneNumberSendOtp: 'PhoneNumberSendOtp',
38
+ phoneNumberVerify: 'PhoneNumberVerify',
39
+ requestPasswordReset: 'RequestPasswordReset',
40
+ resetPassword: 'ResetPassword',
41
+ revokeOtherSessions: 'RevokeOtherSessions',
42
+ sendVerificationEmail: 'SendVerificationEmail',
43
+ setActiveOrganization: 'SetActiveOrganization',
44
+ signUp: 'SignUp',
45
+ twoFactorDisable: 'TwoFactorDisable',
46
+ twoFactorEnable: 'TwoFactorEnable',
47
+ twoFactorGenerateBackupCodes: 'TwoFactorGenerateBackupCodes',
48
+ twoFactorVerify: 'TwoFactorVerify',
49
+ updateSession: 'UpdateSession',
50
+ };
51
+
52
+ export default authEngineMethods;
@@ -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 fs from 'node:fs';
18
+
19
+ import authEngineActions from './authEngineActions.js';
20
+ import authEngineMethods from './authEngineMethods.js';
21
+
22
+ test('authEngineMethods maps client auth methods to exactly the authEngineActions types', () => {
23
+ expect(new Set(Object.values(authEngineMethods))).toEqual(authEngineActions);
24
+ expect(Object.values(authEngineMethods)).toHaveLength(authEngineActions.size);
25
+ });
26
+
27
+ test('every method of authEngineMethods is a method the client creates', () => {
28
+ const source = fs.readFileSync(
29
+ new URL('../../../../../client/src/auth/createAuthMethods.js', import.meta.url),
30
+ 'utf8'
31
+ );
32
+ const exported = source.slice(source.lastIndexOf(' return {'));
33
+ Object.keys(authEngineMethods).forEach((method) => {
34
+ expect(exported).toMatch(new RegExp(`^ ${method},?$`, 'm'));
35
+ });
36
+ });
@@ -0,0 +1,66 @@
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 JourneyStepError from '../JourneyStepError.js';
20
+ import authEngineMethods from './authEngineMethods.js';
21
+ import installAuthActionRecorder from './installAuthActionRecorder.js';
22
+ import readAuthActionRecorder from './readAuthActionRecorder.js';
23
+
24
+ const RECORDER_KEY = '__lowdefyAuthActions';
25
+
26
+ // On a data-set run (journey.dataSetRails), starts recording the auth-engine
27
+ // actions the page executes, before a click acts. Journeys without a data set
28
+ // record nothing.
29
+ async function armAuthActionRecorder({ journey, page }) {
30
+ if (type.isUndefined(journey.dataSetRails)) {
31
+ return;
32
+ }
33
+ await page.evaluate(installAuthActionRecorder, {
34
+ key: RECORDER_KEY,
35
+ methods: Object.keys(authEngineMethods),
36
+ });
37
+ }
38
+
39
+ // After a click on a data-set run has settled, fails the step when an
40
+ // auth-engine action executed: its callers are injected and have no auth
41
+ // session, so it fails for a harness reason. An action the chain skipped (a
42
+ // validation that stopped it, a skip: condition) never runs, so a click that
43
+ // only might reach one is fine. blockId is the clicked block, when known.
44
+ async function checkAuthActionsRan({ journey, page, blockId }) {
45
+ if (type.isUndefined(journey.dataSetRails)) {
46
+ return;
47
+ }
48
+ const called = await page.evaluate(readAuthActionRecorder, { key: RECORDER_KEY }).catch(() => []);
49
+ const actions = [...new Set(called.map((method) => authEngineMethods[method] ?? method))];
50
+ if (actions.length === 0) {
51
+ return;
52
+ }
53
+ const subject = type.isNone(blockId) ? 'A click' : `Block "${blockId}"`;
54
+ throw new JourneyStepError(
55
+ `${subject} ran the auth action ${actions.join(
56
+ ', '
57
+ )}, which a data-set journey cannot run: its users are injected and have no auth session. A click that skips the action (a failed validation, a skip condition) is fine.`,
58
+ {
59
+ expected: 'a click that runs no auth action',
60
+ actual: `a click that ran the auth action ${actions.join(', ')}`,
61
+ }
62
+ );
63
+ }
64
+
65
+ export { armAuthActionRecorder };
66
+ export default checkAuthActionsRan;