@lowdefy/server-dev 6.1.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 (623) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +2 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +222 -36
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +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/{getAuthConfig.js → getAuth.js} +15 -9
  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/{session.js → getStrategies.js} +17 -16
  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 +168 -41
  348. package/lib/server/createLowdefyContext.test.mjs +328 -8
  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 +15 -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/sinkExternalConnections.js +95 -0
  428. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  429. package/lib/server/skipStaleMapWrites.js +62 -0
  430. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  431. package/lib/server/startIdleGc.js +67 -0
  432. package/lib/server/startIdleGc.test.mjs +138 -0
  433. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  434. package/lib/server/trackSessionWork.js +30 -0
  435. package/lib/vite/createDevServerExclude.js +40 -0
  436. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  437. package/manager/getContext.mjs +24 -2
  438. package/manager/processes/initialBuild.mjs +14 -0
  439. package/manager/processes/initialBuild.test.mjs +101 -0
  440. package/manager/processes/lowdefyBuild.mjs +30 -7
  441. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
  442. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  443. package/manager/processes/optimizeDependencies.mjs +59 -0
  444. package/manager/processes/restartServer.mjs +34 -6
  445. package/manager/processes/restartServer.test.mjs +162 -0
  446. package/manager/processes/shutdownServer.mjs +11 -0
  447. package/manager/processes/shutdownServer.test.mjs +45 -0
  448. package/manager/processes/startFirstServer.mjs +46 -0
  449. package/manager/processes/startFirstServer.test.mjs +326 -0
  450. package/manager/processes/startMailSink.mjs +135 -0
  451. package/manager/processes/startMailSink.test.mjs +146 -0
  452. package/manager/processes/startProxy.mjs +129 -12
  453. package/manager/processes/startProxy.test.mjs +366 -0
  454. package/manager/processes/startServer.mjs +47 -6
  455. package/manager/processes/startServer.test.mjs +155 -0
  456. package/manager/processes/startWatchers.mjs +6 -4
  457. package/manager/processes/syncServer.mjs +69 -0
  458. package/manager/processes/syncServer.test.mjs +141 -0
  459. package/manager/processes/waitForDevServer.mjs +48 -0
  460. package/manager/processes/waitForDevServer.test.mjs +112 -0
  461. package/manager/run.mjs +128 -43
  462. package/manager/utils/BatchChanges.mjs +21 -5
  463. package/manager/utils/BatchChanges.test.mjs +77 -0
  464. package/manager/utils/acquireDevInstance.mjs +99 -0
  465. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  466. package/manager/utils/createBuildActivity.mjs +67 -0
  467. package/manager/utils/createBuildActivity.test.mjs +58 -0
  468. package/manager/utils/createChangeSignal.mjs +39 -0
  469. package/manager/utils/createChangeSignal.test.mjs +62 -0
  470. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  471. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  472. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  473. package/manager/utils/createDotPathIgnore.mjs +44 -0
  474. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  475. package/manager/utils/createIdleGc.mjs +51 -0
  476. package/manager/utils/createIdleGc.test.mjs +77 -0
  477. package/manager/utils/createRequestActivity.mjs +100 -0
  478. package/manager/utils/createRequestActivity.test.mjs +121 -0
  479. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  480. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  481. package/manager/utils/createServerEnv.mjs +39 -0
  482. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  483. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  485. package/manager/utils/importFresh.mjs +46 -0
  486. package/manager/utils/importFresh.test.mjs +72 -0
  487. package/manager/utils/importFreshWorker.mjs +20 -0
  488. package/manager/utils/killTaggedBrowser.mjs +35 -0
  489. package/manager/utils/pagePathChanged.mjs +56 -0
  490. package/manager/utils/pruneRecordings.mjs +85 -0
  491. package/manager/utils/pruneRecordings.test.mjs +117 -0
  492. package/manager/utils/publishBuildDirectory.mjs +121 -0
  493. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  494. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  495. package/manager/utils/readBasePath.mjs +27 -0
  496. package/manager/utils/readBasePath.test.mjs +43 -0
  497. package/manager/utils/readBuildStatusWait.mjs +67 -0
  498. package/manager/utils/readManagerVersion.mjs +30 -0
  499. package/manager/utils/readManagerVersion.test.mjs +35 -0
  500. package/manager/utils/readPluginDefinitions.mjs +36 -0
  501. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  502. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  503. package/manager/utils/resolvePorts.mjs +49 -0
  504. package/manager/utils/setupWatcher.mjs +19 -11
  505. package/manager/utils/startRecordingPruner.mjs +45 -0
  506. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  507. package/manager/utils/waitForChildExit.mjs +53 -0
  508. package/manager/utils/waitForChildExit.test.mjs +70 -0
  509. package/manager/utils/waitForServer.mjs +44 -0
  510. package/manager/utils/writeBuildStatus.mjs +1 -1
  511. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  512. package/manager/watchers/envWatcher.mjs +9 -2
  513. package/manager/watchers/envWatcher.test.mjs +87 -0
  514. package/manager/watchers/listServerSidePackages.mjs +47 -0
  515. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  516. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  517. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  518. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  519. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  520. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  521. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  522. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  523. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  524. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  525. package/package.json +60 -47
  526. package/package.original.json +60 -47
  527. package/src/app.js +131 -20
  528. package/src/html/renderDevPage.js +63 -11
  529. package/src/html/renderDevPage.test.mjs +138 -0
  530. package/src/lib/getRequestPath.js +29 -0
  531. package/src/middleware/createSameOriginGuard.js +37 -0
  532. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  533. package/src/middleware/errorHandler.js +51 -1
  534. package/src/middleware/errorHandler.test.mjs +173 -3
  535. package/src/middleware/isRebindingSafeHost.js +44 -0
  536. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  537. package/src/middleware/localDevToolsOnly.js +38 -0
  538. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  539. package/src/middleware/noStore.js +27 -0
  540. package/src/middleware/noStore.test.mjs +48 -0
  541. package/src/middleware/requestTimeout.js +38 -0
  542. package/src/middleware/staleFlag.js +76 -0
  543. package/src/middleware/staleFlag.test.mjs +127 -0
  544. package/src/middleware/stampCaller.js +38 -0
  545. package/src/middleware/stampCaller.test.mjs +64 -0
  546. package/src/routes/agent.js +15 -3
  547. package/src/routes/auth.js +29 -20
  548. package/src/routes/auth.test.mjs +30 -56
  549. package/src/routes/clientError.js +13 -13
  550. package/src/routes/cron.js +3 -1
  551. package/src/routes/cronForward.js +3 -1
  552. package/src/routes/detached.js +22 -9
  553. package/src/routes/devInspect.js +17 -8
  554. package/src/routes/devInspect.test.mjs +44 -7
  555. package/src/routes/devRecording.js +113 -0
  556. package/src/routes/devRecording.test.mjs +253 -0
  557. package/src/routes/docs/buildStatus.js +10 -2
  558. package/src/routes/docs/buildStatus.test.mjs +46 -0
  559. package/src/routes/docs/content.js +5 -3
  560. package/src/routes/docs/evalOperator.js +15 -1
  561. package/src/routes/docs/events.js +50 -0
  562. package/src/routes/docs/events.test.mjs +117 -0
  563. package/src/routes/docs/find.js +9 -1
  564. package/src/routes/docs/icons.js +30 -0
  565. package/src/routes/docs/inspectState.js +16 -1
  566. package/src/routes/docs/journey.js +194 -0
  567. package/src/routes/docs/journey.test.mjs +401 -0
  568. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  569. package/src/routes/docs/loadState.js +4 -2
  570. package/src/routes/docs/mcp.js +34 -3
  571. package/src/routes/docs/mcp.test.mjs +130 -0
  572. package/src/routes/docs/mutants.js +78 -0
  573. package/src/routes/docs/mutants.test.mjs +180 -0
  574. package/src/routes/docs/parsePathParamsParam.js +41 -0
  575. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  576. package/src/routes/docs/parseUserParam.js +27 -21
  577. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  578. package/src/routes/docs/restart.js +35 -0
  579. package/src/routes/docs/restart.test.mjs +64 -0
  580. package/src/routes/docs/runEndpoint.js +54 -0
  581. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  582. package/src/routes/docs/runRequest.js +25 -8
  583. package/src/routes/docs/runRequest.test.mjs +92 -0
  584. package/src/routes/docs/screenshot.js +50 -2
  585. package/src/routes/docs/screenshot.test.mjs +117 -0
  586. package/src/routes/docs/search.js +15 -2
  587. package/src/routes/docs/snapshotState.js +17 -3
  588. package/src/routes/endpoints.js +27 -26
  589. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  590. package/src/routes/feedback.js +10 -13
  591. package/src/routes/jitPage.js +99 -35
  592. package/src/routes/jitPage.test.mjs +248 -0
  593. package/src/routes/mcp.js +82 -5
  594. package/src/routes/mcp.test.mjs +215 -0
  595. package/src/routes/mcpMemberToken.test.mjs +125 -0
  596. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  597. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  598. package/src/routes/mountDevPageRoutes.js +25 -0
  599. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  600. package/src/routes/mountOauthDiscovery.js +44 -0
  601. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  602. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  604. package/src/routes/reload.js +16 -0
  605. package/src/routes/request.js +6 -4
  606. package/src/routes/request.test.mjs +9 -5
  607. package/src/routes/requestBody.test.mjs +159 -0
  608. package/src/routes/usage.js +6 -2
  609. package/src/routes/user.js +27 -0
  610. package/src/routes/websocket.js +29 -8
  611. package/src/routes/websocket.test.mjs +65 -0
  612. package/src/routes/wellKnownFallback.js +30 -0
  613. package/src/routes/wellKnownFallback.test.mjs +32 -0
  614. package/src/websocket/devWebSocket.js +41 -26
  615. package/src/websocket/devWebSocket.test.mjs +157 -0
  616. package/vite.config.js +53 -23
  617. package/lib/server/auth/getDevSession.js +0 -70
  618. package/lib/server/auth/getDevSession.test.mjs +0 -138
  619. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  620. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  621. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  622. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  623. package/src/routes/apiPage.js +0 -37
