@lowdefy/server-dev 6.1.0 → 7.1.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 (624) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +2 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +222 -36
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +78 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +727 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/describeTargetKey.js +29 -0
  104. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  105. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  106. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  107. package/lib/docs/dataSets/duplicateIndexName.js +24 -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 +52 -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 +133 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +493 -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 +83 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +251 -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/sessionDatabaseName.js +32 -0
  133. package/lib/docs/dataSets/sweepOrphanDatabases.js +36 -0
  134. package/lib/docs/dataSets/test/app/lowdefy.yaml +225 -0
  135. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  136. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +12 -0
  137. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  138. package/lib/docs/devEventBus.js +136 -0
  139. package/lib/docs/devEventBus.test.mjs +269 -0
  140. package/lib/docs/devToolDefinitions.js +529 -0
  141. package/lib/docs/docSources.js +21 -0
  142. package/lib/docs/docs.test.mjs +171 -15
  143. package/lib/docs/docsIndex.test.mjs +377 -0
  144. package/lib/docs/evalOperator.js +5 -3
  145. package/lib/docs/evalOperatorHeadless.js +84 -12
  146. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  147. package/lib/docs/evalOperatorInTab.js +2 -1
  148. package/lib/docs/findConfig.js +95 -49
  149. package/lib/docs/fitResponse.js +81 -0
  150. package/lib/docs/fitResponse.test.mjs +128 -0
  151. package/lib/docs/formatFeedback.js +26 -5
  152. package/lib/docs/formatFeedback.test.mjs +48 -0
  153. package/lib/docs/getAppMap.js +7 -1
  154. package/lib/docs/getBrowser.js +150 -80
  155. package/lib/docs/getBrowser.test.mjs +407 -16
  156. package/lib/docs/getBuildId.js +61 -0
  157. package/lib/docs/getBuildId.test.mjs +84 -0
  158. package/lib/docs/getBuildStatus.js +81 -7
  159. package/lib/docs/getBuildStatus.test.mjs +229 -0
  160. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  161. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  162. package/lib/docs/getCoreDoc.js +9 -1
  163. package/lib/docs/getDoc.js +100 -0
  164. package/lib/docs/getDocsIndex.js +152 -0
  165. package/lib/docs/getHazards.js +115 -0
  166. package/lib/docs/getHazards.test.mjs +175 -0
  167. package/lib/docs/getMailOutboxDirectory.js +26 -0
  168. package/lib/docs/getOverview.js +79 -13
  169. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  170. package/lib/docs/getPageBuildStatus.js +46 -0
  171. package/lib/docs/getPluginDoc.js +21 -23
  172. package/lib/docs/getSchema.js +33 -1
  173. package/lib/docs/getStaleStatus.js +36 -0
  174. package/lib/docs/getStaleStatus.test.mjs +74 -0
  175. package/lib/docs/guardJourneyOrigin.js +39 -0
  176. package/lib/docs/inspectState.js +5 -3
  177. package/lib/docs/inspectStateFromTab.js +2 -2
  178. package/lib/docs/inspectStateHeadless.js +89 -27
  179. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  180. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  181. package/lib/docs/installHeadlessShell.js +123 -0
  182. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  183. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  184. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  185. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  186. package/lib/docs/isPageReady.js +77 -0
  187. package/lib/docs/isPageReady.test.mjs +235 -0
  188. package/lib/docs/isRunErrorEntry.js +25 -0
  189. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  190. package/lib/docs/journeyRunRecording.js +35 -0
  191. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  192. package/lib/docs/keepEventStreamAlive.js +68 -0
  193. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  194. package/lib/docs/launchBrowser.js +83 -0
  195. package/lib/docs/launchBrowser.test.mjs +175 -0
  196. package/lib/docs/listModuleDocEntries.js +69 -0
  197. package/lib/docs/listPackageDocFiles.js +67 -0
  198. package/lib/docs/listPluginDocEntries.js +111 -0
  199. package/lib/docs/listPlugins.js +9 -58
  200. package/lib/docs/listTypes.js +14 -6
  201. package/lib/docs/loadFrameworkHazards.js +40 -0
  202. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  203. package/lib/docs/loadState.js +103 -35
  204. package/lib/docs/makeModuleManifestDoc.js +174 -0
  205. package/lib/docs/matchExercisedPages.js +35 -0
  206. package/lib/docs/matchJourneyRoute.js +69 -0
  207. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  208. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  209. package/lib/docs/nextJourneySequence.js +30 -0
  210. package/lib/docs/noBrowserError.js +22 -0
  211. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  212. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  213. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  214. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  215. package/lib/docs/observe/evaluateInvariants.js +183 -0
  216. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  217. package/lib/docs/observe/findingKey.js +29 -0
  218. package/lib/docs/observe/hasNoEffect.js +31 -0
  219. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  220. package/lib/docs/observe/installStepObserver.js +72 -0
  221. package/lib/docs/observe/isAppApiUrl.js +33 -0
  222. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  223. package/lib/docs/observe/readStepObserver.js +42 -0
  224. package/lib/docs/observe/relativeSource.js +29 -0
  225. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  226. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  227. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  228. package/lib/docs/observe/usesSearchStage.js +45 -0
  229. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  230. package/lib/docs/observe/watchJourneyContext.js +75 -0
  231. package/lib/docs/observe/withAppErrors.js +31 -0
  232. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  233. package/lib/docs/openJourney.js +94 -0
  234. package/lib/docs/openJourneyEmail.js +41 -0
  235. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  236. package/lib/docs/pageBlocks.js +47 -0
  237. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  238. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  239. package/lib/docs/rails/authEngineActions.js +53 -0
  240. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  241. package/lib/docs/rails/authEngineMethods.js +52 -0
  242. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  243. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  244. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  245. package/lib/docs/rails/checkClickRails.js +56 -0
  246. package/lib/docs/rails/createDataSetRails.js +82 -0
  247. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  248. package/lib/docs/rails/findExternalBlocks.js +147 -0
  249. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  250. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  251. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  252. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  253. package/lib/docs/readConnectionPlugins.js +26 -0
  254. package/lib/docs/readDevAuthMode.js +32 -0
  255. package/lib/docs/readDocEntry.js +40 -0
  256. package/lib/docs/readDocTitle.js +26 -0
  257. package/lib/docs/readJourneyEmailMatch.js +57 -0
  258. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  259. package/lib/docs/readJourneySession.js +56 -0
  260. package/lib/docs/readMailOutbox.js +37 -0
  261. package/lib/docs/readPagePath.js +27 -0
  262. package/lib/docs/readPluginPackages.js +99 -0
  263. package/lib/docs/readProxyBuildWait.js +38 -0
  264. package/lib/docs/readRoutineSteps.js +45 -0
  265. package/lib/docs/registerShownTextEngine.js +118 -0
  266. package/lib/docs/requestRestart.js +30 -0
  267. package/lib/docs/requestRestart.test.mjs +49 -0
  268. package/lib/docs/resolvePageInstance.js +46 -0
  269. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  270. package/lib/docs/resolveSource.js +11 -7
  271. package/lib/docs/resolveSource.test.mjs +1 -1
  272. package/lib/docs/resolveToolCaller.js +62 -0
  273. package/lib/docs/reviewPage.js +51 -0
  274. package/lib/docs/reviewPageBuilds.js +56 -0
  275. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  276. package/lib/docs/runCheck.js +75 -0
  277. package/lib/docs/runCheck.test.mjs +91 -0
  278. package/lib/docs/runCheckChild.js +49 -0
  279. package/lib/docs/runEndpoint.js +185 -0
  280. package/lib/docs/runEndpoint.test.mjs +509 -0
  281. package/lib/docs/runErrorBuffers.js +99 -0
  282. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  283. package/lib/docs/runJourney.js +347 -0
  284. package/lib/docs/runJourney.test.mjs +2383 -0
  285. package/lib/docs/runJourneyAsUsers.js +44 -0
  286. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  287. package/lib/docs/runJourneySteps.js +1320 -0
  288. package/lib/docs/runRequest.js +100 -59
  289. package/lib/docs/runRequest.test.mjs +190 -0
  290. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  291. package/lib/docs/scaffoldPage.js +3 -1
  292. package/lib/docs/screenshotPage.js +163 -11
  293. package/lib/docs/screenshotPage.test.mjs +263 -1
  294. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  295. package/lib/docs/searchDocs.js +82 -30
  296. package/lib/docs/searchDocs.test.mjs +100 -0
  297. package/lib/docs/searchIcons.js +90 -0
  298. package/lib/docs/searchIcons.test.mjs +127 -0
  299. package/lib/docs/selectExercised.js +39 -0
  300. package/lib/docs/selectFinalState.js +66 -0
  301. package/lib/docs/selectFinalState.test.mjs +51 -0
  302. package/lib/docs/serverErrorStore.js +54 -0
  303. package/lib/docs/serverErrorStore.test.mjs +50 -0
  304. package/lib/docs/setupTestFixtures.mjs +133 -39
  305. package/lib/docs/shownTextSelector.js +28 -0
  306. package/lib/docs/snapshotState.js +13 -5
  307. package/lib/docs/splitSearchTerms.js +62 -0
  308. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  309. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  310. package/lib/docs/tabAvailable.js +6 -5
  311. package/lib/docs/tabChannel.js +127 -21
  312. package/lib/docs/tabChannel.test.mjs +187 -29
  313. package/lib/docs/unsettledPageNote.js +24 -0
  314. package/lib/docs/validateJourneyMail.js +36 -0
  315. package/lib/docs/validateJourneyTimeout.js +38 -0
  316. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  317. package/lib/docs/validateRecordingParam.js +42 -0
  318. package/lib/docs/validateStateSelection.js +33 -0
  319. package/lib/docs/validateStateSelection.test.mjs +33 -0
  320. package/lib/docs/validateViewport.js +51 -0
  321. package/lib/docs/validateViewport.test.mjs +54 -0
  322. package/lib/docs/waitForBuild.js +50 -0
  323. package/lib/docs/waitForBuild.test.mjs +75 -0
  324. package/lib/docs/waitForJourneyEmail.js +77 -0
  325. package/lib/docs/withBrowserSlot.js +45 -0
  326. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  327. package/lib/docs/withDataSession.js +53 -0
  328. package/lib/server/applyDataSetRedirect.js +92 -0
  329. package/lib/server/applyDataSetRedirect.test.mjs +446 -0
  330. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +207 -0
  331. package/lib/server/applyDataSetRedirectWalled.test.mjs +134 -0
  332. package/lib/server/auth/createSystemContext.js +73 -0
  333. package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
  334. package/lib/server/auth/getHeadlessUser.js +9 -8
  335. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  336. package/lib/server/auth/getMockUser.js +18 -14
  337. package/lib/server/auth/{session.js → getStrategies.js} +17 -16
  338. package/lib/server/auth/headlessUser.js +3 -2
  339. package/lib/server/auth/journeyActor.js +35 -0
  340. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  341. package/lib/server/checkPageRecord.js +44 -0
  342. package/lib/server/contextMapBudget.js +24 -0
  343. package/lib/server/contextMapBudget.test.mjs +98 -0
  344. package/lib/server/continueJitKeys.js +28 -0
  345. package/lib/server/continueJitKeys.test.mjs +43 -0
  346. package/lib/server/createDataSessionEndedError.js +33 -0
  347. package/lib/server/createDataSetRefusal.js +26 -0
  348. package/lib/server/createLowdefyContext.js +168 -41
  349. package/lib/server/createLowdefyContext.test.mjs +331 -8
  350. package/lib/server/getClientAddress.js +51 -0
  351. package/lib/server/getClientAddress.test.mjs +56 -0
  352. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  353. package/lib/server/hashConfigContent.js +26 -0
  354. package/lib/server/importAppCode.test.mjs +57 -0
  355. package/lib/server/isRoutineStep.js +27 -0
  356. package/lib/server/jitPageBuilder.js +309 -126
  357. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  358. package/lib/server/jitPageBuilder.test.mjs +218 -0
  359. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  360. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  361. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  362. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  363. package/lib/server/journeyCookies.js +22 -0
  364. package/lib/server/journeyCookies.test.mjs +145 -0
  365. package/lib/server/loadDynamicJsMap.js +47 -0
  366. package/lib/server/log/createHandleError.js +38 -2
  367. package/lib/server/log/createHandleError.test.mjs +125 -2
  368. package/lib/server/log/createLogger.js +3 -0
  369. package/lib/server/log/createLogger.test.mjs +15 -0
  370. package/lib/server/log/logRequest.js +2 -3
  371. package/lib/server/mutantDataSetWrappers.test.mjs +115 -0
  372. package/lib/server/mutants/applyMutant.js +39 -0
  373. package/lib/server/mutants/artifactPatterns.js +25 -0
  374. package/lib/server/mutants/buildAnchor.js +45 -0
  375. package/lib/server/mutants/collectSharedIds.js +41 -0
  376. package/lib/server/mutants/configPathSegments.js +38 -0
  377. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  378. package/lib/server/mutants/describeEvent.js +22 -0
  379. package/lib/server/mutants/enumerateArtifact.js +56 -0
  380. package/lib/server/mutants/findNodeByKey.js +45 -0
  381. package/lib/server/mutants/findNodeByPointer.js +49 -0
  382. package/lib/server/mutants/getArtifactKind.js +32 -0
  383. package/lib/server/mutants/groupMutantCopies.js +56 -0
  384. package/lib/server/mutants/hashArtifact.js +25 -0
  385. package/lib/server/mutants/hashMutantTarget.js +34 -0
  386. package/lib/server/mutants/listMutants.js +122 -0
  387. package/lib/server/mutants/listMutants.test.mjs +502 -0
  388. package/lib/server/mutants/mutantId.js +30 -0
  389. package/lib/server/mutants/mutantRuns.js +57 -0
  390. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  391. package/lib/server/mutants/operators/dropAction.js +47 -0
  392. package/lib/server/mutants/operators/dropBlock.js +44 -0
  393. package/lib/server/mutants/operators/dropPayload.js +52 -0
  394. package/lib/server/mutants/operators/dropStep.js +44 -0
  395. package/lib/server/mutants/operators/flipVisible.js +53 -0
  396. package/lib/server/mutants/operators/index.js +37 -0
  397. package/lib/server/mutants/operators/retargetLink.js +72 -0
  398. package/lib/server/mutants/operators/skipValidate.js +77 -0
  399. package/lib/server/mutants/operators/swapIf.js +112 -0
  400. package/lib/server/mutants/operators.test.mjs +396 -0
  401. package/lib/server/mutants/positionFreePath.js +38 -0
  402. package/lib/server/mutants/removeFromParent.js +28 -0
  403. package/lib/server/mutants/test/fixtures.mjs +173 -0
  404. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  405. package/lib/server/mutants/validateMutantParam.js +69 -0
  406. package/lib/server/mutants/validateMutantsBody.js +88 -0
  407. package/lib/server/mutants/walkArtifact.js +179 -0
  408. package/lib/server/pageBuildRecords.js +131 -0
  409. package/lib/server/pageBuildRecords.test.mjs +258 -0
  410. package/lib/server/pageCache.mjs +22 -7
  411. package/lib/server/pageCache.test.mjs +22 -7
  412. package/lib/server/pageRebuilds.test.mjs +543 -0
  413. package/lib/server/pruneJitMaps.js +36 -0
  414. package/lib/server/pruneJitMaps.test.mjs +55 -0
  415. package/lib/server/readInstanceRecord.js +33 -0
  416. package/lib/server/readInstanceRecord.test.mjs +99 -0
  417. package/lib/server/readMergedMaps.js +86 -0
  418. package/lib/server/readMergedMaps.test.mjs +79 -0
  419. package/lib/server/recording/appendRecords.js +33 -0
  420. package/lib/server/recording/errorRecordingStamp.js +34 -0
  421. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  422. package/lib/server/recording/isRecordingEnabled.js +31 -0
  423. package/lib/server/recording/readRecordingCookie.js +71 -0
  424. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  425. package/lib/server/recording/recordingCookiePayload.js +33 -0
  426. package/lib/server/recording/servedBuilds.js +46 -0
  427. package/lib/server/recording/servedBuilds.test.mjs +42 -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 +25 -3
  439. package/manager/processes/initialBuild.mjs +17 -1
  440. package/manager/processes/initialBuild.test.mjs +104 -0
  441. package/manager/processes/lowdefyBuild.mjs +31 -7
  442. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +72 -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 +60 -47
  527. package/package.original.json +60 -47
  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 +173 -3
  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 +3 -1
  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/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  621. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  622. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  623. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  624. package/src/routes/apiPage.js +0 -37
