@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,79 @@
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 createPluginSourceIgnore from './createPluginSourceIgnore.mjs';
20
+ import listServerSidePackages from './listServerSidePackages.mjs';
21
+ import readPluginDefinitions from '../utils/readPluginDefinitions.mjs';
22
+ import selectLocalPluginPackages from './selectLocalPluginPackages.mjs';
23
+ import setupWatcher from '../utils/setupWatcher.mjs';
24
+
25
+ function isInside({ filePath, dir }) {
26
+ const relative = path.relative(dir, filePath);
27
+ return !relative.startsWith('..') && !path.isAbsolute(relative);
28
+ }
29
+
30
+ // A local plugin's code can change while the dev server runs, and nothing
31
+ // else watches it when it lives outside the config directory. The config
32
+ // build reads each plugin's type list, so any change rebuilds - which also
33
+ // clears a build a broken plugin failed. The server process caches the
34
+ // modules of server-side types (requests, connections, operators, ...), so a
35
+ // change to a plugin that has them also restarts the server; client code
36
+ // is Vite's to hot-replace. Only code files anywhere in the package are
37
+ // watched: a plugin may be imported from its sources or from a build output
38
+ // its own watcher writes, but non-code files (public/, YAML, Markdown) are
39
+ // never plugin code, and a package that is also the config directory writes
40
+ // some of them on every build (see createPluginSourceIgnore).
41
+ async function pluginSourceWatcher(context) {
42
+ const plugins = await readPluginDefinitions({ directories: context.directories });
43
+ const packages = selectLocalPluginPackages({
44
+ directories: context.directories,
45
+ packageNames: plugins.map((plugin) => plugin.name),
46
+ });
47
+ if (packages.length === 0) {
48
+ return undefined;
49
+ }
50
+
51
+ const callback = async (filePaths) => {
52
+ const changedFiles = filePaths.flat();
53
+ const customTypesMap = JSON.parse(
54
+ fs.readFileSync(path.join(context.directories.build, 'customTypesMap.json'), 'utf8')
55
+ );
56
+ const serverSide = listServerSidePackages({ customTypesMap });
57
+ const restart = packages.some(
58
+ ({ package: packageName, dir }) =>
59
+ serverSide.has(packageName) && changedFiles.some((filePath) => isInside({ filePath, dir }))
60
+ );
61
+ context.logger.info({ spin: 'start' }, 'Local plugin source changed, rebuilding.');
62
+ try {
63
+ await context.lowdefyBuild();
64
+ } finally {
65
+ await context.syncServer({ restart });
66
+ await context.reloadClients();
67
+ }
68
+ };
69
+
70
+ return setupWatcher({
71
+ callback,
72
+ context,
73
+ onBusy: context.buildActivity.setBusy,
74
+ ignorePaths: [createPluginSourceIgnore({ directories: context.directories })],
75
+ watchPaths: packages.map(({ dir }) => dir),
76
+ });
77
+ }
78
+
79
+ export default pluginSourceWatcher;
@@ -0,0 +1,222 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import fs from 'fs';
18
+ import os from 'os';
19
+ import path from 'path';
20
+ import { jest } from '@jest/globals';
21
+
22
+ import flushFsEvents from '../../test-utils/flushFsEvents.mjs';
23
+ import waitFor from '../../test-utils/waitFor.mjs';
24
+
25
+ const { default: pluginSourceWatcher } = await import('./pluginSourceWatcher.mjs');
26
+
27
+ // macOS can hold file events back for tens of seconds on a busy machine.
28
+ jest.setTimeout(180000);
29
+
30
+ let root;
31
+ let context;
32
+ let watcher;
33
+
34
+ function writeCustomTypesMap(customTypesMap) {
35
+ fs.writeFileSync(
36
+ path.join(context.directories.build, 'customTypesMap.json'),
37
+ JSON.stringify(customTypesMap)
38
+ );
39
+ }
40
+
41
+ function writeLowdefyYaml(pluginNames) {
42
+ fs.writeFileSync(
43
+ path.join(context.directories.config, 'lowdefy.yaml'),
44
+ `lowdefy: local\nplugins:\n${pluginNames
45
+ .map((name) => ` - name: '${name}'\n version: 'workspace:*'\n`)
46
+ .join('')}`
47
+ );
48
+ }
49
+
50
+ // Otherwise the fixture writes can reach the watcher as edits and rebuild
51
+ // before the edit under test.
52
+ async function startWatcher() {
53
+ await flushFsEvents();
54
+ watcher = await pluginSourceWatcher(context);
55
+ }
56
+
57
+ function addLinkedPackage(name) {
58
+ const dir = path.join(root, 'plugins', name);
59
+ fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
60
+ fs.mkdirSync(path.join(dir, 'dist'), { recursive: true });
61
+ const linked = path.join(context.directories.server, 'node_modules', name);
62
+ fs.mkdirSync(path.dirname(linked), { recursive: true });
63
+ fs.symlinkSync(dir, linked, 'dir');
64
+ return dir;
65
+ }
66
+
67
+ beforeEach(() => {
68
+ root = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-plugin-watcher-test-'));
69
+ const config = path.join(root, 'app');
70
+ const server = path.join(config, '.lowdefy', 'dev');
71
+ fs.mkdirSync(path.join(server, 'node_modules'), { recursive: true });
72
+ fs.mkdirSync(path.join(server, 'build'), { recursive: true });
73
+ context = {
74
+ buildActivity: { setBusy: jest.fn() },
75
+ directories: { build: path.join(server, 'build'), config, server },
76
+ logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn() },
77
+ lowdefyBuild: jest.fn(async () => {}),
78
+ reloadClients: jest.fn(async () => {}),
79
+ syncServer: jest.fn(async () => {}),
80
+ };
81
+ writeCustomTypesMap({});
82
+ });
83
+
84
+ afterEach(async () => {
85
+ if (watcher) {
86
+ await watcher.close();
87
+ watcher = undefined;
88
+ }
89
+ fs.rmSync(root, { recursive: true, force: true });
90
+ });
91
+
92
+ test('pluginSourceWatcher watches nothing when lowdefy.yaml lists no local plugin', async () => {
93
+ writeLowdefyYaml(['@lowdefy/blocks-basic']);
94
+
95
+ await expect(pluginSourceWatcher(context)).resolves.toBeUndefined();
96
+ });
97
+
98
+ test.each([
99
+ ['a server-side plugin rebuilds and restarts the server', 'requests', 'src/find.js', true],
100
+ ['a block-only plugin rebuilds without a restart', 'blocks', 'src/Fancy.js', false],
101
+ ['a plugin imported from its build output rebuilds', 'blocks', 'dist/Fancy.js', false],
102
+ ])('editing %s', async (_, kind, file, restart) => {
103
+ const dir = addLinkedPackage('@app/plugin');
104
+ fs.writeFileSync(path.join(dir, file), 'export default 1;');
105
+ writeLowdefyYaml(['@app/plugin']);
106
+ writeCustomTypesMap({ [kind]: { Type: { package: '@app/plugin', version: '1.0.0' } } });
107
+
108
+ await startWatcher();
109
+ fs.writeFileSync(path.join(dir, file), 'export default 2;');
110
+ await waitFor(() => context.reloadClients.mock.calls.length > 0, {
111
+ description: 'clients to reload',
112
+ });
113
+
114
+ expect(context.lowdefyBuild).toHaveBeenCalledTimes(1);
115
+ expect(context.syncServer).toHaveBeenCalledWith({ restart });
116
+ expect(context.buildActivity.setBusy.mock.calls[0]).toEqual([true]);
117
+ });
118
+
119
+ test('a fix to a plugin whose edit failed the build rebuilds again', async () => {
120
+ const dir = addLinkedPackage('@app/plugin');
121
+ fs.writeFileSync(path.join(dir, 'src', 'types.js'), 'export default {};');
122
+ writeLowdefyYaml(['@app/plugin']);
123
+ context.lowdefyBuild.mockRejectedValueOnce(new Error('Failed to import plugin "@app/plugin".'));
124
+
125
+ await startWatcher();
126
+ fs.writeFileSync(path.join(dir, 'src', 'types.js'), 'export default {;');
127
+ await waitFor(() => context.syncServer.mock.calls.length === 1, {
128
+ description: 'the failed build to sync the server',
129
+ });
130
+ fs.writeFileSync(path.join(dir, 'src', 'types.js'), 'export default { blocks: [] };');
131
+ await waitFor(() => context.syncServer.mock.calls.length === 2, {
132
+ description: 'the fixed build to sync the server',
133
+ });
134
+
135
+ expect(context.lowdefyBuild).toHaveBeenCalledTimes(2);
136
+ });
137
+
138
+ function linkConfigAsPackage(name) {
139
+ const dir = context.directories.config;
140
+ fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
141
+ fs.mkdirSync(path.join(dir, 'dist'), { recursive: true });
142
+ fs.mkdirSync(path.join(dir, 'public', 'md'), { recursive: true });
143
+ fs.mkdirSync(path.join(dir, 'pages'), { recursive: true });
144
+ const linked = path.join(context.directories.server, 'node_modules', name);
145
+ fs.mkdirSync(path.dirname(linked), { recursive: true });
146
+ fs.symlinkSync(dir, linked, 'dir');
147
+ return dir;
148
+ }
149
+
150
+ // Gives chokidar time to report an event the watcher should ignore, then
151
+ // checks no build ran: a build would have started within the batch delay.
152
+ async function expectNoRebuild() {
153
+ await new Promise((resolve) => setTimeout(resolve, 1200));
154
+ expect(context.lowdefyBuild).not.toHaveBeenCalled();
155
+ }
156
+
157
+ describe('a plugin package that is also the config directory', () => {
158
+ // Only non-code files are written before the watcher starts: macOS can
159
+ // report a write made just before the watch began, which for a code file
160
+ // would rebuild and spoil a test that expects none.
161
+ beforeEach(() => {
162
+ const dir = linkConfigAsPackage('@app/docs');
163
+ fs.writeFileSync(path.join(dir, 'public', 'llms.txt'), 'one');
164
+ fs.writeFileSync(path.join(dir, 'public', 'search-index.json'), '[]');
165
+ fs.writeFileSync(path.join(dir, 'public', 'md', 'page.md'), 'one');
166
+ fs.writeFileSync(path.join(dir, 'pages', 'home.yaml'), 'id: home');
167
+ fs.writeFileSync(path.join(dir, 'pages', 'home.md'), 'one');
168
+ writeLowdefyYaml(['@app/docs']);
169
+ });
170
+
171
+ test.each([['public/llms.txt'], ['public/search-index.json'], ['public/md/page.md']])(
172
+ 'writing %s does not rebuild',
173
+ async (file) => {
174
+ await startWatcher();
175
+ fs.writeFileSync(path.join(context.directories.config, file), 'two');
176
+ await expectNoRebuild();
177
+ }
178
+ );
179
+
180
+ test.each([['pages/home.yaml'], ['pages/home.md']])(
181
+ 'editing %s does not rebuild',
182
+ async (file) => {
183
+ await startWatcher();
184
+ fs.writeFileSync(path.join(context.directories.config, file), 'two');
185
+ await expectNoRebuild();
186
+ }
187
+ );
188
+
189
+ test('writing into the build directory does not rebuild', async () => {
190
+ await startWatcher();
191
+ fs.writeFileSync(path.join(context.directories.build, 'keyMap.json'), '{}');
192
+ await expectNoRebuild();
193
+ });
194
+
195
+ test.each([['src/types.js'], ['dist/types.js']])('editing %s rebuilds', async (file) => {
196
+ await startWatcher();
197
+ fs.writeFileSync(path.join(context.directories.config, file), 'export default 2;');
198
+ await waitFor(() => context.reloadClients.mock.calls.length > 0);
199
+ expect(context.lowdefyBuild).toHaveBeenCalledTimes(1);
200
+ });
201
+ });
202
+
203
+ test.each([
204
+ ['a .js file rebuilds', 'src/other.js', true],
205
+ ['a .json file rebuilds', 'package.json', true],
206
+ ['a .md file does not rebuild', 'README.md', false],
207
+ ['a .yaml file does not rebuild', 'src/config.yaml', false],
208
+ ['a file with no extension does not rebuild', 'LICENSE', false],
209
+ ])('in a plugin outside the config directory, %s', async (_, file, rebuilds) => {
210
+ const dir = addLinkedPackage('@app/plugin');
211
+ fs.writeFileSync(path.join(dir, file), 'one');
212
+ writeLowdefyYaml(['@app/plugin']);
213
+
214
+ await startWatcher();
215
+ fs.writeFileSync(path.join(dir, file), 'two');
216
+ if (rebuilds) {
217
+ await waitFor(() => context.reloadClients.mock.calls.length > 0);
218
+ expect(context.lowdefyBuild).toHaveBeenCalledTimes(1);
219
+ return;
220
+ }
221
+ await expectNoRebuild();
222
+ });
@@ -0,0 +1,71 @@
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 { type } from '@lowdefy/helpers';
20
+ import setupWatcher from '../utils/setupWatcher.mjs';
21
+
22
+ function readReason(filePath) {
23
+ try {
24
+ const { reason } = JSON.parse(fs.readFileSync(filePath, 'utf8'));
25
+ return type.isString(reason) && reason !== '' ? reason : 'no reason given';
26
+ } catch {
27
+ // The app may still be writing the file, or it was already consumed.
28
+ return 'no reason given';
29
+ }
30
+ }
31
+
32
+ // The dev tools (POST /lowdefy-docs/restart, lowdefy_restart) run inside the
33
+ // app process, which has no channel to the manager except the build directory.
34
+ // They write build/.restart; the manager consumes it here.
35
+ function restartRequestWatcher(context) {
36
+ const sentinelPath = path.join(context.directories.build, '.restart');
37
+
38
+ const callback = async () => {
39
+ if (!fs.existsSync(sentinelPath)) {
40
+ return;
41
+ }
42
+ const reason = readReason(sentinelPath);
43
+ fs.rmSync(sentinelPath, { force: true });
44
+ context.logger.info({ spin: 'start' }, `Restart requested by the dev tools: ${reason}.`);
45
+ // The config build reads the plugins' type lists afresh, so a block,
46
+ // request or operator type added to a plugin since the last build is
47
+ // defined once the server is back. The server restarts even when the
48
+ // build fails.
49
+ try {
50
+ await context.lowdefyBuild();
51
+ } finally {
52
+ await context.syncServer({ restart: true });
53
+ }
54
+ };
55
+
56
+ // The sentinel does not exist until the first request, so watch the build
57
+ // directory (non-recursively) and react only to the sentinel itself.
58
+ return setupWatcher({
59
+ callback,
60
+ context,
61
+ onBusy: context.buildActivity.setBusy,
62
+ watchDotfiles: true,
63
+ watchPaths: [context.directories.build],
64
+ ignorePaths: [
65
+ (filePath) => filePath !== context.directories.build && filePath !== sentinelPath,
66
+ ],
67
+ delay: 100,
68
+ });
69
+ }
70
+
71
+ export default restartRequestWatcher;
@@ -0,0 +1,111 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import fs from 'fs';
18
+ import os from 'os';
19
+ import path from 'path';
20
+ import { jest } from '@jest/globals';
21
+
22
+ import flushFsEvents from '../../test-utils/flushFsEvents.mjs';
23
+ import waitFor from '../../test-utils/waitFor.mjs';
24
+
25
+ const { default: restartRequestWatcher } = await import('./restartRequestWatcher.mjs');
26
+
27
+ // macOS can hold file events back for tens of seconds on a busy machine.
28
+ jest.setTimeout(180000);
29
+
30
+ let fixtureDir;
31
+ let context;
32
+ let watcher;
33
+
34
+ beforeEach(async () => {
35
+ fixtureDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-restart-watcher-test-'));
36
+ fs.mkdirSync(path.join(fixtureDir, 'build'), { recursive: true });
37
+ await flushFsEvents();
38
+ context = {
39
+ directories: { build: path.join(fixtureDir, 'build') },
40
+ logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn() },
41
+ buildActivity: { setBusy: jest.fn() },
42
+ lowdefyBuild: jest.fn(async () => {}),
43
+ syncServer: jest.fn(async () => {}),
44
+ };
45
+ });
46
+
47
+ afterEach(async () => {
48
+ if (watcher) {
49
+ await watcher.close();
50
+ watcher = undefined;
51
+ }
52
+ fs.rmSync(fixtureDir, { recursive: true, force: true });
53
+ });
54
+
55
+ test('writing build/.restart restarts the server once and removes the sentinel', async () => {
56
+ watcher = await restartRequestWatcher(context);
57
+ const sentinelPath = path.join(fixtureDir, 'build', '.restart');
58
+
59
+ fs.writeFileSync(
60
+ sentinelPath,
61
+ JSON.stringify({ requestedAt: new Date().toISOString(), reason: 'Edited a request plugin' })
62
+ );
63
+ await waitFor(() => context.syncServer.mock.calls.length > 0, {
64
+ description: 'the server restart',
65
+ });
66
+ expect(fs.existsSync(sentinelPath)).toBe(false);
67
+
68
+ // Removing the sentinel is itself a file event. A second request, seen
69
+ // after it, fences it: any restart the removal caused lands before the
70
+ // second request's.
71
+ fs.writeFileSync(sentinelPath, JSON.stringify({ reason: 'Fence' }));
72
+ await waitFor(() => context.syncServer.mock.calls.length > 1, {
73
+ description: 'the second restart',
74
+ });
75
+
76
+ expect(context.syncServer).toHaveBeenCalledTimes(2);
77
+ expect(
78
+ context.logger.info.mock.calls.filter(([, message]) => message.startsWith('Restart requested'))
79
+ ).toEqual([
80
+ [{ spin: 'start' }, 'Restart requested by the dev tools: Edited a request plugin.'],
81
+ [{ spin: 'start' }, 'Restart requested by the dev tools: Fence.'],
82
+ ]);
83
+ });
84
+
85
+ test('a restart request rebuilds the config before restarting the server', async () => {
86
+ watcher = await restartRequestWatcher(context);
87
+ const sentinelPath = path.join(fixtureDir, 'build', '.restart');
88
+
89
+ fs.writeFileSync(sentinelPath, JSON.stringify({ reason: 'Added a block type' }));
90
+ await waitFor(() => context.syncServer.mock.calls.length > 0, {
91
+ description: 'the server restart',
92
+ });
93
+
94
+ expect(context.lowdefyBuild).toHaveBeenCalledTimes(1);
95
+ expect(context.lowdefyBuild.mock.invocationCallOrder[0]).toBeLessThan(
96
+ context.syncServer.mock.invocationCallOrder[0]
97
+ );
98
+ });
99
+
100
+ test('a restart request restarts the server when the config build fails', async () => {
101
+ context.lowdefyBuild.mockRejectedValue(new Error('Build failed with 1 error(s).'));
102
+ watcher = await restartRequestWatcher(context);
103
+ const sentinelPath = path.join(fixtureDir, 'build', '.restart');
104
+
105
+ fs.writeFileSync(sentinelPath, JSON.stringify({ reason: 'Added a block type' }));
106
+ await waitFor(() => context.syncServer.mock.calls.length > 0, {
107
+ description: 'the server restart',
108
+ });
109
+
110
+ expect(context.syncServer).toHaveBeenCalledTimes(1);
111
+ });
@@ -0,0 +1,48 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import fs from 'fs';
18
+ import path from 'path';
19
+
20
+ // The build imports plugins from the server directory; an app in a pnpm
21
+ // workspace also links them into its own node_modules. A published package
22
+ // lives inside node_modules and cannot change under a running dev server;
23
+ // only a linked local plugin resolves outside it.
24
+ function resolveLocalPluginDir({ directories, packageName }) {
25
+ for (const base of [directories.server, directories.config]) {
26
+ const linked = path.join(base, 'node_modules', packageName);
27
+ if (fs.existsSync(linked)) {
28
+ const realPath = fs.realpathSync(linked);
29
+ return realPath.split(path.sep).includes('node_modules') ? null : realPath;
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+
35
+ // The plugins lowdefy.yaml lists that are local packages, whatever kinds of
36
+ // types they hold: a block-only plugin's type list is read by the config
37
+ // build too, so an edit to it - or a fix to one that broke the build - must
38
+ // rebuild.
39
+ function selectLocalPluginPackages({ directories, packageNames }) {
40
+ return [...new Set(packageNames)]
41
+ .map((packageName) => ({
42
+ package: packageName,
43
+ dir: resolveLocalPluginDir({ directories, packageName }),
44
+ }))
45
+ .filter(({ dir }) => dir !== null);
46
+ }
47
+
48
+ export default selectLocalPluginPackages;
@@ -0,0 +1,73 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import fs from 'fs';
18
+ import os from 'os';
19
+ import path from 'path';
20
+
21
+ import selectLocalPluginPackages from './selectLocalPluginPackages.mjs';
22
+
23
+ let root;
24
+ let directories;
25
+
26
+ function addLinkedPackage({ name, base }) {
27
+ const dir = path.join(root, 'plugins', name);
28
+ fs.mkdirSync(dir, { recursive: true });
29
+ fs.mkdirSync(path.dirname(path.join(base, 'node_modules', name)), { recursive: true });
30
+ fs.symlinkSync(dir, path.join(base, 'node_modules', name), 'dir');
31
+ return fs.realpathSync(dir);
32
+ }
33
+
34
+ beforeEach(() => {
35
+ root = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-plugin-select-test-'));
36
+ directories = {
37
+ config: path.join(root, 'app'),
38
+ server: path.join(root, 'app', '.lowdefy', 'dev'),
39
+ };
40
+ fs.mkdirSync(path.join(directories.server, 'node_modules'), { recursive: true });
41
+ });
42
+
43
+ afterEach(() => {
44
+ fs.rmSync(root, { recursive: true, force: true });
45
+ });
46
+
47
+ test('selectLocalPluginPackages selects every linked plugin, block-only plugins included', () => {
48
+ const ui = addLinkedPackage({ name: '@app/ui-plugin', base: directories.server });
49
+ const db = addLinkedPackage({ name: '@app/db-plugin', base: directories.config });
50
+
51
+ expect(
52
+ selectLocalPluginPackages({
53
+ directories,
54
+ packageNames: ['@app/ui-plugin', '@app/db-plugin', '@app/ui-plugin'],
55
+ })
56
+ ).toEqual([
57
+ { package: '@app/ui-plugin', dir: ui },
58
+ { package: '@app/db-plugin', dir: db },
59
+ ]);
60
+ });
61
+
62
+ test('selectLocalPluginPackages skips installed and missing packages', () => {
63
+ fs.mkdirSync(path.join(directories.server, 'node_modules', '@lowdefy', 'connection-mongodb'), {
64
+ recursive: true,
65
+ });
66
+
67
+ expect(
68
+ selectLocalPluginPackages({
69
+ directories,
70
+ packageNames: ['@lowdefy/connection-mongodb', '@app/not-installed'],
71
+ })
72
+ ).toEqual([]);
73
+ });
@@ -0,0 +1,38 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import path from 'path';
18
+ import setupWatcher from '../utils/setupWatcher.mjs';
19
+
20
+ // A page build in the dev server that finds a plugin package missing adds it
21
+ // to the server's package.json (the page answers "installing"). Installing it,
22
+ // rebuilding and restarting is syncServer's work, as it is after a config
23
+ // build that adds one. Config builds sync the server themselves, so this
24
+ // watcher only has work when the change came from the dev server.
25
+ function serverPackageWatcher(context) {
26
+ const callback = async () => {
27
+ await context.syncServer();
28
+ };
29
+ return setupWatcher({
30
+ callback,
31
+ context,
32
+ onBusy: context.buildActivity.setBusy,
33
+ watchDotfiles: true,
34
+ watchPaths: [path.join(context.directories.server, 'package.json')],
35
+ });
36
+ }
37
+
38
+ export default serverPackageWatcher;