@@ -0,0 +1,30 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ // The context.waitUntil of a data-session request: background work (scheduleBackground, the detached
18
+ // dispatch and the detached run) is added to the session's work set, so closing the session waits
19
+ // for it before it drops the database.
20
+ function trackSessionWork({ session }) {
21
+ return function waitUntil(promise) {
22
+ session.work.add(promise);
23
+ const remove = () => {
24
+ session.work.delete(promise);
25
+ };
26
+ promise.then(remove, remove);
27
+ };
28
+ }
29
+
30
+ export default trackSessionWork;
@@ -0,0 +1,40 @@
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
+ function escapeRegExp(value) {
18
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
19
+ }
20
+
21
+ // The paths Vite serves itself; every other request routes to the Hono app.
22
+ // @hono/vite-dev-server tests these against the raw request url, before Vite
23
+ // strips its `base`, and Vite serves every module under `base` - so with
24
+ // config.basePath the patterns must carry the prefix. Without it
25
+ // `<basePath>/@vite/client` and `<basePath>/client/main.jsx` fall through to
26
+ // the Hono page route and come back as the HTML shell.
27
+ function createDevServerExclude({ basePath }) {
28
+ const prefix = escapeRegExp(basePath);
29
+ return [
30
+ new RegExp(`^${prefix}/client/.+`),
31
+ new RegExp(`^${prefix}/lib/.+`),
32
+ new RegExp(`^${prefix}/build/.+`),
33
+ new RegExp(`^${prefix}/@.+$`),
34
+ new RegExp(`^${prefix}/node_modules/.*`),
35
+ /\?t=\d+$/,
36
+ /^\/favicon\.ico$/,
37
+ ];
38
+ }
39
+
40
+ export default createDevServerExclude;
@@ -0,0 +1,79 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import createDevServerExclude from './createDevServerExclude.js';
18
+
19
+ function isExcluded(exclude, url) {
20
+ return exclude.some((pattern) => pattern.test(url));
21
+ }
22
+
23
+ test('createDevServerExclude leaves Vite module paths to Vite when there is no basePath', () => {
24
+ const exclude = createDevServerExclude({ basePath: '' });
25
+
26
+ expect(isExcluded(exclude, '/@vite/client')).toBe(true);
27
+ expect(isExcluded(exclude, '/@react-refresh')).toBe(true);
28
+ expect(isExcluded(exclude, '/client/main.jsx')).toBe(true);
29
+ expect(isExcluded(exclude, '/build/plugins/blocks.js')).toBe(true);
30
+ expect(isExcluded(exclude, '/lib/client/ErrorBar.jsx')).toBe(true);
31
+ expect(isExcluded(exclude, '/node_modules/.vite/deps/react.js?v=1')).toBe(true);
32
+ expect(isExcluded(exclude, '/client/App.jsx?t=1712345678')).toBe(true);
33
+ expect(isExcluded(exclude, '/favicon.ico')).toBe(true);
34
+ });
35
+
36
+ test('createDevServerExclude routes page, api and docs paths to Hono when there is no basePath', () => {
37
+ const exclude = createDevServerExclude({ basePath: '' });
38
+
39
+ expect(isExcluded(exclude, '/')).toBe(false);
40
+ expect(isExcluded(exclude, '/home')).toBe(false);
41
+ expect(isExcluded(exclude, '/api/ping')).toBe(false);
42
+ expect(isExcluded(exclude, '/lowdefy-docs/mcp')).toBe(false);
43
+ });
44
+
45
+ test('createDevServerExclude leaves Vite module paths under the basePath to Vite', () => {
46
+ const exclude = createDevServerExclude({ basePath: '/app' });
47
+
48
+ expect(isExcluded(exclude, '/app/@vite/client')).toBe(true);
49
+ expect(isExcluded(exclude, '/app/@react-refresh')).toBe(true);
50
+ expect(isExcluded(exclude, '/app/@fs/Users/dev/app/plugin.js')).toBe(true);
51
+ expect(isExcluded(exclude, '/app/client/main.jsx')).toBe(true);
52
+ expect(isExcluded(exclude, '/app/build/plugins/blocks.js')).toBe(true);
53
+ expect(isExcluded(exclude, '/app/lib/client/ErrorBar.jsx')).toBe(true);
54
+ expect(isExcluded(exclude, '/app/node_modules/.vite/deps/react.js?v=1')).toBe(true);
55
+ });
56
+
57
+ test('createDevServerExclude routes page, api and docs paths under the basePath to Hono', () => {
58
+ const exclude = createDevServerExclude({ basePath: '/app' });
59
+
60
+ expect(isExcluded(exclude, '/app')).toBe(false);
61
+ expect(isExcluded(exclude, '/app/home')).toBe(false);
62
+ expect(isExcluded(exclude, '/app/client-list')).toBe(false);
63
+ expect(isExcluded(exclude, '/app/api/ping')).toBe(false);
64
+ expect(isExcluded(exclude, '/app/lowdefy-docs/mcp')).toBe(false);
65
+ });
66
+
67
+ test('createDevServerExclude does not hand unprefixed module paths to Vite under a basePath', () => {
68
+ const exclude = createDevServerExclude({ basePath: '/app' });
69
+
70
+ expect(isExcluded(exclude, '/@vite/client')).toBe(false);
71
+ expect(isExcluded(exclude, '/client/main.jsx')).toBe(false);
72
+ });
73
+
74
+ test('createDevServerExclude matches a basePath containing regular expression characters literally', () => {
75
+ const exclude = createDevServerExclude({ basePath: '/my.app' });
76
+
77
+ expect(isExcluded(exclude, '/my.app/@vite/client')).toBe(true);
78
+ expect(isExcluded(exclude, '/myxapp/@vite/client')).toBe(false);
79
+ });
@@ -25,13 +25,17 @@ import checkMockUserWarning from './processes/checkMockUserWarning.mjs';
25
25
  import initialBuild from './processes/initialBuild.mjs';