@@ -0,0 +1,157 @@
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 { Hono } from 'hono';
18
+ import { jest } from '@jest/globals';
19
+
20
+ const mockCreateWebSocketConnection = jest.fn(() => ({
21
+ close: jest.fn(),
22
+ handleMessage: jest.fn(),
23
+ }));
24
+ // The route's origin rule has its own tests (src/routes/websocket.test.mjs);
25
+ // these requests carry no Origin, which it allows.
26
+ jest.unstable_mockModule('@lowdefy/api', () => ({
27
+ createChannelRegistry: jest.fn(() => ({})),
28
+ createWebSocketConnection: mockCreateWebSocketConnection,
29
+ isWebSocketOriginAllowed: () => true,
30
+ }));
31
+ const mockStrategies = [];
32
+ jest.unstable_mockModule('../../lib/server/auth/getStrategies.js', () => ({
33
+ default: () => mockStrategies,
34
+ }));
35
+ const mockLogger = { warn: jest.fn(), debug: jest.fn() };
36
+ jest.unstable_mockModule('../../lib/server/log/createLogger.js', () => ({
37
+ default: () => mockLogger,
38
+ }));
39
+
40
+ const { handleWebSocketUpgrade } = await import('./devWebSocket.js');
41
+ const { default: websocketHandler } = await import('../routes/websocket.js');
42
+
43
+ // Mirrors the dev app: middleware builds the request context, then the
44
+ // websocket route runs. `status` stands in for the auth middleware refusing.
45
+ function createApp({ status } = {}) {
46
+ const app = new Hono();
47
+ app.use('/api/*', async (c, next) => {
48
+ if (status) {
49
+ return c.json({ message: 'Unauthorized.' }, status);
50
+ }
51
+ c.set('lowdefyContext', { rid: 'request-1', logger: mockLogger });
52
+ return next();
53
+ });
54
+ app.get('/api/websocket', websocketHandler);
55
+ return app;
56
+ }
57
+
58
+ function createUpgrade() {
59
+ const socket = { end: jest.fn() };
60
+ const ws = { close: jest.fn(), on: jest.fn(), send: jest.fn() };
61
+ const wss = {
62
+ handleUpgrade: jest.fn((request, socket, head, callback) => {
63
+ callback(ws);
64
+ return ws;
65
+ }),
66
+ };
67
+ const request = {
68
+ headers: { host: 'localhost:3000', cookie: 'session=1' },
69
+ url: '/api/websocket',
70
+ };
71
+ return { request, socket, wss, head: Buffer.alloc(0) };
72
+ }
73
+
74
+ test('an upgrade hands the context the route built to the websocket connection', async () => {
75
+ const upgrade = createUpgrade();
76
+
77
+ await handleWebSocketUpgrade({ app: createApp(), ...upgrade });
78
+
79
+ expect(upgrade.wss.handleUpgrade).toHaveBeenCalledTimes(1);
80
+ expect(mockCreateWebSocketConnection.mock.calls[0][0]).toEqual({
81
+ rid: 'request-1',
82
+ logger: mockLogger,
83
+ });
84
+ expect(upgrade.socket.end).not.toHaveBeenCalled();
85
+ });
86
+
87
+ test('an upgrade hands its headers and the auth strategies to the websocket connection', async () => {
88
+ const upgrade = createUpgrade();
89
+
90
+ await handleWebSocketUpgrade({ app: createApp(), ...upgrade });
91
+
92
+ const options = mockCreateWebSocketConnection.mock.calls[0][1];
93
+ expect(options.headers.get('cookie')).toBe('session=1');
94
+ expect(options.strategies).toBe(mockStrategies);
95
+ });
96
+
97
+ test('the websocket connection closes its socket with the code and reason it is given', async () => {
98
+ const upgrade = createUpgrade();
99
+
100
+ await handleWebSocketUpgrade({ app: createApp(), ...upgrade });
101
+
102
+ const ws = upgrade.wss.handleUpgrade.mock.results[0].value;
103
+ const { closeSocket } = mockCreateWebSocketConnection.mock.calls[0][1];
104
+ closeSocket({ code: 4401, reason: 'caller-changed' });
105
+ expect(ws.close).toHaveBeenCalledWith(4401, 'caller-changed');
106
+ });
107
+
108
+ test('concurrent upgrades through different app instances each get a connection', async () => {
109
+ const first = createUpgrade();
110
+ const second = createUpgrade();
111
+
112
+ await Promise.all([
113
+ handleWebSocketUpgrade({ app: createApp(), ...first }),
114
+ handleWebSocketUpgrade({ app: createApp(), ...second }),
115
+ ]);
116
+
117
+ expect(mockCreateWebSocketConnection).toHaveBeenCalledTimes(2);
118
+ });
119
+
120
+ test('a refused upgrade answers with the route status and logs why', async () => {
121
+ const upgrade = createUpgrade();
122
+
123
+ await handleWebSocketUpgrade({ app: createApp({ status: 401 }), ...upgrade });
124
+
125
+ expect(upgrade.wss.handleUpgrade).not.toHaveBeenCalled();
126
+ expect(upgrade.socket.end).toHaveBeenCalledWith(
127
+ 'HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n'
128
+ );
129
+ expect(mockLogger.warn).toHaveBeenCalledWith(
130
+ { event: 'ws_upgrade_refused', status: 401 },
131
+ 'WebSocket upgrade refused: /api/websocket answered 401. {"message":"Unauthorized."}'
132
+ );
133
+ });
134
+
135
+ test('the websocket route refuses a plain HTTP request', async () => {
136
+ const response = await createApp().request('/api/websocket');
137
+
138
+ expect(response.status).toBe(400);
139
+ expect(await response.json()).toEqual({ message: 'WebSocket upgrade required.' });
140
+ });
141
+
142
+ test('an upgrade answered 200 without a request context is refused and logged', async () => {
143
+ const upgrade = createUpgrade();
144
+ const app = new Hono();
145
+ app.get('/api/websocket', (c) => c.json({ ok: true }));
146
+
147
+ await handleWebSocketUpgrade({ app, ...upgrade });
148
+
149
+ expect(upgrade.wss.handleUpgrade).not.toHaveBeenCalled();
150
+ expect(upgrade.socket.end).toHaveBeenCalledWith(
151
+ 'HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n'
152
+ );
153
+ expect(mockLogger.warn).toHaveBeenCalledWith(
154
+ { event: 'ws_upgrade_refused', status: 200 },
155
+ 'WebSocket upgrade refused: /api/websocket answered 200 without a request context; the websocket route did not run.'
156
+ );
157
+ });
package/vite.config.js CHANGED
@@ -19,6 +19,17 @@ import { defineConfig } from 'vite';
19
19
  import react from '@vitejs/plugin-react';
