@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,87 @@
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: envWatcher } = await import('./envWatcher.mjs');
26
+
27
+ // macOS can hold file events back for tens of seconds on a busy machine.
28
+ jest.setTimeout(180000);
29
+
30
+ let configDir;
31
+ let context;
32
+ let watcher;
33
+
34
+ beforeEach(async () => {
35
+ configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-env-watcher-test-'));
36
+ fs.writeFileSync(path.join(configDir, '.env'), 'LOWDEFY_SECRET_A=one\n');
37
+ // Otherwise the fixture write can reach the watcher as a second edit.
38
+ await flushFsEvents();
39
+ context = {
40
+ directories: { config: configDir },
41
+ logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn() },
42
+ lowdefyBuild: jest.fn(async () => {}),
43
+ buildActivity: { setBusy: jest.fn() },
44
+ readDotEnv: jest.fn(),
45
+ syncServer: jest.fn(async () => {}),
46
+ };
47
+ });
48
+
49
+ afterEach(async () => {
50
+ if (watcher) {
51
+ await watcher.close();
52
+ watcher = undefined;
53
+ }
54
+ fs.rmSync(configDir, { recursive: true, force: true });
55
+ });
56
+
57
+ test.each([
58
+ ['succeeds', async () => {}],
59
+ [
60
+ 'fails',
61
+ async () => {
62
+ throw new Error('Build failed with 1 error(s).');
63
+ },
64
+ ],
65
+ ])(
66
+ 'a .env edit reads the file, rebuilds and restarts the server when the build %s',
67
+ async (_, build) => {
68
+ context.lowdefyBuild.mockImplementation(build);
69
+ watcher = await envWatcher(context);
70
+
71
+ fs.writeFileSync(path.join(configDir, '.env'), 'LOWDEFY_SECRET_A=two\n');
72
+ await waitFor(() => context.syncServer.mock.calls.length > 0, {
73
+ description: 'the server sync',
74
+ });
75
+
76
+ expect(context.readDotEnv).toHaveBeenCalledTimes(1);
77
+ expect(context.lowdefyBuild).toHaveBeenCalledTimes(1);
78
+ expect(context.readDotEnv.mock.invocationCallOrder[0]).toBeLessThan(
79
+ context.lowdefyBuild.mock.invocationCallOrder[0]
80
+ );
81
+ expect(context.syncServer).toHaveBeenCalledWith({ restart: true });
82
+ await waitFor(() => context.buildActivity.setBusy.mock.calls.length === 2, {
83
+ description: 'the watcher to report it is idle',
84
+ });
85
+ expect(context.buildActivity.setBusy.mock.calls).toEqual([[true], [false]]);
86
+ }
87
+ );
@@ -0,0 +1,47 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { get, type } from '@lowdefy/helpers';
18
+
19
+ // Only these kinds are loaded by the server process and cached in its ESM
20
+ // module cache. blocks, actions and operators.client are bundled by Vite,
21
+ // which rebuilds and hot-reloads them itself.
22
+ const serverSideKinds = [
23
+ 'agents',
24
+ 'connections',
25
+ 'notifications',
26
+ 'operators.server',
27
+ 'requests',
28
+ 'websockets',
29
+ 'auth.adapters',
30
+ 'auth.providers',
31
+ 'auth.strategies',
32
+ ];
33
+
34
+ function listServerSidePackages({ customTypesMap }) {
35
+ const packages = new Set();
36
+ for (const kind of serverSideKinds) {
37
+ const store = get(customTypesMap, kind, { default: {} });
38
+ for (const definition of Object.values(store ?? {})) {
39
+ if (type.isString(definition?.package)) {
40
+ packages.add(definition.package);
41
+ }
42
+ }
43
+ }
44
+ return packages;
45
+ }
46
+
47
+ export default listServerSidePackages;
@@ -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 listServerSidePackages from './listServerSidePackages.mjs';
18
+
19
+ test('listServerSidePackages lists packages with server-side types once each, not client-only ones', () => {
20
+ const customTypesMap = {
21
+ actions: { Do: { package: '@app/ui-plugin', version: '1.0.0' } },
22
+ auth: { providers: { Corp: { package: '@app/auth-plugin', version: '1.0.0' } } },
23
+ blocks: { Fancy: { package: '@app/mixed-plugin', version: '1.0.0' } },
24
+ operators: {
25
+ client: { _ui: { package: '@app/ui-plugin', version: '1.0.0' } },
26
+ server: { _srv: { package: '@app/mixed-plugin', version: '1.0.0' } },
27
+ },
28
+ requests: {
29
+ Find: { package: '@app/mixed-plugin', version: '1.0.0' },
30
+ Insert: { package: '@app/mixed-plugin', version: '1.0.0' },
31
+ },
32
+ };
33
+
34
+ expect([...listServerSidePackages({ customTypesMap })].sort()).toEqual([
35
+ '@app/auth-plugin',
36
+ '@app/mixed-plugin',
37
+ ]);
38
+ });
@@ -14,28 +14,60 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import fs from 'fs';
18
17
  import path from 'path';