26
26
  import installPlugins from './processes/installPlugins.mjs';
27
27
  import lowdefyBuild from './processes/lowdefyBuild.mjs';
28
+ import optimizeDependencies from './processes/optimizeDependencies.mjs';
28
29
  import readDotEnv from './processes/readDotEnv.mjs';
29
30
  import reloadClients from './processes/reloadClients.mjs';
30
31
  import restartServer from './processes/restartServer.mjs';
31
32
  import shutdownServer from './processes/shutdownServer.mjs';
32
33
  import startWatchers from './processes/startWatchers.mjs';
34
+ import syncServer from './processes/syncServer.mjs';
33
35
 
36
+ import createServerArtifactTracker from './utils/createServerArtifactTracker.mjs';
34
37
  import getViteBin from './utils/getViteBin.mjs';
38
+ import readManagerVersion from './utils/readManagerVersion.mjs';
35
39
 
36
40
  const argv = yargs(hideBin(process.argv)).array('watch').array('watchIgnore').argv;
37
41
 
@@ -44,6 +48,7 @@ async function getContext() {
44
48
  },
45
49
  directories: {
46
50
  build: path.resolve(process.cwd(), './build'),
51
+ buildStaging: path.resolve(process.cwd(), './build-staging'),
47
52
  config: path.resolve(argv.configDirectory ?? env.LOWDEFY_DIRECTORY_CONFIG ?? process.cwd()),
48
53
  server: process.cwd(),
49
54
  },