20
20
  import devServer from '@hono/vite-dev-server';
21
21
  import { WebSocketServer } from 'ws';
22
+ import watchOwner from '@lowdefy/node-utils/watchOwner.js';
23
+
24
+ import createDevServerExclude from './lib/vite/createDevServerExclude.js';
25
+
26
+ const devServerEntry = './src/app.js';
27
+
28
+ // The dev manager spawns this Vite process with a stdin pipe it holds and
29
+ // LOWDEFY_EXIT_ON_STDIN_CLOSE set, so a manager killed outright (SIGKILL, out
30
+ // of memory) does not leave Vite holding the internal port. process.exit runs
31
+ // Playwright's exit handler, which closes any browser this process launched.
32
+ watchOwner({ onExit: () => process.exit(0) });
22
33
 
23
34
  // basePath from the Lowdefy build — assets and routes are served under it.
24
35
  let basePath = '';
@@ -30,10 +41,11 @@ try {
30
41
  }
31
42
 
32
43
  // Vite owns the HTTP server in dev, so /api/websocket upgrades are handled
33
- // here instead of @hono/node-server. The upgrade handler module is loaded
34
- // through Vite's SSR graph so it shares the Hono app and @lowdefy/api module
35
- // instances with the dev server routes. Vite's own HMR websocket uses a
36
- // different path and is untouched.
44
+ // here instead of @hono/node-server. The Hono app is loaded from the same
45
+ // entry @hono/vite-dev-server serves, so the upgrade runs the app every other
46
+ // request runs, and the upgrade handler module is loaded through Vite's SSR
47
+ // graph so it shares the @lowdefy/api module instances with the dev server
48
+ // routes. Vite's own HMR websocket uses a different path and is untouched.
37
49
  function lowdefyWebSocket() {
38
50
  return {
39
51
  name: 'lowdefy-websocket',
@@ -45,12 +57,13 @@ function lowdefyWebSocket() {
45
57
  return;
46
58
  }
47
59
  try {
48
- const { handleWebSocketUpgrade } = await server.ssrLoadModule(
49
- './src/websocket/devWebSocket.js'
50
- );
51
- await handleWebSocketUpgrade({ request, socket, head, wss });
60
+ const [{ default: app }, { handleWebSocketUpgrade }] = await Promise.all([
61
+ server.ssrLoadModule(devServerEntry),
62
+ server.ssrLoadModule('./src/websocket/devWebSocket.js'),
63
+ ]);
64
+ await handleWebSocketUpgrade({ app, request, socket, head, wss });
52
65
  } catch (error) {
53
- console.error(error);
66
+ console.error('WebSocket upgrade failed:', error);
54
67
  socket.destroy();
55
68
  }
56
69
  });