18
+ import { type } from '@lowdefy/helpers';
19
+ import { relativePosixPath } from '@lowdefy/node-utils';
20
+
21
+ import createChangeSignal from '../utils/createChangeSignal.mjs';
22
+ import findBuildFilesOutsideWatch from '../utils/findBuildFilesOutsideWatch.mjs';
19
23
  import getLowdefyVersion from '../utils/getLowdefyVersion.mjs';
20
24
  import loadSkeletonSourceFiles from '../utils/loadSkeletonSourceFiles.mjs';
25
+ import pagePathChanged from '../utils/pagePathChanged.mjs';
21
26
  import setupWatcher from '../utils/setupWatcher.mjs';
22
27
  import updatePageTailwindCss from '../utils/updatePageTailwindCss.mjs';
23
28
 
24
- function lowdefyBuildWatcher(context) {
29
+ function findLocalModuleRoots(context) {
30
+ return Object.values(context.buildContext.modules ?? {})
31
+ .filter((moduleEntry) => moduleEntry.isLocal)
32
+ .map((moduleEntry) => moduleEntry.moduleRoot);
33
+ }
34
+
35
+ // Watches the config directory, the --watch directories and local module
36
+ // roots, and every other file the build reads (found in the build's ref maps).
37
+ // A change to a file that shapes the skeleton (lowdefy.yaml, a
38
+ // module.lowdefy.yaml, or a file in skeletonSourceFiles.json) rebuilds the
39
+ // config, as does a page file edit that changes the page's path, and any
40
+ // change after a failed config build. After every batch,
41
+ // the dev server is told files changed (build/invalidatePages), and checks the
42
+ // JIT-built pages against what they read.
43
+ async function lowdefyBuildWatcher(context) {
44
+ const configDirectory = context.directories.config;
45
+ const writeChangeSignal = createChangeSignal({ buildDirectory: context.directories.build });
25
46
  const fixRelativePathConfigDir = (item) =>
26
- path.isAbsolute(item) ? item : path.resolve(context.directories.config, item);
47
+ path.isAbsolute(item) ? item : path.resolve(configDirectory, item);
27
48
 
28
49
  const callback = async (filePaths) => {
29
- const changedFiles = filePaths
30
- .flat()
31
- .map((filePath) => path.relative(context.directories.config, filePath));
50
+ const changedFiles = filePaths.flat();
51
+ // Skeleton source files and the page registry hold app refs as their
52
+ // forward-slash _ref paths relative to the config directory, and module
53
+ // refs as native absolute paths, so each change is checked both ways.
54
+ const relativeChangedFiles = changedFiles.map((filePath) =>
55
+ relativePosixPath({ from: configDirectory, to: filePath })
56
+ );
32
57
 
33
- const lowdefyYamlModified = changedFiles.some(
58
+ const lowdefyYamlModified = relativeChangedFiles.some(
34
59
  (filePath) => filePath === 'lowdefy.yaml' || filePath === 'lowdefy.yml'
35
60
  );
36
61
  if (lowdefyYamlModified) {
37
- const lowdefyVersion = await getLowdefyVersion(context);
38
- if (lowdefyVersion !== context.version && lowdefyVersion !== 'local') {
62
+ // A lowdefy.yaml that cannot be read, such as one with a YAML syntax
63
+ // error, has no version to compare. The config build still runs, so the
64
+ // build status reports the error instead of the last build's result.
65
+ const lowdefyVersion = await getLowdefyVersion(context).catch(() => null);
66
+ if (
67
+ !type.isNone(lowdefyVersion) &&
68
+ lowdefyVersion !== context.version &&
69
+ lowdefyVersion !== 'local'
70
+ ) {
39
71
  context.shutdownServer();
40
72
  context.logger.warn('Lowdefy version changed. You should restart your development server.');
41
73
  process.exit();
@@ -44,37 +76,89 @@ function lowdefyBuildWatcher(context) {
44
76
 
45
77
  try {
46
78
  const skeletonSourceFiles = loadSkeletonSourceFiles(context.directories.build);
79
+ const moduleYamlModified = changedFiles.some(
80
+ (filePath) => path.basename(filePath) === 'module.lowdefy.yaml'
81
+ );
82
+ const skeletonFileModified = changedFiles.some(
83
+ (filePath, index) =>
84
+ skeletonSourceFiles.has(filePath) || skeletonSourceFiles.has(relativeChangedFiles[index])
85
+ );
47
86
 
48
- const isSkeletonChange =
87
+ // skeletonSourceFiles.json comes from the last build that succeeded, so it
88
+ // cannot list a file only the failed build read, such as a new endpoint
89
+ // file whose error is being fixed. While the last build failed, any
90
+ // change rebuilds the config.
91
+ if (
92
+ context.lastBuildFailed ||
49
93
  lowdefyYamlModified ||
50
- changedFiles.some((f) => skeletonSourceFiles.has(f));
51
-
52
- if (isSkeletonChange) {
94
+ moduleYamlModified ||
95
+ skeletonFileModified ||
96
+ (await pagePathChanged({
97
+ changedFiles: [...changedFiles, ...relativeChangedFiles],
98
+ context,
99
+ }))
100
+ ) {
53
101
  await context.lowdefyBuild();
102
+ // In this batch, so a build-status wait also waits for the restart
103
+ // the build needs (a new connection type, a new plugin package).
104
+ await context.syncServer();
54
105
  } else {
55
- const invalidatePath = path.join(context.directories.build, 'invalidatePages');
56
- fs.writeFileSync(invalidatePath, String(Date.now()));
57
- await updatePageTailwindCss({ changedFiles, context });
58
- context.logger.info('Page files changed, invalidated all pages.');
106
+ await updatePageTailwindCss({ changedFiles: relativeChangedFiles, context });
107
+ context.logger.info('Page files changed.');
59
108
  }
60
109
  } catch (error) {
61
110
  context.logger.error(error);
62
111
  } finally {
112
+ // Also after a config build, failed or not: a failed build publishes
113
+ // nothing, and the edit must still reach the pages it touched.
114
+ writeChangeSignal();
63
115
  await context.reloadClients();
64
116
  }
65
117
  };
66
- return setupWatcher({
118
+
119
+ const watchRoots = [
120
+ configDirectory,
121
+ ...context.options.watch.map(fixRelativePathConfigDir),
122
+ ...findLocalModuleRoots(context),
123
+ ];
124
+ const configWatcher = await setupWatcher({
67
125
  callback,
68
126
  context,
127
+ onBusy: context.buildActivity.setBusy,
69
128
  ignorePaths: [
70
129
  '**/node_modules/**',
71
130
  ...context.options.watchIgnore.map(fixRelativePathConfigDir),
72
131
  ],
73
- watchPaths: [
74
- context.directories.config,
75
- ...context.options.watch.map(fixRelativePathConfigDir),
76
- ],
132
+ watchPaths: watchRoots,
77
133
  });
134
+
135
+ // The config build writes refMap.json and each JIT page build writes a new
136
+ // file to jitMaps/, so the files they read outside the watched directories
137
+ // are added as they appear. Only the changed maps files are read.
138
+ const buildDirectory = context.directories.build;
139
+ const jitMapsDirectory = path.join(buildDirectory, 'jitMaps');
140
+ const watchBuildFilesOutsideWatch = (mapsFiles) => {
141
+ configWatcher.add(findBuildFilesOutsideWatch({ mapsFiles, configDirectory, watchRoots }));
142
+ };
143
+ watchBuildFilesOutsideWatch([path.join(buildDirectory, 'refMap.json')]);
144
+ const isMapsFile = (filePath) =>
145
+ filePath === buildDirectory ||
146
+ filePath === path.join(buildDirectory, 'refMap.json') ||
147
+ filePath === jitMapsDirectory ||
148
+ (path.dirname(filePath) === jitMapsDirectory && filePath.endsWith('.json'));
149
+ const mapsWatcher = await setupWatcher({
150
+ callback: (filePaths) => watchBuildFilesOutsideWatch([...new Set(filePaths.flat())]),
151
+ context,
152
+ // The build directory is watched rather than jitMaps/ itself, which does
153
+ // not exist until the first page build; everything else in it is ignored.
154
+ ignorePaths: [(filePath) => !isMapsFile(filePath)],
155
+ watchDotfiles: true,
156
+ watchPaths: [buildDirectory],
157
+ });
158
+
159
+ return {
160
+ close: () => Promise.all([configWatcher.close(), mapsWatcher.close()]),
161
+ };
78
162
  }
79
163
 
80
164
  export default lowdefyBuildWatcher;
@@ -0,0 +1,309 @@
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 spyOnChokidar from '../../test-utils/spyOnChokidar.mjs';
24
+ import waitFor from '../../test-utils/waitFor.mjs';
25
+
26
+ // The path each page file declares now, as resolvePagePath would read it. A
27
+ // page left out declares no path, so its path is its id.
28
+ const declaredPaths = new Map();
29
+ jest.unstable_mockModule('@lowdefy/build/dev', () => ({
30
+ resolvePagePath: jest.fn(async ({ pageId }) => declaredPaths.get(pageId) ?? pageId),
31
+ }));
32
+
33
+ const watchers = spyOnChokidar();
34
+ const { default: lowdefyBuildWatcher } = await import('./lowdefyBuildWatcher.mjs');
35
+
36
+ // macOS can hold file events back for tens of seconds on a busy machine.
37
+ jest.setTimeout(180000);
38
+
39
+ function write(filePath, content) {
40
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
41
+ fs.writeFileSync(filePath, content);
42
+ }
43
+
44
+ let repoDir;
45
+ let configDir;
46
+ let buildDir;
47
+ let localModuleRoot;
48
+ let context;
49
+ let watcher;
50
+
51
+ // The path a ref map records for an app _ref: as written in YAML, relative to
52
+ // the config directory with forward slashes on every platform.
53
+ function appRefPath(filePath) {
54
+ return path.relative(configDir, filePath).split(path.sep).join('/');
55
+ }
56
+
57
+ function invalidated() {
58
+ return fs.existsSync(path.join(buildDir, 'invalidatePages'));
59
+ }
60
+
61
+ // Starts the watcher under test and returns the chokidar watcher it watches
62
+ // the app with, the first one it creates.
63
+ async function startWatcher() {
64
+ const first = watchers.length;
65
+ watcher = await lowdefyBuildWatcher(context);
66
+ return watchers[first].watcher;
67
+ }
68
+
69
+ function isWatched({ configWatcher, filePath }) {
70
+ const names = configWatcher.getWatched()[path.dirname(filePath)] ?? [];
71
+ return names.includes(path.basename(filePath));
72
+ }
73
+
74
+ // A file added to a running watcher is listed as watched a moment before its
75
+ // event stream starts, so an edit made in that gap goes unseen. The file is
76
+ // edited again every two seconds - well past the watcher's batch delay, so an
77
+ // edit that was seen is processed before the next - until pages are
78
+ // invalidated. Only a watched file can invalidate them.
79
+ async function editUntilInvalidated(filePath) {
80
+ await waitFor(
81
+ () => {
82
+ if (invalidated()) return true;
83
+ fs.appendFileSync(filePath, 'type: Box\n');
84
+ return false;
85
+ },
86
+ { interval: 2000, description: `an edit to ${path.basename(filePath)} to invalidate pages` }
87
+ );
88
+ }
89
+
90
+ // The app lives in a git worktree under a dot-folder, like the worktrees
91
+ // Claude Code creates under .claude/worktrees/.
92
+ beforeEach(async () => {
93
+ const tmpDir = fs.realpathSync(os.tmpdir());
94
+ repoDir = fs.mkdtempSync(path.join(tmpDir, 'lowdefy-build-watcher-test-'));
95
+ const worktreeDir = path.join(repoDir, '.claude', 'worktrees', 'feature');
96
+ configDir = path.join(worktreeDir, 'app');
97
+ buildDir = path.join(configDir, '.lowdefy', 'server', 'build');
98
+ localModuleRoot = path.join(worktreeDir, 'modules', 'layout');
99
+
100
+ write(path.join(configDir, 'lowdefy.yaml'), 'lowdefy: local\n');
101
+ write(path.join(configDir, 'pages.yaml'), '- _ref: pages/home.yaml\n');
102
+ write(path.join(configDir, 'pages', 'home.yaml'), 'id: home\ntype: Box\n');
103
+ write(path.join(configDir, 'menus', 'main.yaml'), '- id: main\n');
104
+ write(path.join(localModuleRoot, 'module.lowdefy.yaml'), 'name: Layout\n');
105
+ write(path.join(localModuleRoot, 'menus.yaml'), '- id: main\n');
106
+ write(path.join(worktreeDir, 'modules', 'shared', 'title-block.yaml'), 'id: title\n');
107
+ write(
108
+ path.join(buildDir, 'skeletonSourceFiles.json'),
109
+ JSON.stringify(['menus/main.yaml', 'pages.yaml', path.join(localModuleRoot, 'menus.yaml')])
110
+ );
111
+ write(
112
+ path.join(buildDir, 'routes.json'),
113
+ JSON.stringify([{ pageId: 'home', path: 'home', auth: { public: true } }])
114
+ );
115
+ declaredPaths.clear();
116
+ write(
117
+ path.join(buildDir, 'refMap.json'),
118
+ JSON.stringify({
119
+ 1: { parent: null },
120
+ 2: { parent: '1', path: 'pages.yaml' },
121
+ 3: { parent: '2', path: 'pages/home.yaml' },
122
+ 4: { parent: null, path: path.join(localModuleRoot, 'components', 'page.yaml') },
123
+ 5: { parent: '4', path: path.join(worktreeDir, 'modules', 'shared', 'title-block.yaml') },
124
+ })
125
+ );
126
+ // Otherwise the fixture writes can reach the watcher as edits: a
127
+ // lowdefy.yaml "edit" turns every page edit into a config rebuild.
128
+ await flushFsEvents();
129
+
130
+ context = {
131
+ buildContext: {
132
+ modules: {
133
+ layout: { isLocal: true, moduleRoot: localModuleRoot },
134
+ remote: { isLocal: false, moduleRoot: path.join(repoDir, 'cache', 'remote') },
135
+ },
136
+ },
137
+ directories: {
138
+ build: buildDir,
139
+ config: configDir,
140
+ server: path.join(configDir, '.lowdefy', 'server'),
141
+ },
142
+ logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn() },
143
+ buildActivity: { setBusy: jest.fn() },
144
+ lowdefyBuild: jest.fn(async () => {}),
145
+ pageRegistry: new Map([['home', { pageId: 'home', refPath: 'pages/home.yaml' }]]),
146
+ options: { watch: [], watchIgnore: [] },
147
+ reloadClients: jest.fn(async () => {}),
148
+ syncServer: jest.fn(async () => {}),
149
+ version: 'local',
150
+ };
151
+ });
152
+
153
+ afterEach(async () => {
154
+ if (watcher) {
155
+ await watcher.close();
156
+ watcher = undefined;
157
+ }
158
+ fs.rmSync(repoDir, { recursive: true, force: true });
159
+ });
160
+
161
+ test('a page file edit in an app under a dot-folder invalidates pages', async () => {
162
+ await startWatcher();
163
+ fs.appendFileSync(path.join(configDir, 'pages', 'home.yaml'), 'layout: {}\n');
164
+
165
+ await waitFor(invalidated, { description: 'pages to be invalidated' });
166
+ await waitFor(() => context.reloadClients.mock.calls.length > 0, {
167
+ description: 'clients to reload',
168
+ });
169
+ expect(context.lowdefyBuild).not.toHaveBeenCalled();
170
+ expect(context.syncServer).not.toHaveBeenCalled();
171
+ });
172
+
173
+ test('a page file edit that changes the page path rebuilds the config', async () => {
174
+ await startWatcher();
175
+ declaredPaths.set('home', 'start');
176
+ fs.appendFileSync(path.join(configDir, 'pages', 'home.yaml'), 'path: start\n');
177
+
178
+ await waitFor(() => context.lowdefyBuild.mock.calls.length === 1, {
179
+ description: 'a config build',
180
+ });
181
+ await waitFor(invalidated, { description: 'the pages to be invalidated' });
182
+ });
183
+
184
+ test('adding a page to the pages list rebuilds the config', async () => {
185
+ await startWatcher();
186
+ fs.appendFileSync(path.join(configDir, 'pages.yaml'), '- _ref: pages/about.yaml\n');
187
+
188
+ await waitFor(() => context.lowdefyBuild.mock.calls.length === 1, {
189
+ description: 'a config build',
190
+ });
191
+ await waitFor(invalidated, { description: 'the pages to be invalidated' });
192
+ });
193
+
194
+ test('a config rebuild stays busy until the server has caught up with the build', async () => {
195
+ let finishSync;
196
+ context.syncServer.mockImplementation(
197
+ () =>
198
+ new Promise((resolve) => {
199
+ finishSync = resolve;
200
+ })
201
+ );
202
+ await startWatcher();
203
+ fs.appendFileSync(path.join(configDir, 'pages.yaml'), '- _ref: pages/about.yaml\n');
204
+
205
+ await waitFor(() => context.syncServer.mock.calls.length === 1, {
206
+ description: 'the server sync',
207
+ });
208
+ expect(context.buildActivity.setBusy.mock.calls).toEqual([[true]]);
209
+ finishSync();
210
+ await waitFor(() => context.buildActivity.setBusy.mock.calls.length === 2, {
211
+ description: 'the watcher to report it is idle',
212
+ });
213
+ expect(context.buildActivity.setBusy.mock.calls).toEqual([[true], [false]]);
214
+ });
215
+
216
+ test('a lowdefy.yaml edit that breaks its YAML syntax still rebuilds the config', async () => {
217
+ await startWatcher();
218
+ write(path.join(configDir, 'lowdefy.yaml'), 'lowdefy: local\npages:\n - id: a\n - id: b\n');
219
+
220
+ await waitFor(() => context.lowdefyBuild.mock.calls.length === 1, {
221
+ description: 'a config build',
222
+ });
223
+ expect(context.logger.error).not.toHaveBeenCalled();
224
+ });
225
+
226
+ test('after a failed config build, an edit to a file not in skeletonSourceFiles rebuilds the config', async () => {
227
+ context.lastBuildFailed = true;
228
+ await startWatcher();
229
+ write(path.join(localModuleRoot, 'api', 'check-name.yaml'), 'id: check-name\n');
230
+
231
+ await waitFor(() => context.lowdefyBuild.mock.calls.length === 1, {
232
+ description: 'a config build',
233
+ });
234
+ await waitFor(invalidated, { description: 'the pages to be invalidated' });
235
+ });
236
+
237
+ test('a skeleton file in a subdirectory of the config directory rebuilds the config', async () => {
238
+ await startWatcher();
239
+ fs.appendFileSync(path.join(configDir, 'menus', 'main.yaml'), '- id: other\n');
240
+
241
+ await waitFor(() => context.lowdefyBuild.mock.calls.length === 1, {
242
+ description: 'a config build',
243
+ });
244
+ });
245
+
246
+ test('a skeleton file in a local module outside the config directory rebuilds the config', async () => {
247
+ await startWatcher();
248
+ fs.appendFileSync(path.join(localModuleRoot, 'menus.yaml'), '- id: other\n');
249
+
250
+ await waitFor(() => context.lowdefyBuild.mock.calls.length === 1, {
251
+ description: 'a config build',
252
+ });
253
+ });
254
+
255
+ test('a file the build read outside every watched directory invalidates pages when edited', async () => {
256
+ const titleBlock = path.join(
257
+ repoDir,
258
+ '.claude',
259
+ 'worktrees',
260
+ 'feature',
261
+ 'modules',
262
+ 'shared',
263
+ 'title-block.yaml'
264
+ );
265
+ const configWatcher = await startWatcher();
266
+
267
+ await waitFor(() => isWatched({ configWatcher, filePath: titleBlock }), {
268
+ description: 'title-block.yaml to be watched',
269
+ });
270
+ await editUntilInvalidated(titleBlock);
271
+ expect(context.lowdefyBuild).not.toHaveBeenCalled();
272
+ });
273
+
274
+ test('a file that appears in refMap.json after the watch started is watched', async () => {
275
+ const footer = path.join(repoDir, 'shared', 'footer.yaml');
276
+ write(footer, 'id: footer\n');
277
+ await flushFsEvents();
278
+ const configWatcher = await startWatcher();
279
+ expect(isWatched({ configWatcher, filePath: footer })).toBe(false);
280
+
281
+ const refMap = JSON.parse(fs.readFileSync(path.join(buildDir, 'refMap.json'), 'utf8'));
282
+ refMap[6] = { parent: '3', path: appRefPath(footer) };
283
+ fs.writeFileSync(path.join(buildDir, 'refMap.json'), JSON.stringify(refMap));
284
+
285
+ // The refMap watcher batches its change before adding the file.
286
+ await waitFor(() => isWatched({ configWatcher, filePath: footer }), {
287
+ description: 'footer.yaml to be watched',
288
+ });
289
+ await editUntilInvalidated(footer);
290
+ });
291
+
292
+ test('a file a JIT page build read outside every watched directory is watched from its jitMaps file', async () => {
293
+ const header = path.join(repoDir, 'shared', 'header.yaml');
294
+ write(header, 'id: header\n');
295
+ watcher = await lowdefyBuildWatcher(context);
296
+
297
+ write(
298
+ path.join(buildDir, 'jitMaps', 'abc123-1-1.json'),
299
+ JSON.stringify({
300
+ keyMap: {},
301
+ refMap: { p_abc123_1: { parent: null, path: appRefPath(header) } },
302
+ })
303
+ );
304
+ // The maps watcher batches its change for half a second before adding.
305
+ await new Promise((resolve) => setTimeout(resolve, 1500));
306
+ fs.appendFileSync(header, 'type: Box\n');
307
+
308
+ await waitFor(invalidated);
309
+ });