@@ -54,8 +59,14 @@ async function getContext() {
54
59
  destination: pino.destination({ dest: 1, sync: true }),
55
60
  }),
56
61
  options: {
62
+ internalPort: env.LOWDEFY_SERVER_DEV_INTERNAL_PORT
63
+ ? Number(env.LOWDEFY_SERVER_DEV_INTERNAL_PORT)
64
+ : undefined,
65
+ owner: env.LOWDEFY_DEV_OWNER === 'hub' ? 'hub' : 'terminal',
57
66
  port: Number(argv.port ?? env.PORT ?? 3000),
58
67
  refResolver: argv.refResolver ?? env.LOWDEFY_BUILD_REF_RESOLVER,
68
+ skipPluginChecks: env.LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS === 'true',
69
+ strictPort: env.LOWDEFY_SERVER_DEV_STRICT_PORT === 'true',
59
70
  watch:
60
71
  argv.watch ?? env.LOWDEFY_SERVER_DEV_WATCH ? JSON.parse(env.LOWDEFY_SERVER_DEV_WATCH) : [],
61
72
  watchIgnore:
@@ -63,11 +74,12 @@ async function getContext() {
63
74
  ? JSON.parse(env.LOWDEFY_SERVER_DEV_WATCH_IGNORE)
64
75
  : [],
65
76
  },
66
- version: env.npm_package_version,
77
+ version: readManagerVersion(),
67
78
 
68
79
  // JIT build state
69
80
  pageRegistry: null,
70
81
  buildContext: null,
82
+ lastBuildFailed: false,
71
83
  };
