@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,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
+
21
+ import findBuildFilesOutsideWatch from './findBuildFilesOutsideWatch.mjs';
22
+
23
+ let root;
24
+ let buildDirectory;
25
+ let configDirectory;
26
+
27
+ function writeRefMap(refMap) {
28
+ fs.writeFileSync(path.join(buildDirectory, 'refMap.json'), JSON.stringify(refMap));
29
+ return path.join(buildDirectory, 'refMap.json');
30
+ }
31
+
32
+ function writeJitMaps(name, refMap) {
33
+ const filePath = path.join(buildDirectory, 'jitMaps', name);
34
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
35
+ fs.writeFileSync(filePath, JSON.stringify({ keyMap: {}, refMap }));
36
+ return filePath;
37
+ }
38
+
39
+ beforeEach(() => {
40
+ root = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-build-files-'));
41
+ buildDirectory = path.join(root, 'app', '.lowdefy', 'server', 'build');
42
+ configDirectory = path.join(root, 'app');
43
+ fs.mkdirSync(buildDirectory, { recursive: true });
44
+ });
45
+
46
+ afterEach(() => {
47
+ fs.rmSync(root, { recursive: true, force: true });
48
+ });
49
+
50
+ test('findBuildFilesOutsideWatch returns files the build read outside the watched directories', () => {
51
+ const refMapFile = writeRefMap({
52
+ 1: { parent: null },
53
+ 2: { parent: '1', path: 'pages.yaml' },
54
+ 3: { parent: '2', path: '../shared/header.yaml' },
55
+ 4: { parent: null, path: path.join(root, 'modules', 'layout', 'components', 'page.yaml') },
56
+ 5: { parent: '4', path: path.join(root, 'modules', 'shared', 'title-block.yaml') },
57
+ 6: { parent: '4', path: path.join(root, 'modules', 'shared', 'title-block.yaml') },
58
+ });
59
+
60
+ const files = findBuildFilesOutsideWatch({
61
+ mapsFiles: [refMapFile],
62
+ configDirectory,
63
+ watchRoots: [configDirectory, path.join(root, 'modules', 'layout')],
64
+ });
65
+
66
+ expect(files).toEqual([
67
+ path.join(root, 'shared', 'header.yaml'),
68
+ path.join(root, 'modules', 'shared', 'title-block.yaml'),
69
+ ]);
70
+ });
71
+
72
+ test('findBuildFilesOutsideWatch skips refs without a file path', () => {
73
+ const refMapFile = writeRefMap({
74
+ 1: { parent: null },
75
+ 2: { parent: '1', path: null, original: { resolver: 'resolver.js' } },
76
+ });
77
+
78
+ const files = findBuildFilesOutsideWatch({
79
+ mapsFiles: [refMapFile],
80
+ configDirectory,
81
+ watchRoots: [configDirectory],
82
+ });
83
+
84
+ expect(files).toEqual([]);
85
+ });
86
+
87
+ test("findBuildFilesOutsideWatch reads a JIT page build's refs from its jitMaps file alone", () => {
88
+ writeRefMap({ 1: { parent: null, path: '../shared/skeleton.yaml' } });
89
+ const jitMapsFile = writeJitMaps('abc123-1-1.json', {
90
+ p_abc123_1: { parent: null, path: 'pages/home.yaml' },
91
+ p_abc123_2: { parent: 'p_abc123_1', path: '../shared/header.yaml' },
92
+ });
93
+
94
+ const files = findBuildFilesOutsideWatch({
95
+ mapsFiles: [jitMapsFile],
96
+ configDirectory,
97
+ watchRoots: [configDirectory],
98
+ });
99
+
100
+ expect(files).toEqual([path.join(root, 'shared', 'header.yaml')]);
101
+ });
102
+
103
+ test('findBuildFilesOutsideWatch skips a jitMaps file pruned before it was read', () => {
104
+ const files = findBuildFilesOutsideWatch({
105
+ mapsFiles: [path.join(buildDirectory, 'jitMaps', 'gone-1-1.json')],
106
+ configDirectory,
107
+ watchRoots: [configDirectory],
108
+ });
109
+
110
+ expect(files).toEqual([]);
111
+ });
@@ -0,0 +1,46 @@
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 { pathToFileURL } from 'node:url';
18
+ import { Worker } from 'node:worker_threads';
19
+
20
+ // Node keeps every module it loads for the life of the process, and an ES
21
+ // module can not be dropped from that cache, so importing a changed file again
22
+ // in the manager returns the old module and the old modules it imports. A
23
+ // plugin's type list is read from its built files, which change while the dev
24
+ // server runs (a new block, request or operator type), so each read imports
25
+ // the file in a new worker thread, which starts with an empty module cache.
26
+ // Resolves to the module's default export (its namespace if it has none),
27
+ // copied to the manager as structured-clone data.
28
+ function importFresh(filePath) {
29
+ return new Promise((resolve, reject) => {
30
+ const worker = new Worker(new URL('./importFreshWorker.mjs', import.meta.url), {
31
+ workerData: { url: pathToFileURL(filePath).href },
32
+ });
33
+ worker.once('message', (value) => {
34
+ resolve(value);
35
+ worker.terminate();
36
+ });
37
+ worker.once('error', reject);
38
+ worker.once('exit', (code) => {
39
+ if (code !== 0) {
40
+ reject(new Error(`Importing "${filePath}" stopped with exit code ${code}.`));
41
+ }
42
+ });
43
+ });
44
+ }
45
+
46
+ export default importFresh;
@@ -0,0 +1,72 @@
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 importFresh from './importFresh.mjs';
22
+
23
+ let pluginDir;
24
+
25
+ beforeEach(() => {
26
+ pluginDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-import-fresh-'));
27
+ fs.writeFileSync(path.join(pluginDir, 'package.json'), JSON.stringify({ type: 'module' }));
28
+ fs.writeFileSync(
29
+ path.join(pluginDir, 'types.js'),
30
+ "import * as metas from './metas.js';\nexport default { blocks: Object.keys(metas) };\n"
31
+ );
32
+ });
33
+
34
+ afterEach(() => {
35
+ fs.rmSync(pluginDir, { recursive: true, force: true });
36
+ });
37
+
38
+ test('importFresh returns the default export of the module', async () => {
39
+ fs.writeFileSync(path.join(pluginDir, 'metas.js'), 'export const Card = {};\n');
40
+
41
+ const types = await importFresh(path.join(pluginDir, 'types.js'));
42
+
43
+ expect(types).toEqual({ blocks: ['Card'] });
44
+ });
45
+
46
+ test('importFresh sees a change to a module the imported file imports', async () => {
47
+ fs.writeFileSync(path.join(pluginDir, 'metas.js'), 'export const Card = {};\n');
48
+ const before = await importFresh(path.join(pluginDir, 'types.js'));
49
+
50
+ fs.writeFileSync(
51
+ path.join(pluginDir, 'metas.js'),
52
+ 'export const Card = {};\nexport const TaskList = {};\n'
53
+ );
54
+ const after = await importFresh(path.join(pluginDir, 'types.js'));
55
+
56
+ expect(before).toEqual({ blocks: ['Card'] });
57
+ expect(after).toEqual({ blocks: ['Card', 'TaskList'] });
58
+ });
59
+
60
+ test('importFresh returns the namespace of a module without a default export', async () => {
61
+ fs.writeFileSync(path.join(pluginDir, 'messages.js'), "export const hello = 'Hello';\n");
62
+
63
+ const messages = await importFresh(path.join(pluginDir, 'messages.js'));
64
+
65
+ expect(messages).toEqual({ hello: 'Hello' });
66
+ });
67
+
68
+ test('importFresh rejects when the module throws', async () => {
69
+ fs.writeFileSync(path.join(pluginDir, 'broken.js'), "throw new Error('Broken plugin.');\n");
70
+
71
+ await expect(importFresh(path.join(pluginDir, 'broken.js'))).rejects.toThrow('Broken plugin.');
72
+ });
@@ -0,0 +1,20 @@
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 { parentPort, workerData } from 'node:worker_threads';
18
+
19
+ const module = await import(workerData.url);
20
+ parentPort.postMessage(module.default ?? { ...module });
@@ -0,0 +1,35 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { spawn } from 'child_process';
18
+
19
+ // Kills the browser a Vite child launched, found by the tag the manager gave
20
+ // that child (see launchBrowser.js). A child that exits normally already
21
+ // takes its browser with it; this is for one killed outright, whose system
22
+ // Chrome runs in its own process group and is left running. Chrome's helper
23
+ // processes do not carry the tag; they exit with the browser process.
24
+ function killTaggedBrowser({ tag }) {
25
+ // Windows has no pkill. Best effort, as for the other process clean-up:
26
+ // the headless shell, the default browser, exits with its parent anyway.
27
+ if (process.platform === 'win32') {
28
+ return;
29
+ }
30
+ // pkill exits 1 when nothing matched, the usual case.
31
+ const pkill = spawn('pkill', ['-f', '--', `--lowdefy-browser-tag=${tag}`], { stdio: 'ignore' });
32
+ pkill.on('error', () => {});
33
+ }
34
+
35
+ export default killTaggedBrowser;
@@ -0,0 +1,56 @@
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 { resolvePagePath } from '@lowdefy/build/dev';
20
+
21
+ // Whether a changed page file now declares a path other than the one its page
22
+ // has in routes.json (adding or removing a path included). Page files are not
23
+ // skeleton sources, so without this the old URL would keep matching and the
24
+ // new one would 404 until the config is rebuilt.
25
+ async function pagePathChanged({ changedFiles, context }) {
26
+ const changed = new Set(changedFiles);
27
+ const pageIds = [...context.pageRegistry.values()]
28
+ .filter((pageEntry) => changed.has(pageEntry.refPath))
29
+ .map((pageEntry) => pageEntry.pageId);
30
+ if (pageIds.length === 0) {
31
+ return false;
32
+ }
33
+ const routes = JSON.parse(
34
+ fs.readFileSync(path.join(context.directories.build, 'routes.json'), 'utf8')
35
+ );
36
+ const routePaths = new Map(routes.map((route) => [route.pageId, route.path]));
37
+ for (const pageId of pageIds) {
38
+ let pagePath;
39
+ try {
40
+ pagePath = await resolvePagePath({
41
+ pageId,
42
+ pageRegistry: context.pageRegistry,
43
+ context: context.buildContext,
44
+ });
45
+ } catch {
46
+ // A page file that does not resolve is reported by its page build.
47
+ continue;
48
+ }
49
+ if (pagePath !== routePaths.get(pageId)) {
50
+ return true;
51
+ }
52
+ }
53
+ return false;
54
+ }
55
+
56
+ export default pagePathChanged;
@@ -0,0 +1,85 @@
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
+ // Only what the dev server records. traces/production/ is the production
21
+ // mining cache, pruned by its own command on its own schedule.
22
+ const PRUNED_SOURCES = ['dev', 'journey'];
23
+ const DATE_DIRECTORY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
24
+ const DAY_MS = 24 * 60 * 60 * 1000;
25
+
26
+ function listDateDirectories({ tracesDirectory }) {
27
+ return PRUNED_SOURCES.flatMap((source) => {
28
+ const sourceDirectory = path.join(tracesDirectory, source);
29
+ if (!fs.existsSync(sourceDirectory)) return [];
30
+ return fs
31
+ .readdirSync(sourceDirectory, { withFileTypes: true })
32
+ .filter((entry) => entry.isDirectory() && DATE_DIRECTORY_PATTERN.test(entry.name))
33
+ .map((entry) => ({ date: entry.name, path: path.join(sourceDirectory, entry.name) }));
34
+ });
35
+ }
36
+
37
+ function listTraceFiles({ dateDirectories }) {
38
+ return dateDirectories.flatMap((directory) =>
39
+ fs
40
+ .readdirSync(directory.path, { withFileTypes: true })
41
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl'))
42
+ .map((entry) => {
43
+ const filePath = path.join(directory.path, entry.name);
44
+ const stat = fs.statSync(filePath);
45
+ return { path: filePath, size: stat.size, mtimeMs: stat.mtimeMs };
46
+ })
47
+ );
48
+ }
49
+
50
+ // Recordings are kept 7 days or 200 MB, whichever is smaller. Date
51
+ // directories older than maxAgeDays go first, then the oldest files by mtime
52
+ // until the rest fit, never the newest file (the tab recording right now).
53
+ function pruneRecordings({
54
+ configDirectory,
55
+ now = Date.now(),
56
+ maxAgeDays = 7,
57
+ maxBytes = 200 * 1024 * 1024,
58
+ }) {
59
+ const tracesDirectory = path.join(configDirectory, '.lowdefy', 'traces');
60
+ const cutoff = new Date(now - maxAgeDays * DAY_MS).toISOString().slice(0, 10);
61
+ const deleted = { directories: [], files: [] };
62
+
63
+ const dateDirectories = listDateDirectories({ tracesDirectory });
64
+ const kept = [];
65
+ dateDirectories.forEach((directory) => {
66
+ if (directory.date < cutoff) {
67
+ fs.rmSync(directory.path, { recursive: true, force: true });
68
+ deleted.directories.push(directory.path);
69
+ return;
70
+ }
71
+ kept.push(directory);
72
+ });
73
+
74
+ const files = listTraceFiles({ dateDirectories: kept }).sort((a, b) => a.mtimeMs - b.mtimeMs);
75
+ let total = files.reduce((sum, file) => sum + file.size, 0);
76
+ for (const file of files.slice(0, -1)) {
77
+ if (total <= maxBytes) break;
78
+ fs.rmSync(file.path, { force: true });
79
+ deleted.files.push(file.path);
80
+ total -= file.size;
81
+ }
82
+ return deleted;
83
+ }
84
+
85
+ export default pruneRecordings;
@@ -0,0 +1,117 @@
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 pruneRecordings from './pruneRecordings.mjs';
22
+
23
+ const NOW = Date.parse('2026-10-03T12:00:00Z');
24
+ let configDirectory;
25
+
26
+ function tracePath(...segments) {
27
+ return path.join(configDirectory, '.lowdefy', 'traces', ...segments);
28
+ }
29
+
30
+ function writeFile({ segments, size = 10, mtimeMs = NOW }) {
31
+ const filePath = tracePath(...segments);
32
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
33
+ fs.writeFileSync(filePath, 'x'.repeat(size));
34
+ fs.utimesSync(filePath, mtimeMs / 1000, mtimeMs / 1000);
35
+ return filePath;
36
+ }
37
+
38
+ beforeEach(() => {
39
+ configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-prune-'));
40
+ });
41
+
42
+ afterEach(() => {
43
+ fs.rmSync(configDirectory, { recursive: true, force: true });
44
+ });
45
+
46
+ test('pruneRecordings deletes date directories older than 7 days under dev and journey', () => {
47
+ ['dev', 'journey'].forEach((source) => {
48
+ writeFile({ segments: [source, '2026-09-25', '20260925T100000Z-old000.jsonl'] });
49
+ writeFile({ segments: [source, '2026-09-26', '20260926T100000Z-keep00.jsonl'] });
50
+ });
51
+ const deleted = pruneRecordings({ configDirectory, now: NOW });
52
+ expect(deleted.directories).toHaveLength(2);
53
+ ['dev', 'journey'].forEach((source) => {
54
+ expect(fs.existsSync(tracePath(source, '2026-09-25'))).toBe(false);
55
+ expect(fs.existsSync(tracePath(source, '2026-09-26', '20260926T100000Z-keep00.jsonl'))).toBe(
56
+ true
57
+ );
58
+ });
59
+ });
60
+
61
+ test('pruneRecordings trims the oldest files by mtime until the rest fit, keeping the newest', () => {
62
+ const oldest = writeFile({
63
+ segments: ['dev', '2026-10-03', '20261003T080000Z-aaaaaa.jsonl'],
64
+ size: 40,
65
+ mtimeMs: NOW - 3000,
66
+ });
67
+ const middle = writeFile({
68
+ segments: ['journey', '2026-10-03', '20261003T090000Z-bbbbbb.jsonl'],
69
+ size: 40,
70
+ mtimeMs: NOW - 2000,
71
+ });
72
+ const newest = writeFile({
73
+ segments: ['dev', '2026-10-03', '20261003T100000Z-cccccc.jsonl'],
74
+ size: 40,
75
+ mtimeMs: NOW - 1000,
76
+ });
77
+ const deleted = pruneRecordings({ configDirectory, now: NOW, maxBytes: 90 });
78
+ expect(deleted.files).toEqual([oldest]);
79
+ expect(fs.existsSync(middle)).toBe(true);
80
+ expect(fs.existsSync(newest)).toBe(true);
81
+ });
82
+
83
+ test('pruneRecordings never deletes the newest file, even when it alone is over the limit', () => {
84
+ const older = writeFile({
85
+ segments: ['dev', '2026-10-03', '20261003T080000Z-aaaaaa.jsonl'],
86
+ size: 50,
87
+ mtimeMs: NOW - 2000,
88
+ });
89
+ const newest = writeFile({
90
+ segments: ['dev', '2026-10-03', '20261003T100000Z-cccccc.jsonl'],
91
+ size: 200,
92
+ mtimeMs: NOW - 1000,
93
+ });
94
+ pruneRecordings({ configDirectory, now: NOW, maxBytes: 100 });
95
+ expect(fs.existsSync(older)).toBe(false);
96
+ expect(fs.existsSync(newest)).toBe(true);
97
+ });
98
+
99
+ test('pruneRecordings leaves foreign files and the production cache alone', () => {
100
+ const production = writeFile({
101
+ segments: ['production', '2026-01-01', 'events.jsonl'],
102
+ size: 500,
103
+ });
104
+ const foreignDirectory = writeFile({ segments: ['dev', 'scratch', 'notes.jsonl'], size: 500 });
105
+ const foreignFile = writeFile({ segments: ['dev', '2026-10-03', 'notes.txt'], size: 500 });
106
+ const loose = writeFile({ segments: ['dev', 'old.jsonl'], size: 500, mtimeMs: NOW - 99999 });
107
+ writeFile({ segments: ['dev', '2026-10-03', '20261003T100000Z-cccccc.jsonl'], size: 10 });
108
+ const deleted = pruneRecordings({ configDirectory, now: NOW, maxBytes: 1 });
109
+ expect(deleted).toEqual({ directories: [], files: [] });
110
+ [production, foreignDirectory, foreignFile, loose].forEach((filePath) => {
111
+ expect(fs.existsSync(filePath)).toBe(true);
112
+ });
113
+ });
114
+
115
+ test('pruneRecordings does nothing when there are no traces', () => {
116
+ expect(pruneRecordings({ configDirectory, now: NOW })).toEqual({ directories: [], files: [] });
117
+ });
@@ -0,0 +1,121 @@
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/promises';
18
+ import path from 'path';
19
+
20
+ import filesHaveSameBytes from './filesHaveSameBytes.mjs';
21
+
22
+ async function listFiles(directory) {
23
+ const entries = await fs.readdir(directory, { recursive: true, withFileTypes: true });
24
+ return entries
25
+ .filter((entry) => entry.isFile())
26
+ .map((entry) => path.relative(directory, path.join(entry.parentPath, entry.name)));
27
+ }
28
+
29
+ // The dev server reads build artifacts from disk on every request. Emptying
30
+ // the build directory and writing the new build into it leaves a window where
31
+ // artifacts are missing, so the build is written to a staging directory and
32
+ // moved over the live one file by file. A rename replaces a file in one step,
33
+ // so a reader always finds each artifact, from the old build or the new one.
34
+ // A staged file whose bytes equal the live one is left in staging, so the
35
+ // live file keeps its mtime and Vite, which watches the build directory, does
36
+ // not re-process every client artifact after each config build. Live files
37
+ // the new build did not write are removed next. The page registry
38
+ // is moved last: the JIT page builder rebuilds its cached build context, and
39
+ // drops its built pages, when the registry changes, so the registry must
40
+ // arrive after every other new file and after the old build's pages are gone.
41
+ // It is moved even when its bytes are unchanged: the JIT page builder learns
42
+ // of a config build only from its identity (inode and mtime), which a rename
43
+ // always changes.
44
+ // The live directory itself is never replaced, so file watchers on it keep
45
+ // working.
46
+ const pageRegistryFile = 'pageRegistry.json';
47
+
48
+ // idCounter.json is moved first. A JIT page build writes keyMap.json and refMap.json
49
+ // only while the live idCounter.json still names the config build it started from
50
+ // (skipStaleMapWrites), so the new idCounter.json must be live before the new maps
51
+ // arrive, or a page build of the old config could write its maps over them.
52
+ // It is moved even when its bytes are unchanged, so the order holds for every
53
+ // build.
54
+ const idCounterFile = 'idCounter.json';
55
+
56
+ // Files the manager and the dev tools write into the build directory to
57
+ // signal each other. The build does not write them, so they are never in the
58
+ // staged build, and removing them as stale would drop the signal: a restart
59
+ // request (.restart), a page invalidation or client reload queued behind the
60
+ // build, or the build status the dev tools read.
61
+ const controlFiles = new Set(['.restart', 'buildStatus.json', 'invalidatePages', 'reload']);
62
+
63
+ // On Windows a rename over a file another process has open (the dev server
64
+ // reading it, a virus scanner, the search indexer) fails for a moment with
65
+ // EPERM, EBUSY or EACCES, so each rename is retried for a few seconds before
66
+ // the publish fails.
67
+ const LOCKED_CODES = new Set(['EACCES', 'EBUSY', 'EPERM']);
68
+ const RENAME_DELAYS_MS = [25, 50, 100, 200, 400, 500, 500, 500, 500, 500];
69
+ const RM_OPTIONS = { force: true, recursive: true, maxRetries: 5 };
70
+
71
+ async function renameWithRetry({ from, to }) {
72
+ for (let attempt = 0; ; attempt += 1) {
73
+ try {
74
+ await fs.rename(from, to);
75
+ return;
76
+ } catch (error) {
77
+ if (!LOCKED_CODES.has(error.code) || attempt === RENAME_DELAYS_MS.length) {
78
+ throw error;
79
+ }
80
+ await new Promise((resolve) => setTimeout(resolve, RENAME_DELAYS_MS[attempt]));
81
+ }
82
+ }
83
+ }
84
+
85
+ async function moveFile({ buildDirectory, stagingDirectory, file }) {
86
+ const target = path.join(buildDirectory, file);
87
+ await fs.mkdir(path.dirname(target), { recursive: true });
88
+ await renameWithRetry({ from: path.join(stagingDirectory, file), to: target });
89
+ }
90
+
91
+ async function publishBuildDirectory({ buildDirectory, stagingDirectory }) {
92
+ await fs.mkdir(buildDirectory, { recursive: true });
93
+ const stagedFiles = await listFiles(stagingDirectory);
94
+ const liveFiles = await listFiles(buildDirectory);
95
+
96
+ const firstFiles = stagedFiles.filter((file) => file === idCounterFile);
97
+ const otherFiles = stagedFiles.filter(
98
+ (file) => file !== idCounterFile && file !== pageRegistryFile
99
+ );
100
+ for (const file of firstFiles) {
101
+ await moveFile({ buildDirectory, stagingDirectory, file });
102
+ }
103
+ for (const file of otherFiles) {
104
+ const unchanged = await filesHaveSameBytes({
105
+ a: path.join(stagingDirectory, file),
106
+ b: path.join(buildDirectory, file),
107
+ });
108
+ if (!unchanged) {
109
+ await moveFile({ buildDirectory, stagingDirectory, file });
110
+ }
111
+ }
112
+
113
+ const staged = new Set(stagedFiles);
114
+ const staleFiles = liveFiles.filter((file) => !staged.has(file) && !controlFiles.has(file));
115
+ await Promise.all(staleFiles.map((file) => fs.rm(path.join(buildDirectory, file), RM_OPTIONS)));
116
+
117
+ await moveFile({ buildDirectory, stagingDirectory, file: pageRegistryFile });
118
+ await fs.rm(stagingDirectory, RM_OPTIONS);
119
+ }
120
+
121
+ export default publishBuildDirectory;