@@ -58,23 +71,26 @@ function lowdefyWebSocket() {
58
71
  };
59
72
  }
60
73
 
74
+ // The @lowdefy packages this server depends on. A linked plugin pinned to
75
+ // another release brings its own copies, and the client would then mix
76
+ // versions: the dev server pre-bundles one copy per package for every
77
+ // importer (whichever its scan meets first, so the server's own code can get
78
+ // a plugin's older @lowdefy/helpers without an export it imports), and a
79
+ // production build bundles both, so blocks read module state the client never
80
+ // filled (the HTML enhancements it registers in @lowdefy/block-utils). The
81
+ // server's copies are the ones its client and blocks were released with.
82
+ const lowdefyDependencies = Object.keys(
83
+ JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url), 'utf8')).dependencies ?? {}
84
+ ).filter((name) => name.startsWith('@lowdefy/'));
85
+
61
86
  export default defineConfig(({ mode }) => ({
62
87
  base: `${basePath}/`,
63
88
  plugins: [
64
89
  react(),
65
90
  lowdefyWebSocket(),
66
91
  devServer({
67
- entry: './src/app.js',
68
- // Vite serves these itself; everything else routes to the Hono app.
69
- exclude: [
70
- /^\/client\/.+/,
71
- /^\/lib\/.+/,
72
- /^\/build\/.+/,
73
- /^\/@.+$/,
74
- /^\/node_modules\/.*/,
75
- /\?t=\d+$/,
76
- /^\/favicon\.ico$/,
77
- ],
92
+ entry: devServerEntry,
93
+ exclude: createDevServerExclude({ basePath }),
78
94
  }),
79
95
  ],
80
96
  define: {
@@ -94,8 +110,20 @@ export default defineConfig(({ mode }) => ({
94
110
  entries: ['client/main.jsx'],
95
111
  },
96
112
  resolve: {
97
- // linked plugin packages (pnpm link: / workspace) must share one React.
98
- dedupe: ['react', 'react-dom'],
113
+ // Linked plugin packages (pnpm link: / workspace) resolve their own copies of
114
+ // shared libraries. They must share one React, the antd/X/cssinjs copies
115
+ // whose context providers (App, ConfigProvider, StyleProvider) the client
116
+ // renders, the dayjs whose global locale the client sets, and the server's
117
+ // @lowdefy packages (see lowdefyDependencies).
118
+ dedupe: [
119
+ 'react',
120
+ 'react-dom',
121
+ 'antd',
122
+ '@ant-design/x',
123
+ '@ant-design/cssinjs',
124
+ 'dayjs',
125
+ ...lowdefyDependencies,
126
+ ],
99
127
  },
100
128
  server: {
101
129
  watch: {
@@ -105,7 +133,9 @@ export default defineConfig(({ mode }) => ({
105
133
  // not need these events: globals.css imports build/tailwind-candidates.css,
106
134
  // which the JIT builder touches whenever tailwind content changes (the
107
135
  // sole recompile trigger — see writeGlobalsCss in @lowdefy/build).
108
- ignored: ['**/lowdefy-build/tailwind/**'],
136
+ // The config build writes into build-staging and then moves each file
137
+ // into build, so only build is watched.
138
+ ignored: ['**/lowdefy-build/tailwind/**', '**/build-staging/**'],
109
139
  },
110
140
  },
111
141
  }));
@@ -1,70 +0,0 @@
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 { createSessionCallback } from '@lowdefy/api';
18
-
19
- import authJson from '../../build/auth.js';
20
- import callbacks from '../../../build/plugins/auth/callbacks.js';
21
- import getHeadlessUser from './getHeadlessUser.js';
22
- import getMockUser from './getMockUser.js';
23
-
24
- // THE single source of dev sessions (mock user, headless renderer). Two
25
- // invariants live here, by construction rather than by convention:
26
- //
27
- // 1. Prod parity: the dev user runs through the identical Auth.js session
28
- // callback a real sign-in uses (OIDC claim copy, userFields mapping,
29
- // custom session-callback plugins, roles validation, hashed_id) — so what
30
- // a mock or headless session sees is exactly what a real authenticated
31
- // user would see in production, and dev validates prod behaviour.
32
- // 2. Client/server parity: every consumer — the server request context
33
- // (session.js) and the browser client's GET /api/auth/session
34
- // (routes/auth.js) — obtains the session from this one function, so the
35
- // two can never diverge.
36
- //
37
- // Auth.js sessions carry an expires timestamp (the client SessionProvider
38
- // schedules refetches off it); dev sessions never expire while the server
39
- // runs, a day keeps polling calm.
40
- const DEV_SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1000;
41
-
42
- async function getDevSession(c) {
43
- const user = getMockUser() ?? getHeadlessUser(c);
44
- if (!user) {
45
- return undefined;
46
- }
47
- // Without auth configured nothing is protected and there is no session
48
- // callback pipeline to run — a dev session would not match any prod
49
- // behaviour. (A configured mock user already threw in getMockUser.)
50
- if (authJson.configured !== true) {
51
- return undefined;
52
- }
53
-
54
- const sessionCallback = createSessionCallback({
55
- authConfig: authJson,
56
- plugins: { callbacks },
57
- });
58
-
59
- // The dev user acts as both token and user, exactly how a decoded JWT
60
- // reaches the session callback on a real request.
61
- const session = await sessionCallback({
62
- session: { user: {} },
63
- token: user,
64
- user,
65
- });
66
- session.expires = new Date(Date.now() + DEV_SESSION_MAX_AGE_MS).toISOString();
67
- return session;
68
- }
69
-
70
- export default getDevSession;
@@ -1,138 +0,0 @@
1
- /*
2
- Copyright 2020-2026 Lowdefy, Inc
3
-
4
- Licensed under the Apache License, Version 2.0 (the "License");
5
- you may not use this file except in compliance with the License.
6
- You may obtain a copy of the License at
7
-
8
- http://www.apache.org/licenses/LICENSE-2.0
9
-
10
- Unless required by applicable law or agreed to in writing, software
11
- distributed under the License is distributed on an "AS IS" BASIS,
12
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- See the License for the specific language governing permissions and
14
- limitations under the License.
15
- */
16
-
17
- import fs from 'fs';
18
- import path from 'path';
19
- import { fileURLToPath } from 'url';
20
-
21
- import { jest } from '@jest/globals';
22
-
23
- // Only the auth build artifact is mocked — createSessionCallback is the REAL
24
- // @lowdefy/api pipeline, because prod parity is exactly what these tests
25
- // exist to prove: a dev session must be shaped by the same userFields
26
- // mapping, roles validation, and hashed_id stamping as a real sign-in.
27
- jest.unstable_mockModule('../../build/auth.js', () => ({
28
- default: {
29
- configured: true,
30
- callbacks: [],
31
- userFields: {
32
- id: 'user.id',
33
- roles: 'user.roles',
34
- global_attributes: 'user.global_attributes',
35
- },
36
- },
37
- }));
38
-
39
- // getDevSession statically imports the app build's auth callbacks module,
40
- // which only exists in a built app (build/** is gitignored). Materialize an
41
- // empty one — exactly what a build without custom auth callback plugins
42
- // writes — so the import resolves; clean up only if this test created it.
43
- const dirname = path.dirname(fileURLToPath(import.meta.url));
44
- const callbacksDir = path.resolve(dirname, '../../../build/plugins/auth');
45
- const callbacksPath = path.join(callbacksDir, 'callbacks.js');
46
- const createdCallbacksFixture = !fs.existsSync(callbacksPath);
47
- if (createdCallbacksFixture) {
48
- fs.mkdirSync(callbacksDir, { recursive: true });
49
- fs.writeFileSync(callbacksPath, 'export default {};\n');
50
- }
51
-
52
- afterAll(() => {
53
- if (createdCallbacksFixture) {
54
- fs.rmSync(callbacksPath, { force: true });
55
- }
56
- });
57
-
58
- const { default: getDevSession } = await import('./getDevSession.js');
59
- const { HEADLESS_USER_COOKIE, headlessUser } = await import('./headlessUser.js');
60
-
61
- function createContext({ cookie } = {}) {
62
- return { req: { header: (name) => (name === 'cookie' ? (cookie ?? '') : undefined) } };
63
- }
64
-
65
- function headlessCookie(user = headlessUser) {
66
- return `${HEADLESS_USER_COOKIE}=${Buffer.from(JSON.stringify(user)).toString('base64')}`;
67
- }
68
-
69
- const originalDevUser = process.env.LOWDEFY_DEV_USER;
70
-
71
- afterEach(() => {
72
- if (originalDevUser === undefined) {
73
- delete process.env.LOWDEFY_DEV_USER;
74
- } else {
75
- process.env.LOWDEFY_DEV_USER = originalDevUser;
76
- }
77
- });
78
-
79
- test('getDevSession returns undefined with no mock user and no headless cookie', async () => {
80
- delete process.env.LOWDEFY_DEV_USER;
81
- expect(await getDevSession(createContext())).toBe(undefined);
82
- });
83
-
84
- test('mock user session is built by the real session callback — userFields, hashed_id, expires', async () => {
85
- // The claim shape a consuming app's tenant convention uses (e.g.
86
- // global_attributes.company_ids) must survive to session.user on BOTH the
87
- // server request path and the client session endpoint — this session is
88
- // what both serve.
89
- process.env.LOWDEFY_DEV_USER = JSON.stringify({
90
- id: 'dev-user',
91
- sub: 'dev-user',
92
- name: 'Dev User',
93
- roles: ['admin'],
94
- global_attributes: { company_ids: ['C-1'] },
95
- });
96
-
97
- const session = await getDevSession(createContext());
98
- expect(session.user.id).toEqual('dev-user');
99
- expect(session.user.name).toEqual('Dev User');
100
- expect(session.user.roles).toEqual(['admin']);
101
- expect(session.user.global_attributes).toEqual({ company_ids: ['C-1'] });
102
- // Prod-session markers from the real pipeline:
103
- expect(typeof session.hashed_id).toBe('string');
104
- expect(session.hashed_id.length).toBeGreaterThan(0);
105
- expect(new Date(session.expires).getTime()).toBeGreaterThan(Date.now());
106
- });
107
-
108
- test('headless cookie session runs through the same pipeline as a real sign-in', async () => {
109
- delete process.env.LOWDEFY_DEV_USER;
110
-
111
- const session = await getDevSession(createContext({ cookie: headlessCookie() }));
112
- expect(session.user.id).toEqual(headlessUser.id);
113
- expect(session.user.roles).toEqual([]);
114
- expect(typeof session.hashed_id).toBe('string');
115
- });
116
-
117
- test('mock user takes precedence over the headless cookie', async () => {
118
- process.env.LOWDEFY_DEV_USER = JSON.stringify({ id: 'mock-wins', roles: [] });
119
-
120
- const session = await getDevSession(createContext({ cookie: headlessCookie() }));
121
- expect(session.user.id).toEqual('mock-wins');
122
- });
123
-
124
- test('invalid roles are rejected by the real pipeline, exactly as in prod', async () => {
125
- process.env.LOWDEFY_DEV_USER = JSON.stringify({ id: 'bad', roles: 'admin' });
126
-
127
- await expect(getDevSession(createContext())).rejects.toThrow(
128
- 'session.user.roles must be an array of strings.'
129
- );
130
- });
131
-
132
- test('invalid JSON in LOWDEFY_DEV_USER throws a clear error', async () => {
133
- process.env.LOWDEFY_DEV_USER = 'not json';
134
-
135
- await expect(getDevSession(createContext())).rejects.toThrow(
136
- 'Invalid JSON in LOWDEFY_DEV_USER environment variable.'
137
- );
138
- });
@@ -1,47 +0,0 @@
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
- // Auth.js derives the app origin from request headers (trustHost), so an unset
18
- // AUTH_URL works on whatever port the dev server binds to. But when AUTH_URL (or
19
- // the v4 fallback NEXTAUTH_URL) is set, @auth/core pins the origin to it verbatim
20
- // — OAuth callbacks and sign-in redirects then target that URL. A pinned URL on a
21
- // mismatched port is left untouched but warned about.
22
- function warnAuthUrlPortMismatch({ context }) {
23
- const { port } = context.options;
24
- const name = process.env.AUTH_URL ? 'AUTH_URL' : 'NEXTAUTH_URL';
25
- const current = process.env.AUTH_URL ?? process.env.NEXTAUTH_URL;
26
-
27
- if (!current) return;
28
-
29
- let currentPort;
30
- try {
31
- const url = new URL(current);
32
- currentPort = url.port || (url.protocol === 'https:' ? '443' : '80');
33
- } catch (_) {
34
- context.logger.warn(`${name} (${current}) is not a valid URL.`);
35
- return;
36
- }
37
-
38
- if (currentPort !== String(port)) {
39
- context.logger.warn(
40
- `${name} (${current}) does not match the dev server port ${port}. ` +
41
- `Auth sign-in callbacks and redirects will target ${current}. ` +
42
- `Update ${name} or unset it to derive the origin from the request.`
43
- );
44
- }
45
- }
46
-
47
- export default warnAuthUrlPortMismatch;
@@ -1,93 +0,0 @@
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
- import warnAuthUrlPortMismatch from './warnAuthUrlPortMismatch.mjs';
19
-
20
- const originalAuthUrl = process.env.AUTH_URL;
21
- const originalNextAuthUrl = process.env.NEXTAUTH_URL;
22
-
23
- function createContext(port) {
24
- return {
25
- options: { port },
26
- logger: { warn: jest.fn() },
27
- };
28
- }
29
-
30
- function restoreEnv(name, value) {
31
- if (value === undefined) {
32
- delete process.env[name];
33
- } else {
34
- process.env[name] = value;
35
- }
36
- }
37
-
38
- beforeEach(() => {
39
- delete process.env.AUTH_URL;
40
- delete process.env.NEXTAUTH_URL;
41
- });
42
-
43
- afterEach(() => {
44
- restoreEnv('AUTH_URL', originalAuthUrl);
45
- restoreEnv('NEXTAUTH_URL', originalNextAuthUrl);
46
- });
47
-
48
- test('warnAuthUrlPortMismatch stays silent when no auth URL is set', () => {
49
- const context = createContext(3001);
50
- warnAuthUrlPortMismatch({ context });
51
- expect(context.logger.warn).not.toHaveBeenCalled();
52
- });
53
-
54
- test('warnAuthUrlPortMismatch stays silent when AUTH_URL matches the dev port', () => {
55
- process.env.AUTH_URL = 'http://localhost:3000';
56
- const context = createContext(3000);
57
- warnAuthUrlPortMismatch({ context });
58
- expect(context.logger.warn).not.toHaveBeenCalled();
59
- });
60
-
61
- test('warnAuthUrlPortMismatch warns when AUTH_URL port does not match', () => {
62
- process.env.AUTH_URL = 'http://localhost:3000';
63
- const context = createContext(3001);
64
- warnAuthUrlPortMismatch({ context });
65
- expect(context.logger.warn).toHaveBeenCalledTimes(1);
66
- expect(context.logger.warn.mock.calls[0][0]).toContain(
67
- 'AUTH_URL (http://localhost:3000) does not match the dev server port 3001'
68
- );
69
- });
70
-
71
- test('warnAuthUrlPortMismatch falls back to NEXTAUTH_URL and names it in the warning', () => {
72
- process.env.NEXTAUTH_URL = 'http://localhost:3000';
73
- const context = createContext(3001);
74
- warnAuthUrlPortMismatch({ context });
75
- expect(context.logger.warn).toHaveBeenCalledTimes(1);
76
- expect(context.logger.warn.mock.calls[0][0]).toContain('NEXTAUTH_URL (http://localhost:3000)');
77
- });
78
-
79
- test('warnAuthUrlPortMismatch treats an https URL without a port as 443', () => {
80
- process.env.AUTH_URL = 'https://example.com';
81
- const context = createContext(3000);
82
- warnAuthUrlPortMismatch({ context });
83
- expect(context.logger.warn).toHaveBeenCalledTimes(1);
84
- expect(context.logger.warn.mock.calls[0][0]).toContain('does not match the dev server port 3000');
85
- });
86
-
87
- test('warnAuthUrlPortMismatch warns when the auth URL is not a valid URL', () => {
88
- process.env.AUTH_URL = 'not-a-url';
89
- const context = createContext(3000);
90
- warnAuthUrlPortMismatch({ context });
91
- expect(context.logger.warn).toHaveBeenCalledTimes(1);
92
- expect(context.logger.warn.mock.calls[0][0]).toContain('is not a valid URL');
93
- });