72
84
 
73
85
  context.packageManagerCmd = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
@@ -78,18 +90,28 @@ async function getContext() {
78
90
  // Wrap lowdefyBuild to capture and store the shallow build result
79
91
  const buildFn = lowdefyBuild(context);
80
92
  context.lowdefyBuild = async () => {
81
- const result = await buildFn();
93
+ let result;
94
+ try {
95
+ result = await buildFn();
96
+ } catch (error) {
97
+ context.lastBuildFailed = true;
98
+ throw error;
99
+ }
100
+ context.lastBuildFailed = false;
82
101
  if (result) {
83
102
  context.pageRegistry = result.pageRegistry;
84
103
  context.buildContext = result.context;
85
104
  }
86
105
  };
87
106
 
107
+ context.optimizeDependencies = optimizeDependencies(context);
88
108
  context.readDotEnv = readDotEnv(context);
89
109
  context.reloadClients = reloadClients(context);
90
110
  context.restartServer = restartServer(context);
111
+ context.serverArtifacts = createServerArtifactTracker(context);
91
112
  context.shutdownServer = shutdownServer(context);
92
113
  context.startWatchers = startWatchers(context);
114
+ context.syncServer = syncServer(context);
93
115
 
94
116
  return context;
95
117
  }
@@ -15,12 +15,26 @@
15
15
  limitations under the License.
16
16
  */
17
17
 
18
+ import { pluginChecksNeedRebuild } from '@lowdefy/node-utils';
19
+
18
20
  function initialBuild(context) {
19
21
  return async () => {
20
22
  context.readDotEnv();
21
23
  await context.lowdefyBuild();
22
24
  await context.checkMockUserWarning();
23
25
  await context.installPlugins();
26
+ // The build lists the plugins it used but could not check, since they were
27
+ // not installed. The install above added them, so build once more when that
28
+ // checks something new, unless no checks run this time anyway.
29
+ if (
30
+ !context.options.skipPluginChecks &&
31
+ pluginChecksNeedRebuild({
32
+ buildDirectory: context.directories.build,
33
+ serverDirectory: context.directories.server,
34
+ })
35
+ ) {
36
+ await context.lowdefyBuild();
37
+ }
24
38
  };
25
39
  }
26
40
 
@@ -0,0 +1,101 @@
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 initialBuild from './initialBuild.mjs';
23
+
24
+ let buildDirectory;
25
+ let calls;
26
+
27
+ function createContext({ installPlugins, skipPluginChecks } = {}) {
28
+ const record = (name, impl) =>
29
+ jest.fn(async () => {
30
+ calls.push(name);
31
+ if (impl) await impl();
32
+ });
33
+ return {
34
+ directories: { build: buildDirectory, server: buildDirectory },
35
+ options: { skipPluginChecks },
36
+ readDotEnv: jest.fn(() => calls.push('readDotEnv')),
37
+ lowdefyBuild: record('lowdefyBuild'),
38
+ checkMockUserWarning: record('checkMockUserWarning'),
39
+ installPlugins: record('installPlugins', installPlugins),
40
+ };
41
+ }
42
+
43
+ beforeEach(() => {
44
+ calls = [];
45
+ buildDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-initial-build-'));
46
+ });
47
+
48
+ afterEach(() => {
49
+ fs.rmSync(buildDirectory, { recursive: true, force: true });
50
+ });
51
+
52
+ test('initialBuild builds once when no plugin checks are pending', async () => {
53
+ const context = createContext();
54
+ await initialBuild(context)();
55
+ expect(context.lowdefyBuild).toHaveBeenCalledTimes(1);
56
+ expect(calls).toEqual(['readDotEnv', 'lowdefyBuild', 'checkMockUserWarning', 'installPlugins']);
57
+ });
58
+
59
+ test('initialBuild builds again after installing plugins when plugin checks are pending', async () => {
60
+ const context = createContext({
61
+ installPlugins: () =>
62
+ fs.writeFileSync(
63
+ path.join(buildDirectory, 'plugin-checks-pending.json'),
64
+ JSON.stringify({ packages: [{ name: '@acme/plugin', schemaChecked: true }] })
65
+ ),
66
+ });
67
+ await initialBuild(context)();
68
+ expect(context.lowdefyBuild).toHaveBeenCalledTimes(2);
69
+ expect(calls).toEqual([
70
+ 'readDotEnv',
71
+ 'lowdefyBuild',
72
+ 'checkMockUserWarning',
73
+ 'installPlugins',
74
+ 'lowdefyBuild',
75
+ ]);
76
+ });
77
+
78
+ test('initialBuild builds once with skipPluginChecks, even when plugin checks are pending', async () => {
79
+ const context = createContext({
80
+ skipPluginChecks: true,
81
+ installPlugins: () =>
82
+ fs.writeFileSync(
83
+ path.join(buildDirectory, 'plugin-checks-pending.json'),
84
+ JSON.stringify({ packages: [{ name: '@acme/plugin', schemaChecked: true }] })
85
+ ),
86
+ });
87
+ await initialBuild(context)();
88
+ expect(context.lowdefyBuild).toHaveBeenCalledTimes(1);
89
+ });
90
+
91
+ test('initialBuild builds once when the pending plugins give nothing new to check', async () => {
92
+ const context = createContext({
93
+ installPlugins: () =>
94
+ fs.writeFileSync(
95
+ path.join(buildDirectory, 'plugin-checks-pending.json'),
96
+ JSON.stringify({ packages: [{ name: '@acme/connection', schemaChecked: false }] })
97
+ ),
98
+ });
99
+ await initialBuild(context)();
100
+ expect(context.lowdefyBuild).toHaveBeenCalledTimes(1);
101
+ });
@@ -17,6 +17,7 @@
17
17
  import { serializeBuildException, shallowBuild } from '@lowdefy/build/dev';
