@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,543 @@
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
+ // What a change event rebuilds: the dev server keeps its JIT build context and
18
+ // built pages across edits, and rebuilds a page only when a file its last build
19
+ // read changed, it ran app code, or it was built on an earlier context.
20
+
21
+ import fs from 'fs';
22
+ import os from 'os';
23
+ import path from 'path';
24
+ import { jest } from '@jest/globals';
25
+
26
+ import buildPageIfNeeded, {
27
+ buildPageWithContext,
28
+ getBuildContext,
29
+ getPageJitEnrichment,
30
+ reviewBuiltPage,
31
+ syncBuildSignals,
32
+ } from './jitPageBuilder.js';
33
+
34
+ // A JIT page build loads the build package's plugins on first use.
35
+ jest.setTimeout(30000);
36
+
37
+ function writeFile(filePath, content) {
38
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
39
+ fs.writeFileSync(filePath, content);
40
+ }
41
+
42
+ function writeJson(filePath, data) {
43
+ writeFile(filePath, JSON.stringify(data));
44
+ }
45
+
46
+ let lastSignal = 0;
47
+
48
+ // A config build's output, as far as a JIT page build reads it, and the app's
49
+ // config files. pages maps a page id to its registry entry (refPath, and
50
+ // unresolvedVars when its vars come from a file).
51
+ function createApp({ files, pages }) {
52
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ldf-page-rebuilds-'));
53
+ const buildDirectory = path.join(root, 'server', 'build');
54
+ const configDirectory = path.join(root, 'config');
55
+ writeJson(path.join(buildDirectory, 'idCounter.json'), { prefix: 'cfg1_', counter: 10 });
56
+ writeJson(path.join(buildDirectory, 'keyMap.json'), {});
57
+ writeJson(path.join(buildDirectory, 'refMap.json'), {});
58
+ writeJson(path.join(buildDirectory, 'installedPluginPackages.json'), [
59
+ '@lowdefy/actions-core',
60
+ '@lowdefy/blocks-antd',
61
+ '@lowdefy/blocks-basic',
62
+ '@lowdefy/operators-js',
63
+ ]);
64
+ writeJson(path.join(buildDirectory, 'theme.json'), {});
65
+ writeFile(path.join(configDirectory, 'package.json'), '{"type":"module"}');
66
+ for (const [file, content] of Object.entries(files)) {
67
+ writeFile(path.join(configDirectory, file), content);
68
+ }
69
+ const registry = {};
70
+ for (const [pageId, entry] of Object.entries(pages)) {
71
+ registry[pageId] = { pageId, auth: { public: true }, refId: `ref-${pageId}`, ...entry };
72
+ }
73
+ writeJson(path.join(buildDirectory, 'pageRegistry.json'), registry);
74
+ const app = { root, buildDirectory, configDirectory };
75
+ app.request = (pageId) =>
76
+ buildPageIfNeeded({ pageId, buildDirectory, configDirectory }).then(
77
+ (result) => (result === true ? 'served' : 'built'),
78
+ (error) => {
79
+ error.outcome = 'failed';
80
+ throw error;
81
+ }
82
+ );
83
+ app.edit = (file, content) => writeFile(path.join(configDirectory, file), content);
84
+ // What the manager writes after a batch of watched changes.
85
+ app.signalChange = () => {
86
+ lastSignal += 1;
87
+ writeFile(path.join(buildDirectory, 'invalidatePages'), String(lastSignal));
88
+ };
89
+ // A config build publish renames a new page registry into place.
90
+ app.publishRegistry = () => {
91
+ const registryPath = path.join(buildDirectory, 'pageRegistry.json');
92
+ const stagedPath = `${registryPath}.staged`;
93
+ fs.copyFileSync(registryPath, stagedPath);
94
+ const { atime, mtime } = fs.statSync(registryPath);
95
+ fs.renameSync(stagedPath, registryPath);
96
+ return { atime, mtime };
97
+ };
98
+ app.context = () => {
99
+ syncBuildSignals({ buildDirectory, configDirectory });
100
+ return getBuildContext(buildDirectory, configDirectory);
101
+ };
102
+ app.remove = () => fs.rmSync(root, { recursive: true, force: true });
103
+ return app;
104
+ }
105
+
106
+ // Runs onWrite once, when the build of pageId writes its page: after the build
107
+ // read its files, before it ends.
108
+ function duringBuildOf({ app, pageId, onWrite }) {
109
+ const context = app.context();
110
+ const writeBuildArtifact = context.writeBuildArtifact;
111
+ let done = false;
112
+ context.writeBuildArtifact = async (fileName, content) => {
113
+ if (!done && fileName === `pages/${pageId}.json`) {
114
+ done = true;
115
+ await onWrite();
116
+ }
117
+ return writeBuildArtifact(fileName, content);
118
+ };
119
+ }
120
+
121
+ const sharedRefPages = {
122
+ files: {
123
+ 'pages/a.yaml': 'id: a\ntype: Box\nblocks:\n - _ref: blocks/shared.yaml\n',
124
+ 'pages/b.yaml': 'id: b\ntype: Box\nblocks:\n - _ref: blocks/shared.yaml\n',
125
+ 'pages/c.yaml': 'id: c\ntype: Box\n',
126
+ 'blocks/shared.yaml': 'id: shared\ntype: Box\n',
127
+ },
128
+ pages: {
129
+ a: { refPath: 'pages/a.yaml' },
130
+ b: { refPath: 'pages/b.yaml' },
131
+ c: { refPath: 'pages/c.yaml' },
132
+ },
133
+ };
134
+
135
+ test('editing a page file rebuilds that page and leaves an unrelated built page compiled', async () => {
136
+ const app = createApp(sharedRefPages);
137
+ expect(await app.request('a')).toBe('built');
138
+ expect(await app.request('c')).toBe('built');
139
+ expect(await app.request('a')).toBe('served');
140
+
141
+ app.edit('pages/a.yaml', 'id: a\ntype: Box\nproperties:\n title: Edited\n');
142
+ app.signalChange();
143
+
144
+ expect(await app.request('c')).toBe('served');
145
+ expect(await app.request('a')).toBe('built');
146
+ expect(await app.request('a')).toBe('served');
147
+ const page = JSON.parse(fs.readFileSync(path.join(app.buildDirectory, 'pages', 'a.json')));
148
+ expect(page.properties.title).toBe('Edited');
149
+ app.remove();
150
+ });
151
+
152
+ test('editing a shared _ref file rebuilds every page that read it, and only those', async () => {
153
+ const app = createApp(sharedRefPages);
154
+ for (const pageId of ['a', 'b', 'c']) await app.request(pageId);
155
+
156
+ app.edit('blocks/shared.yaml', 'id: shared\ntype: Box\nproperties:\n title: New\n');
157
+ app.signalChange();
158
+
159
+ expect(await app.request('a')).toBe('built');
160
+ expect(await app.request('b')).toBe('built');
161
+ expect(await app.request('c')).toBe('served');
162
+ app.remove();
163
+ });
164
+
165
+ test('editing a file that is not read by any built page rebuilds nothing', async () => {
166
+ const app = createApp(sharedRefPages);
167
+ for (const pageId of ['a', 'c']) await app.request(pageId);
168
+
169
+ app.edit('pages/b.yaml', 'id: b\ntype: Box\nproperties:\n title: Unread\n');
170
+ app.signalChange();
171
+
172
+ expect(await app.request('a')).toBe('served');
173
+ expect(await app.request('c')).toBe('served');
174
+ app.remove();
175
+ });
176
+
177
+ test('editing a vars file rebuilds the pages whose vars resolved from it', async () => {
178
+ const app = createApp({
179
+ files: {
180
+ 'pages/templated.yaml': 'id: templated\ntype: Box\nproperties:\n title:\n _var: title\n',
181
+ 'pages/other.yaml': 'id: other\ntype: Box\n',
182
+ 'vars/title.yaml': 'First\n',
183
+ },
184
+ pages: {
185
+ templated: {
186
+ refPath: 'pages/templated.yaml',
187
+ unresolvedVars: { title: { _ref: 'vars/title.yaml' } },
188
+ },
189
+ other: { refPath: 'pages/other.yaml' },
190
+ },
191
+ });
192
+ await app.request('templated');
193
+ await app.request('other');
194
+
195
+ app.edit('vars/title.yaml', 'Second\n');
196
+ app.signalChange();
197
+
198
+ expect(await app.request('other')).toBe('served');
199
+ expect(await app.request('templated')).toBe('built');
200
+ const page = JSON.parse(
201
+ fs.readFileSync(path.join(app.buildDirectory, 'pages', 'templated.json'))
202
+ );
203
+ expect(page.properties.title).toBe('Second');
204
+ app.remove();
205
+ });
206
+
207
+ test('editing a nunjucks file rebuilds the pages that referenced it', async () => {
208
+ const app = createApp({
209
+ files: {
210
+ 'pages/nunjucks.yaml.njk': 'id: nunjucks\ntype: Box\nproperties:\n title: {{ heading }}\n',
211
+ 'pages/other.yaml': 'id: other\ntype: Box\n',
212
+ },
213
+ pages: {
214
+ nunjucks: { refPath: 'pages/nunjucks.yaml.njk', unresolvedVars: { heading: 'Hello' } },
215
+ other: { refPath: 'pages/other.yaml' },
216
+ },
217
+ });
218
+ await app.request('nunjucks');
219
+ await app.request('other');
220
+
221
+ app.edit(
222
+ 'pages/nunjucks.yaml.njk',
223
+ 'id: nunjucks\ntype: Box\nproperties:\n title: Hi {{ heading }}\n'
224
+ );
225
+ app.signalChange();
226
+
227
+ expect(await app.request('other')).toBe('served');
228
+ expect(await app.request('nunjucks')).toBe('built');
229
+ app.remove();
230
+ });
231
+
232
+ test('a page that ran app code rebuilds after any edit, even to a file only its app code reads', async () => {
233
+ const app = createApp({
234
+ files: {
235
+ 'pages/coded.yaml': 'id: coded\ntype: Box\nblocks:\n - _ref: blocks/banner.js\n',
236
+ 'pages/plain.yaml': 'id: plain\ntype: Box\n',
237
+ 'blocks/banner.js':
238
+ "import fs from 'fs';\n" +
239
+ "const title = fs.readFileSync(new URL('./title.txt', import.meta.url), 'utf8');\n" +
240
+ "export default { id: 'banner', type: 'Box', properties: { title } };\n",
241
+ 'blocks/title.txt': 'First',
242
+ },
243
+ pages: {
244
+ coded: { refPath: 'pages/coded.yaml' },
245
+ plain: { refPath: 'pages/plain.yaml' },
246
+ },
247
+ });
248
+ await app.request('coded');
249
+ await app.request('plain');
250
+
251
+ app.edit('blocks/title.txt', 'Second');
252
+ app.signalChange();
253
+
254
+ expect(await app.request('plain')).toBe('served');
255
+ expect(await app.request('coded')).toBe('built');
256
+ expect(await app.request('coded')).toBe('served');
257
+ app.remove();
258
+ });
259
+
260
+ test('an edit during a running build rebuilds the page on its next request, also one that waited on its lock', async () => {
261
+ const app = createApp(sharedRefPages);
262
+ let waiter;
263
+ duringBuildOf({
264
+ app,
265
+ pageId: 'a',
266
+ onWrite: () => {
267
+ app.edit('blocks/shared.yaml', 'id: shared\ntype: Box\nproperties:\n title: Late\n');
268
+ app.signalChange();
269
+ waiter = app.request('a');
270
+ },
271
+ });
272
+
273
+ expect(await app.request('a')).toBe('built');
274
+ expect(await waiter).toBe('built');
275
+ expect(await app.request('a')).toBe('served');
276
+ app.remove();
277
+ });
278
+
279
+ test('a check that a rebuild overtook does not mark the rebuilt page current', async () => {
280
+ const app = createApp(sharedRefPages);
281
+ const original = 'id: shared\ntype: Box\nproperties:\n title: Original\n';
282
+ app.edit('blocks/shared.yaml', original);
283
+ app.signalChange();
284
+ await app.request('a');
285
+ app.edit('blocks/shared.yaml', 'id: shared\ntype: Box\nproperties:\n title: Interim\n');
286
+ app.signalChange();
287
+
288
+ // While the rebuild of the interim content runs, the file goes back to what
289
+ // the previous build read, and a build status review checks that build's
290
+ // record. The rebuild ends before the check's reads do.
291
+ let releaseReads;
292
+ const readsHeld = new Promise((resolve) => {
293
+ releaseReads = resolve;
294
+ });
295
+ let review;
296
+ duringBuildOf({
297
+ app,
298
+ pageId: 'a',
299
+ onWrite: () => {
300
+ app.edit('blocks/shared.yaml', original);
301
+ app.signalChange();
302
+ const signals = syncBuildSignals({
303
+ buildDirectory: app.buildDirectory,
304
+ configDirectory: app.configDirectory,
305
+ });
306
+ const context = getBuildContext(app.buildDirectory, app.configDirectory);
307
+ const readConfigFile = context.readConfigFile;
308
+ context.readConfigFile = async (filePath) => {
309
+ await readsHeld;
310
+ return readConfigFile(filePath);
311
+ };
312
+ review = reviewBuiltPage({ pageId: 'a', ...signals });
313
+ context.readConfigFile = readConfigFile;
314
+ },
315
+ });
316
+
317
+ expect(await app.request('a')).toBe('built');
318
+ releaseReads();
319
+ expect(await review).toBe('edited');
320
+ expect(await app.request('a')).toBe('built');
321
+ const page = fs.readFileSync(path.join(app.buildDirectory, 'pages', 'a.json'), 'utf8');
322
+ expect(page).toContain('"title":"Original"');
323
+ app.remove();
324
+ });
325
+
326
+ test('an edit during a running build that the build already read is still caught', async () => {
327
+ const app = createApp(sharedRefPages);
328
+ // The build reads through the read cache, which still holds what was on disk
329
+ // before the edit: the edit's change event arrives after the build read it.
330
+ await app.context().readConfigFile('blocks/shared.yaml');
331
+ app.edit('blocks/shared.yaml', 'id: shared\ntype: Box\nproperties:\n title: Late\n');
332
+
333
+ expect(await app.request('a')).toBe('built');
334
+ app.signalChange();
335
+
336
+ expect(await app.request('a')).toBe('built');
337
+ const page = fs.readFileSync(path.join(app.buildDirectory, 'pages', 'a.json'), 'utf8');
338
+ expect(page).toContain('"title":"Late"');
339
+ app.remove();
340
+ });
341
+
342
+ test('a registry publish during a running build rebuilds the page on the new context, with its _js', async () => {
343
+ const app = createApp({
344
+ files: {
345
+ 'pages/js.yaml': 'id: js\ntype: Box\nproperties:\n title:\n _js: return "from js";\n',
346
+ },
347
+ pages: { js: { refPath: 'pages/js.yaml' } },
348
+ });
349
+ const firstContext = app.context();
350
+ duringBuildOf({ app, pageId: 'js', onWrite: () => app.publishRegistry() });
351
+
352
+ expect(await app.request('js')).toBe('built');
353
+ expect(await app.request('js')).toBe('built');
354
+ const secondContext = getBuildContext(app.buildDirectory, app.configDirectory);
355
+ expect(secondContext).not.toBe(firstContext);
356
+ expect(await app.request('js')).toBe('served');
357
+
358
+ const pageConfig = JSON.parse(fs.readFileSync(path.join(app.buildDirectory, 'pages', 'js.json')));
359
+ const { jsEntries } = getPageJitEnrichment({ pageConfig });
360
+ expect(jsEntries).toContain('from js');
361
+ app.remove();
362
+ });
363
+
364
+ test('a registry publish between a page build and its enrichment still serves the page _js', async () => {
365
+ const app = createApp({
366
+ files: {
367
+ 'pages/js.yaml': 'id: js\ntype: Box\nproperties:\n title:\n _js: return "from js";\n',
368
+ 'pages/other.yaml': 'id: other\ntype: Box\n',
369
+ },
370
+ pages: { js: { refPath: 'pages/js.yaml' }, other: { refPath: 'pages/other.yaml' } },
371
+ });
372
+ const request = (pageId) =>
373
+ buildPageWithContext({
374
+ pageId,
375
+ buildDirectory: app.buildDirectory,
376
+ configDirectory: app.configDirectory,
377
+ });
378
+ const built = await request('js');
379
+ const served = await request('js');
380
+ expect(built.result.built).toBe(true);
381
+ expect(served.result).toBe(true);
382
+ expect(served.buildContext).toBe(built.buildContext);
383
+ const pageConfig = JSON.parse(fs.readFileSync(path.join(app.buildDirectory, 'pages', 'js.json')));
384
+
385
+ // Another request sees a config publish while this one reads the page config.
386
+ app.publishRegistry();
387
+ syncBuildSignals({ buildDirectory: app.buildDirectory, configDirectory: app.configDirectory });
388
+ expect(
389
+ getPageJitEnrichment({ pageConfig, buildContext: served.buildContext }).jsEntries
390
+ ).toContain('from js');
391
+
392
+ // And builds another page on a new context.
393
+ await request('other');
394
+ expect(getBuildContext(app.buildDirectory, app.configDirectory)).not.toBe(served.buildContext);
395
+ expect(
396
+ getPageJitEnrichment({ pageConfig, buildContext: served.buildContext }).jsEntries
397
+ ).toContain('from js');
398
+ app.remove();
399
+ });
400
+
401
+ test('a page build on a discarded context does not replace the JS maps of the new context', async () => {
402
+ const app = createApp({
403
+ files: {
404
+ 'pages/a.yaml': 'id: a\ntype: Box\nproperties:\n title:\n _js: return "from a";\n',
405
+ 'pages/b.yaml': 'id: b\ntype: Box\nproperties:\n title:\n _js: return "from b";\n',
406
+ },
407
+ pages: { a: { refPath: 'pages/a.yaml' }, b: { refPath: 'pages/b.yaml' } },
408
+ });
409
+ const firstContext = app.context();
410
+ // While a is built, a config publish discards its context and b is built on
411
+ // the next one.
412
+ duringBuildOf({
413
+ app,
414
+ pageId: 'a',
415
+ onWrite: async () => {
416
+ app.publishRegistry();
417
+ expect(await app.request('b')).toBe('built');
418
+ },
419
+ });
420
+
421
+ expect(await app.request('a')).toBe('built');
422
+ expect(getBuildContext(app.buildDirectory, app.configDirectory)).not.toBe(firstContext);
423
+ const clientJsMap = fs.readFileSync(
424
+ path.join(app.buildDirectory, 'plugins', 'operators', 'clientJsMap.js'),
425
+ 'utf8'
426
+ );
427
+ expect(clientJsMap).toContain('from b');
428
+ app.remove();
429
+ });
430
+
431
+ test('two change signals written in one millisecond are both seen', async () => {
432
+ const app = createApp(sharedRefPages);
433
+ const signalPath = path.join(app.buildDirectory, 'invalidatePages');
434
+ await app.request('a');
435
+ app.signalChange();
436
+ const time = new Date(1_700_000_000_000);
437
+ fs.utimesSync(signalPath, time, time);
438
+ expect(await app.request('a')).toBe('served');
439
+
440
+ app.edit('pages/a.yaml', 'id: a\ntype: Box\nproperties:\n title: Again\n');
441
+ app.signalChange();
442
+ fs.utimesSync(signalPath, time, time);
443
+
444
+ expect(await app.request('a')).toBe('built');
445
+ app.remove();
446
+ });
447
+
448
+ test('two registry publishes with the same modified time both recreate the context', async () => {
449
+ const app = createApp(sharedRefPages);
450
+ const registryPath = path.join(app.buildDirectory, 'pageRegistry.json');
451
+ await app.request('a');
452
+ const { atime, mtime } = app.publishRegistry();
453
+ fs.utimesSync(registryPath, atime, mtime);
454
+ expect(await app.request('a')).toBe('built');
455
+ expect(await app.request('a')).toBe('served');
456
+
457
+ app.publishRegistry();
458
+ fs.utimesSync(registryPath, atime, mtime);
459
+
460
+ expect(await app.request('a')).toBe('built');
461
+ app.remove();
462
+ });
463
+
464
+ // chmod 000 is how the test makes a read throw, and Windows has no such permission: chmod only
465
+ // sets the read-only attribute and the file stays readable.
466
+ const posixTest = process.platform === 'win32' ? test.skip : test;
467
+
468
+ posixTest(
469
+ 'a recorded file whose read throws during the check rebuilds the page, and the build reports it',
470
+ async () => {
471
+ const app = createApp(sharedRefPages);
472
+ await app.request('a');
473
+ await app.request('c');
474
+ const sharedPath = path.join(app.configDirectory, 'blocks', 'shared.yaml');
475
+ fs.chmodSync(sharedPath, 0o000);
476
+ app.signalChange();
477
+
478
+ try {
479
+ expect(await app.request('c')).toBe('served');
480
+ const error = await app.request('a').catch((caught) => caught);
481
+ expect(error.outcome).toBe('failed');
482
+ expect(error.message).toMatch('EACCES');
483
+ } finally {
484
+ fs.chmodSync(sharedPath, 0o644);
485
+ }
486
+ app.signalChange();
487
+ expect(await app.request('a')).toBe('built');
488
+ app.remove();
489
+ }
490
+ );
491
+
492
+ test('warnings are logged to the terminal again after an edit', async () => {
493
+ const app = createApp({
494
+ files: {
495
+ 'pages/warns.yaml':
496
+ 'id: warns\ntype: Box\nblocks:\n - id: misspelt\n type: Box\n propertys:\n title: Hello\n',
497
+ },
498
+ pages: { warns: { refPath: 'pages/warns.yaml' } },
499
+ });
500
+ const context = app.context();
501
+ const warn = jest.spyOn(context.logger, 'warn').mockImplementation(() => {});
502
+ const loggedWarnings = () =>
503
+ warn.mock.calls.filter(([warning]) => warning?.message?.includes('propertys')).length;
504
+
505
+ await app.request('warns');
506
+ expect(loggedWarnings()).toBe(1);
507
+
508
+ app.edit(
509
+ 'pages/warns.yaml',
510
+ `${fs.readFileSync(path.join(app.configDirectory, 'pages/warns.yaml'))}# edited\n`
511
+ );
512
+ app.signalChange();
513
+ await app.request('warns');
514
+
515
+ expect(loggedWarnings()).toBe(2);
516
+ warn.mockRestore();
517
+ app.remove();
518
+ });
519
+
520
+ test('a page with an unknown block type does not fail another page, and fixing it fixes the page on the kept context', async () => {
521
+ const app = createApp({
522
+ files: {
523
+ 'pages/broken.yaml': 'id: broken\ntype: Box\nblocks:\n - id: typo\n type: Buton\n',
524
+ 'pages/fine.yaml': 'id: fine\ntype: Box\n',
525
+ },
526
+ pages: {
527
+ broken: { refPath: 'pages/broken.yaml' },
528
+ fine: { refPath: 'pages/fine.yaml' },
529
+ },
530
+ });
531
+ const context = app.context();
532
+
533
+ await expect(app.request('broken')).rejects.toThrow('Buton');
534
+ expect(await app.request('fine')).toBe('built');
535
+
536
+ app.edit('pages/broken.yaml', 'id: broken\ntype: Box\nblocks:\n - id: typo\n type: Button\n');
537
+ app.signalChange();
538
+
539
+ expect(await app.request('broken')).toBe('built');
540
+ expect(await app.request('fine')).toBe('served');
541
+ expect(app.context()).toBe(context);
542
+ app.remove();
543
+ });
@@ -0,0 +1,36 @@
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
+ // Removes the jitMaps files of every build context but the one whose file
21
+ // names start with `keep`, so the files on disk stay bounded by the pages
22
+ // built since the last two page edits. A page build's temporary file (written
23
+ // whole, then renamed to .json) is left for that build to rename.
24
+ function pruneJitMaps({ buildDirectory, keep }) {
25
+ const jitMapsDirectory = path.join(buildDirectory, 'jitMaps');
26
+ if (!fs.existsSync(jitMapsDirectory)) {
27
+ return;
28
+ }
29
+ for (const file of fs.readdirSync(jitMapsDirectory)) {
30
+ if (file.endsWith('.json') && !file.startsWith(keep)) {
31
+ fs.rmSync(path.join(jitMapsDirectory, file), { force: true });
32
+ }
33
+ }
34
+ }
35
+
36
+ export default pruneJitMaps;
@@ -0,0 +1,55 @@
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 pruneJitMaps from './pruneJitMaps.js';
22
+
23
+ let buildDirectory;
24
+
25
+ beforeEach(() => {
26
+ buildDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-prune-jit-maps-'));
27
+ fs.mkdirSync(path.join(buildDirectory, 'jitMaps'));
28
+ });
29
+
30
+ afterEach(() => {
31
+ fs.rmSync(buildDirectory, { recursive: true, force: true });
32
+ });
33
+
34
+ test('pruneJitMaps keeps the named context and temporary files, removing every other jitMaps file', () => {
35
+ for (const file of [
36
+ 'abc-1-1.json',
37
+ 'abc-2-1.json',
38
+ 'def-1-1.json',
39
+ '.abc-1-2.json.123.abcd1234.tmp',
40
+ ]) {
41
+ fs.writeFileSync(path.join(buildDirectory, 'jitMaps', file), '{}');
42
+ }
43
+
44
+ pruneJitMaps({ buildDirectory, keep: 'abc-2-' });
45
+
46
+ expect(fs.readdirSync(path.join(buildDirectory, 'jitMaps')).sort()).toEqual([
47
+ '.abc-1-2.json.123.abcd1234.tmp',
48
+ 'abc-2-1.json',
49
+ ]);
50
+ });
51
+
52
+ test('pruneJitMaps does nothing when no page has been built', () => {
53
+ fs.rmSync(path.join(buildDirectory, 'jitMaps'), { recursive: true });
54
+ expect(() => pruneJitMaps({ buildDirectory, keep: 'abc-1-' })).not.toThrow();
55
+ });
@@ -0,0 +1,33 @@
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
+
19
+ import { getDevInstancePath } from '@lowdefy/node-utils';
20
+
21
+ // The instance record as the manager last wrote it, without readDevInstance's
22
+ // liveness check: that check spawns ps to read the manager's start time, and
23
+ // the child that reads this is the manager's own child, which exits with it.
24
+ // Null when there is no record, or one mid-write.
25
+ function readInstanceRecord({ configDirectory }) {
26
+ try {
27
+ return JSON.parse(fs.readFileSync(getDevInstancePath({ configDirectory }), 'utf8'));
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ export default readInstanceRecord;