18
18
  import createCustomPluginMessagesMap from '../utils/createCustomPluginMessagesMap.mjs';
19
19
  import createCustomPluginTypesMap from '../utils/createCustomPluginTypesMap.mjs';
20
+ import publishBuildDirectory from '../utils/publishBuildDirectory.mjs';
20
21
  import writeBuildStatus from '../utils/writeBuildStatus.mjs';
21
22
 
22
23
  function formatDuration(ms) {
@@ -25,26 +26,39 @@ function formatDuration(ms) {
25
26
  }
26
27
 
27
28
  function lowdefyBuild({ directories, logger, options }) {
28
- return async () => {
29
+ async function build() {
29
30
  logger.info({ spin: 'start' }, 'Building config...');
30
31
  const startTime = Date.now();
31
- const customTypesMap = await createCustomPluginTypesMap({ directories, logger });
32
- const customMessagesMap = await createCustomPluginMessagesMap({ directories, logger });
33
32
 
33
+ // Reading the plugin maps parses lowdefy.yaml and imports each plugin's
34
+ // types, so a YAML syntax error or a broken plugin fails the build there,
35
+ // and the build status has to report it like any other build error. So
36
+ // does a publish that fails part way: the live build is then a mix of two
37
+ // builds, and the next change must rebuild and publish it whole.
34
38
  let result;
35
39
  try {
40
+ const [customTypesMap, customMessagesMap] = await Promise.all([
41
+ createCustomPluginTypesMap({ directories, logger }),
42
+ createCustomPluginMessagesMap({ directories, logger }),
43
+ ]);
36
44
  result = await shallowBuild({
37
45
  customMessagesMap,
38
46
  customTypesMap,
39
- directories,
47
+ directories: { ...directories, build: directories.buildStaging },
40
48
  logger,
41
49
  refResolver: options.refResolver,
50
+ skipPluginChecks: options.skipPluginChecks,
42
51
  stage: 'dev',
43
52
  });
53
+ await publishBuildDirectory({
54
+ buildDirectory: directories.build,
55
+ stagingDirectory: directories.buildStaging,
56
+ }).catch((error) => {
57
+ throw new Error(`Publishing the build to ${directories.build} failed: ${error.message}`, {
58
+ cause: error,
59
+ });
60
+ });
44
61
  } catch (error) {
45
- // Write the artifact after the attempt fails - the build may have
46
- // cleaned the build directory before failing, so this must not run
47
- // before the attempt completes.
48
62
  await writeBuildStatus({
49
63
  directories,
50
64
  status: 'error',
@@ -65,6 +79,15 @@ function lowdefyBuild({ directories, logger, options }) {
65
79
  const duration = Date.now() - startTime;
66
80
  logger.info({ spin: 'succeed' }, `Built config in ${formatDuration(duration)}.`);
67
81
  return result;
82
+ }
83
+
84
+ // Watchers, restarts and plugin installs each start builds, and every build
85
+ // writes the same staging directory, so a build waits for the one before it.
86
+ let previousBuild = Promise.resolve();
87
+ return () => {
88
+ const nextBuild = previousBuild.then(build);
89
+ previousBuild = nextBuild.catch(() => {});
90
+ return nextBuild;
68
91
  };
69
92
  }
70
93
 
@@ -0,0 +1,62 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { jest } from '@jest/globals';
18
+
19
+ const mockShallowBuild = jest.fn();
20
+
21
+ jest.unstable_mockModule('@lowdefy/build/dev', () => ({
22
+ shallowBuild: mockShallowBuild,
23
+ serializeBuildException: jest.fn((exception) => ({ message: exception.message })),
24
+ }));
25
+ jest.unstable_mockModule('../utils/writeBuildStatus.mjs', () => ({
26
+ default: jest.fn(),
27
+ }));
28
+ jest.unstable_mockModule('../utils/createCustomPluginTypesMap.mjs', () => ({
29
+ default: jest.fn().mockResolvedValue({}),
30
+ }));
31
+ jest.unstable_mockModule('../utils/createCustomPluginMessagesMap.mjs', () => ({
32
+ default: jest.fn().mockResolvedValue({}),
33
+ }));
34
+ jest.unstable_mockModule('../utils/publishBuildDirectory.mjs', () => ({
35
+ default: jest.fn(async () => {}),
36
+ }));
37
+
38
+ const { default: lowdefyBuild } = await import('./lowdefyBuild.mjs');
39
+
40
+ function createContext(options) {
41
+ return {
42
+ directories: { build: '/app/build', buildStaging: '/app/build-staging' },
43
+ logger: { info: jest.fn() },
44
+ options,
45
+ };
46
+ }
47
+
48
+ beforeEach(() => {
49
+ mockShallowBuild.mockResolvedValue({ components: {}, pageRegistry: {}, context: {} });
50
+ });
51
+
52
+ test('lowdefyBuild passes skipPluginChecks true to shallowBuild', async () => {
53
+ await lowdefyBuild(createContext({ skipPluginChecks: true }))();
54
+ expect(mockShallowBuild.mock.calls[0][0].skipPluginChecks).toBe(true);
55
+ });
56
+
57
+ test('lowdefyBuild passes skipPluginChecks undefined to shallowBuild when the option is not set', async () => {
58
+ await lowdefyBuild(createContext({}))();
59
+ const [args] = mockShallowBuild.mock.calls[0];
60
+ expect(args.skipPluginChecks).toBeUndefined();
61
+ expect(args.stage).toEqual('dev');
62
+ });
@@ -21,6 +21,7 @@ const mockSerializeBuildException = jest.fn((exception) => ({ message: exception
21
21
  const mockWriteBuildStatus = jest.fn();
22
22
  const mockCreateCustomPluginTypesMap = jest.fn().mockResolvedValue({});
23
23
  const mockCreateCustomPluginMessagesMap = jest.fn().mockResolvedValue({});
24
+ const mockPublishBuildDirectory = jest.fn(async () => {});
24
25
 
25
26
  jest.unstable_mockModule('@lowdefy/build/dev', () => ({
26
27
  shallowBuild: mockShallowBuild,
@@ -35,12 +36,15 @@ jest.unstable_mockModule('../utils/createCustomPluginTypesMap.mjs', () => ({
35
36
  jest.unstable_mockModule('../utils/createCustomPluginMessagesMap.mjs', () => ({
36
37
  default: mockCreateCustomPluginMessagesMap,
37
38
  }));
39
+ jest.unstable_mockModule('../utils/publishBuildDirectory.mjs', () => ({
40
+ default: mockPublishBuildDirectory,
41
+ }));
38
42
 
39
43
  const { default: lowdefyBuild } = await import('./lowdefyBuild.mjs');
40
44
 
41
45
  function createContext() {
42
46
  return {
43
- directories: { build: '/app/build' },
47
+ directories: { build: '/app/build', buildStaging: '/app/build-staging' },
44
48
  logger: { info: jest.fn() },
45
49
  options: { refResolver: undefined },
46
50
  };
@@ -100,3 +104,134 @@ test('lowdefyBuild falls back to the raw error message when the thrown error has
100
104
  warnings: [],
101
105
  });
102
106
  });
107
+
108
+ test('lowdefyBuild writes an error buildStatus.json when lowdefy.yaml cannot be read for its plugins', async () => {
109
+ const context = createContext();
110
+ const parseError = new Error('All sequence items must start at the same column at line 7');
111
+ mockCreateCustomPluginTypesMap.mockRejectedValueOnce(parseError);
112
+
113
+ const build = lowdefyBuild(context);
114
+ await expect(build()).rejects.toBe(parseError);
115
+
116
+ expect(mockShallowBuild).not.toHaveBeenCalled();
117
+ expect(mockWriteBuildStatus).toHaveBeenCalledWith({
118
+ directories: context.directories,
119
+ status: 'error',
120
+ errors: [{ message: 'All sequence items must start at the same column at line 7' }],
121
+ warnings: [],
122
+ });
123
+ });
124
+
125
+ test('lowdefyBuild builds into the staging directory and then publishes it', async () => {
126
+ const context = createContext();
127
+ mockShallowBuild.mockResolvedValue({ components: {}, pageRegistry: {}, context: {} });
128
+
129
+ const build = lowdefyBuild(context);
130
+ await build();
131
+
132
+ expect(mockShallowBuild.mock.calls[0][0].directories).toEqual({
133
+ build: '/app/build-staging',
134
+ buildStaging: '/app/build-staging',
135
+ });
136
+ expect(mockPublishBuildDirectory).toHaveBeenCalledWith({
137
+ buildDirectory: '/app/build',
138
+ stagingDirectory: '/app/build-staging',
139
+ });
140
+ expect(mockPublishBuildDirectory.mock.invocationCallOrder[0]).toBeLessThan(
141
+ mockWriteBuildStatus.mock.invocationCallOrder[0]
142
+ );
143
+ });
144
+
145
+ test('lowdefyBuild leaves the live build in place when the build fails', async () => {
146
+ const context = createContext();
147
+ mockShallowBuild.mockRejectedValue(new Error('Build failed'));
148
+
149
+ const build = lowdefyBuild(context);
150
+ await expect(build()).rejects.toThrow('Build failed');
151
+
152
+ expect(mockPublishBuildDirectory).not.toHaveBeenCalled();
153
+ });
154
+
155
+ test('lowdefyBuild writes an error buildStatus.json and rethrows when publishing the build fails', async () => {
156
+ const context = createContext();
157
+ mockShallowBuild.mockResolvedValue({ components: {}, pageRegistry: {}, context: {} });
158
+ const renameError = new Error("EPERM: operation not permitted, rename 'a' -> 'b'");
159
+ mockPublishBuildDirectory.mockRejectedValueOnce(renameError);
160
+
161
+ const build = lowdefyBuild(context);
162
+ await expect(build()).rejects.toThrow(
163
+ "Publishing the build to /app/build failed: EPERM: operation not permitted, rename 'a' -> 'b'"
164
+ );
165
+
166
+ expect(mockWriteBuildStatus).toHaveBeenCalledTimes(1);
167
+ expect(mockWriteBuildStatus).toHaveBeenCalledWith({
168
+ directories: context.directories,
169
+ status: 'error',
170
+ errors: [
171
+ {
172
+ message:
173
+ "Publishing the build to /app/build failed: EPERM: operation not permitted, rename 'a' -> 'b'",
174
+ },
175
+ ],
176
+ warnings: [],
177
+ });
178
+ });
179
+
180
+ test('lowdefyBuild starts a build only after the previous one has finished', async () => {
181
+ const context = createContext();
182
+ const events = [];
183
+ let finishFirst;
184
+ mockShallowBuild
185
+ .mockImplementationOnce(() => {
186
+ events.push('first start');
187
+ return new Promise((resolve) => {
188
+ finishFirst = () => {
189
+ events.push('first end');
190
+ resolve({ components: {}, pageRegistry: {}, context: {} });
191
+ };
192
+ });
193
+ })
194
+ .mockImplementationOnce(async () => {
195
+ events.push('second start');
196
+ return { components: {}, pageRegistry: {}, context: {} };
197
+ });
198
+
199
+ const build = lowdefyBuild(context);
200
+ const first = build();
201
+ const second = build();
202
+ await new Promise((resolve) => setTimeout(resolve, 10));
203
+ expect(events).toEqual(['first start']);
204
+ finishFirst();
205
+ await Promise.all([first, second]);
206
+
207
+ expect(events).toEqual(['first start', 'first end', 'second start']);
208
+ });
209
+
210
+ test('lowdefyBuild runs the next build after a failed one', async () => {
211
+ const context = createContext();
212
+ mockShallowBuild
213
+ .mockRejectedValueOnce(new Error('Build failed'))
214
+ .mockResolvedValueOnce({ components: {}, pageRegistry: {}, context: {} });
215
+
216
+ const build = lowdefyBuild(context);
217
+ const first = build();
218
+ const second = build();
219
+
220
+ await expect(first).rejects.toThrow('Build failed');
221
+ await expect(second).resolves.toEqual({ components: {}, pageRegistry: {}, context: {} });
222
+ });
223
+
224
+ test('lowdefyBuild reads the plugin types and messages maps in parallel', async () => {
225
+ const context = createContext();
226
+ let releaseTypes;
227
+ mockCreateCustomPluginTypesMap.mockImplementationOnce(
228
+ () => new Promise((resolve) => (releaseTypes = () => resolve({})))
229
+ );
230
+ mockShallowBuild.mockResolvedValue({ components: {}, pageRegistry: {}, context: {} });
231
+
232
+ const build = lowdefyBuild(context)();
233
+ await new Promise((resolve) => setTimeout(resolve, 10));
234
+ expect(mockCreateCustomPluginMessagesMap).toHaveBeenCalledTimes(1);
235
+ releaseTypes();
236
+ await build;
237
+ });