@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,78 @@
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
+ const actualBuildDev = await import('@lowdefy/build/dev');
23
+ const mockCreateContext = jest.fn(actualBuildDev.createContext);
24
+
25
+ jest.unstable_mockModule('@lowdefy/build/dev', () => ({
26
+ ...actualBuildDev,
27
+ createContext: mockCreateContext,
28
+ }));
29
+
30
+ const savedSkip = process.env.LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS;
31
+ let loadCount = 0;
32
+ let root;
33
+
34
+ // The build context is kept at module scope, so each case loads a fresh
35
+ // module instance.
36
+ async function createBuildContext() {
37
+ loadCount += 1;
38
+ const { getBuildContext } = await import(`./jitPageBuilder.js?case=${loadCount}`);
39
+ return getBuildContext(path.join(root, 'build'), path.join(root, 'config'));
40
+ }
41
+
42
+ beforeEach(() => {
43
+ delete process.env.LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS;
44
+ mockCreateContext.mockClear();
45
+ root = fs.mkdtempSync(path.join(os.tmpdir(), 'ldf-jit-skip-checks-'));
46
+ fs.mkdirSync(path.join(root, 'build'), { recursive: true });
47
+ fs.writeFileSync(
48
+ path.join(root, 'build', 'idCounter.json'),
49
+ JSON.stringify({ prefix: 'test_', counter: 0 })
50
+ );
51
+ });
52
+
53
+ afterEach(() => {
54
+ fs.rmSync(root, { recursive: true, force: true });
55
+ });
56
+
57
+ afterAll(() => {
58
+ if (savedSkip === undefined) {
59
+ delete process.env.LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS;
60
+ } else {
61
+ process.env.LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS = savedSkip;
62
+ }
63
+ });
64
+
65
+ test('the JIT build context skips plugin checks when LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS is true', async () => {
66
+ process.env.LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS = 'true';
67
+ const context = await createBuildContext();
68
+ expect(mockCreateContext).toHaveBeenCalledTimes(1);
69
+ expect(mockCreateContext.mock.calls[0][0].skipPluginChecks).toBe(true);
70
+ expect(context.skipPluginChecks).toBe(true);
71
+ });
72
+
73
+ test('the JIT build context runs plugin checks when LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS is unset', async () => {
74
+ const context = await createBuildContext();
75
+ expect(mockCreateContext).toHaveBeenCalledTimes(1);
76
+ expect(mockCreateContext.mock.calls[0][0].skipPluginChecks).toBe(false);
77
+ expect(context.skipPluginChecks).toBe(false);
78
+ });
@@ -0,0 +1,218 @@
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
+ import { ConfigError } from '@lowdefy/errors';
22
+ import { serializer } from '@lowdefy/helpers';
23
+
24
+ import buildPageIfNeeded, { getBuildContext } from './jitPageBuilder.js';
25
+ import createHandleError from './log/createHandleError.js';
26
+ import pageBuildRecords from './pageBuildRecords.js';
27
+ import readMergedMaps from './readMergedMaps.js';
28
+
29
+ // The dev page build context restores the tenant facts the skeleton build
30
+ // wrote, so page requests get the same tenant pipeline checks as a full build.
31
+ test('getBuildContext restores the scoped connections, walled collections and shared connections', () => {
32
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ldf-jit-context-'));
33
+ const buildDirectory = path.join(root, 'build');
34
+ fs.mkdirSync(buildDirectory, { recursive: true });
35
+ const walledKey = JSON.stringify(['MongoDBCollection', ['uri'], 'totals']);
36
+ const shared = {
37
+ connection: { type: 'MongoDBCollection', properties: { databaseUri: 'uri' } },
38
+ tenantTarget: { database: ['databaseUri'], collection: 'collection' },
39
+ };
40
+ fs.writeFileSync(
41
+ path.join(buildDirectory, 'idCounter.json'),
42
+ JSON.stringify({ prefix: 'test_', counter: 0 })
43
+ );
44
+ fs.writeFileSync(
45
+ path.join(buildDirectory, 'tenantTargets.json'),
46
+ serializer.serializeToString({
47
+ tenantConnectionIds: ['totals'],
48
+ walledTargets: [[walledKey, { connectionId: 'totals', field: 'organization_id' }]],
49
+ sharedTargets: [['orders_all', shared]],
50
+ })
51
+ );
52
+
53
+ const context = getBuildContext(buildDirectory, path.join(root, 'config'));
54
+
55
+ expect([...context.tenantConnectionIds]).toEqual(['totals']);
56
+ expect(context.walledTargets.get(walledKey)).toEqual({
57
+ connectionId: 'totals',
58
+ field: 'organization_id',
59
+ });
60
+ expect(context.sharedTargets.get('orders_all')).toEqual(shared);
61
+ });
62
+
63
+ // A JIT page build loads the build package's plugins on first use.
64
+ jest.setTimeout(30000);
65
+
66
+ function writeJson(filePath, data) {
67
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
68
+ fs.writeFileSync(filePath, JSON.stringify(data));
69
+ }
70
+
71
+ // A config build's output, as far as a JIT page build reads it.
72
+ function createApp({ pages }) {
73
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ldf-jit-maps-'));
74
+ const buildDirectory = path.join(root, 'server', 'build');
75
+ const configDirectory = path.join(root, 'config');
76
+ writeJson(path.join(buildDirectory, 'idCounter.json'), { prefix: 'cfg1_', counter: 10 });
77
+ writeJson(path.join(buildDirectory, 'keyMap.json'), {
78
+ cfg1_1: { key: 'root', '~r': 'cfg1_r' },
79
+ });
80
+ writeJson(path.join(buildDirectory, 'refMap.json'), {
81
+ cfg1_r: { parent: null, path: 'lowdefy.yaml' },
82
+ });
83
+ writeJson(path.join(buildDirectory, 'installedPluginPackages.json'), [
84
+ '@lowdefy/actions-core',
85
+ '@lowdefy/blocks-antd',
86
+ '@lowdefy/blocks-basic',
87
+ '@lowdefy/operators-js',
88
+ ]);
89
+ writeJson(path.join(buildDirectory, 'theme.json'), {});
90
+ const registry = {};
91
+ for (const [pageId, content] of Object.entries(pages)) {
92
+ fs.mkdirSync(path.join(configDirectory, 'pages'), { recursive: true });
93
+ fs.writeFileSync(path.join(configDirectory, 'pages', `${pageId}.yaml`), content);
94
+ registry[pageId] = {
95
+ pageId,
96
+ auth: { public: true },
97
+ refId: `ref-${pageId}`,
98
+ refPath: `pages/${pageId}.yaml`,
99
+ };
100
+ }
101
+ writeJson(path.join(buildDirectory, 'pageRegistry.json'), registry);
102
+ return { root, buildDirectory, configDirectory };
103
+ }
104
+
105
+ function listJitMaps(buildDirectory) {
106
+ const directory = path.join(buildDirectory, 'jitMaps');
107
+ return fs.existsSync(directory) ? fs.readdirSync(directory).sort() : [];
108
+ }
109
+
110
+ // A config build publish renames a new page registry into place.
111
+ function publishRegistry(buildDirectory) {
112
+ const registryPath = path.join(buildDirectory, 'pageRegistry.json');
113
+ const stagedPath = `${registryPath}.staged`;
114
+ fs.copyFileSync(registryPath, stagedPath);
115
+ fs.renameSync(stagedPath, registryPath);
116
+ }
117
+
118
+ test('a JIT page build leaves keyMap.json and refMap.json as the config build wrote them', async () => {
119
+ const { root, buildDirectory, configDirectory } = createApp({
120
+ pages: { home: 'id: home\ntype: Box\nblocks:\n - id: title\n type: Box\n' },
121
+ });
122
+ const keyMapBefore = fs.readFileSync(path.join(buildDirectory, 'keyMap.json'), 'utf8');
123
+ const refMapBefore = fs.readFileSync(path.join(buildDirectory, 'refMap.json'), 'utf8');
124
+
125
+ await buildPageIfNeeded({ pageId: 'home', buildDirectory, configDirectory });
126
+
127
+ expect(fs.readFileSync(path.join(buildDirectory, 'keyMap.json'), 'utf8')).toBe(keyMapBefore);
128
+ expect(fs.readFileSync(path.join(buildDirectory, 'refMap.json'), 'utf8')).toBe(refMapBefore);
129
+ const files = listJitMaps(buildDirectory);
130
+ expect(files).toHaveLength(1);
131
+ expect(files[0]).toMatch(/^[0-9a-f]{6}-\d+-1\.json$/);
132
+ const { keyMap } = await readMergedMaps({ buildDirectory });
133
+ const titleKey = Object.keys(keyMap).find((key) => keyMap[key].key?.includes('title'));
134
+ // Config build prefix, then this process's id: <cfg prefix><child id>_<counter>.
135
+ expect(titleKey).toMatch(/^cfg1_[0-9a-f]{6}_[0-9a-z]+$/);
136
+ fs.rmSync(root, { recursive: true, force: true });
137
+ });
138
+
139
+ test('an error a failing JIT page build throws after its keys were added resolves to its source line', async () => {
140
+ const { root, buildDirectory, configDirectory } = createApp({
141
+ pages: {
142
+ broken:
143
+ 'id: broken\ntype: Box\nblocks:\n - id: button\n type: Button\n events:\n' +
144
+ ' onClick:\n - id: act\n type: UndefinedAction\n',
145
+ },
146
+ });
147
+
148
+ const error = await buildPageIfNeeded({
149
+ pageId: 'broken',
150
+ buildDirectory,
151
+ configDirectory,
152
+ }).catch((caught) => caught);
153
+ const buildError = (error.buildErrors ?? [error]).find((item) => item.configKey);
154
+ const context = {
155
+ configDirectory,
156
+ logger: { error: jest.fn() },
157
+ readConfigFile: jest.fn(),
158
+ readMaps: () => readMergedMaps({ buildDirectory }),
159
+ };
160
+ // A fresh copy, as the request that logs it would see it.
161
+ const logged = new ConfigError(buildError.message, { configKey: buildError.configKey });
162
+ await createHandleError({ context })(logged);
163
+
164
+ expect(logged.source).toBe(`${path.join(configDirectory, 'pages', 'broken.yaml')}:8`);
165
+ fs.rmSync(root, { recursive: true, force: true });
166
+ });
167
+
168
+ test('after two config publishes only the current and previous build contexts keep jitMaps files', async () => {
169
+ const { root, buildDirectory, configDirectory } = createApp({
170
+ pages: {
171
+ a: 'id: a\ntype: Box\n',
172
+ b: 'id: b\ntype: Box\n',
173
+ c: 'id: c\ntype: Box\n',
174
+ },
175
+ });
176
+
177
+ await buildPageIfNeeded({ pageId: 'a', buildDirectory, configDirectory });
178
+ const [first] = listJitMaps(buildDirectory);
179
+ publishRegistry(buildDirectory);
180
+ await buildPageIfNeeded({ pageId: 'b', buildDirectory, configDirectory });
181
+ publishRegistry(buildDirectory);
182
+ await buildPageIfNeeded({ pageId: 'c', buildDirectory, configDirectory });
183
+
184
+ const files = listJitMaps(buildDirectory);
185
+ expect(files).toHaveLength(2);
186
+ expect(files).not.toContain(first);
187
+ const generations = files.map((file) => Number(file.split('-')[1]));
188
+ expect(generations[1] - generations[0]).toBe(1);
189
+ fs.rmSync(root, { recursive: true, force: true });
190
+ });
191
+
192
+ test('a page build that loads app code is recorded as having run it, a YAML-only one is not', async () => {
193
+ const { root, buildDirectory, configDirectory } = createApp({
194
+ pages: {
195
+ plain: 'id: plain\ntype: Box\nblocks:\n - _ref: blocks/shared.yaml\n',
196
+ coded: 'id: coded\ntype: Box\nblocks:\n - _ref: blocks/banner.js\n',
197
+ },
198
+ });
199
+ fs.writeFileSync(path.join(configDirectory, 'package.json'), '{"type":"module"}');
200
+ fs.mkdirSync(path.join(configDirectory, 'blocks'));
201
+ fs.writeFileSync(path.join(configDirectory, 'blocks', 'shared.yaml'), 'id: shared\ntype: Box\n');
202
+ fs.writeFileSync(
203
+ path.join(configDirectory, 'blocks', 'banner.js'),
204
+ "export default { id: 'banner', type: 'Box' };\n"
205
+ );
206
+
207
+ await buildPageIfNeeded({ pageId: 'plain', buildDirectory, configDirectory });
208
+ await buildPageIfNeeded({ pageId: 'coded', buildDirectory, configDirectory });
209
+
210
+ const plain = pageBuildRecords.get('plain');
211
+ expect(plain.ranAppCode).toBe(false);
212
+ expect([...plain.files.keys()]).toEqual([
213
+ path.join(configDirectory, 'pages', 'plain.yaml'),
214
+ path.join(configDirectory, 'blocks', 'shared.yaml'),
215
+ ]);
216
+ expect(pageBuildRecords.get('coded').ranAppCode).toBe(true);
217
+ fs.rmSync(root, { recursive: true, force: true });
218
+ });
@@ -0,0 +1,37 @@
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
+ // Every cookie the dev server's own headless browser sets on a journey
18
+ // context. Each value is "<journeyActorToken>.<payload>", so only that browser
19
+ // can set one: a cookie from anywhere else fails verification and is ignored.
20
+ // The production server never reads any of them.
21
+ //
22
+ // kind: an inline cookie carries its whole meaning in the payload (an actor's
23
+ // client address); a registry cookie carries the id of an entry the dev server
24
+ // keeps in memory for the length of one journey run.
25
+ //
26
+ // loopback: a detached CallApi loops back through /api/detached/* as a fresh
27
+ // request, so a cookie whose state must reach that request (the data set, the
28
+ // mutant) is forwarded on it - see forwardJourneyCookies. The actor and
29
+ // recording cookies describe the browser, not the work, and stay behind.
30
+ const JOURNEY_COOKIES = {
31
+ actor: { name: 'lowdefy_journey_actor', kind: 'inline', loopback: false },
32
+ recording: { name: 'lowdefy_recording', kind: 'inline', loopback: false },
33
+ data: { name: 'lowdefy_journey_data', kind: 'registry', loopback: true },
34
+ mutant: { name: 'lowdefy_journey_mutant', kind: 'registry', loopback: true },
35
+ };
36
+
37
+ export default JOURNEY_COOKIES;
@@ -0,0 +1,46 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { type } from '@lowdefy/helpers';
18
+
19
+ import JOURNEY_COOKIES from './JOURNEY_COOKIES.js';
20
+ import readJourneyCookie from './readJourneyCookie.js';
21
+ import { journeyActorToken } from '../auth/journeyActor.js';
22
+
23
+ // The Cookie header a detached loopback call carries, so its target runs with
24
+ // the same data set and mutant as the journey request that dispatched it. Only
25
+ // verified cookies are forwarded. `dataSessionId` names a data session a dev
26
+ // tool call opened itself (run_request, run_endpoint with data): that call has
27
+ // no data cookie, but its detached hop must still read the data set, never the
28
+ // app's own database.
29
+ function forwardJourneyCookies({ cookieHeader, dataSessionId }) {
30
+ return Object.values(JOURNEY_COOKIES)
31
+ .filter((cookie) => cookie.loopback)
32
+ .map((cookie) => {
33
+ if (cookie === JOURNEY_COOKIES.data && !type.isNone(dataSessionId)) {
34
+ return `${cookie.name}=${journeyActorToken}.${dataSessionId}`;
35
+ }
36
+ const payload = readJourneyCookie({ cookieHeader, name: cookie.name });
37
+ if (payload === null) {
38
+ return null;
39
+ }
40
+ return `${cookie.name}=${journeyActorToken}.${payload}`;
41
+ })
42
+ .filter((pair) => pair !== null)
43
+ .join('; ');
44
+ }
45
+
46
+ export default forwardJourneyCookies;
@@ -0,0 +1,52 @@
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 crypto from 'node:crypto';
18
+
19
+ import { journeyActorToken } from '../auth/journeyActor.js';
20
+
21
+ const tokenBuffer = Buffer.from(journeyActorToken);
22
+
23
+ function findCookieValue({ cookieHeader, name }) {
24
+ for (const pair of (cookieHeader ?? '').split(';')) {
25
+ const separator = pair.indexOf('=');
26
+ if (separator !== -1 && pair.slice(0, separator).trim() === name) {
27
+ return pair.slice(separator + 1).trim();
28
+ }
29
+ }
30
+ return null;
31
+ }
32
+
33
+ // Returns the payload of a journey cookie the dev server's own headless
34
+ // browser wrote (writeJourneyCookie), or null when the cookie is absent or
35
+ // its token is not this process's journeyActorToken.
36
+ function readJourneyCookie({ cookieHeader, name }) {
37
+ const value = findCookieValue({ cookieHeader, name });
38
+ if (value === null) {
39
+ return null;
40
+ }
41
+ const separator = value.indexOf('.');
42
+ if (separator === -1) {
43
+ return null;
44
+ }
45
+ const token = Buffer.from(value.slice(0, separator));
46
+ if (token.length !== tokenBuffer.length || !crypto.timingSafeEqual(token, tokenBuffer)) {
47
+ return null;
48
+ }
49
+ return value.slice(separator + 1);
50
+ }
51
+
52
+ export default readJourneyCookie;
@@ -0,0 +1,46 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { type } from '@lowdefy/helpers';
18
+
19
+ import JOURNEY_COOKIES from './JOURNEY_COOKIES.js';
20
+ import { journeyActorToken } from '../auth/journeyActor.js';
21
+
22
+ const names = new Set(Object.values(JOURNEY_COOKIES).map((cookie) => cookie.name));
23
+
24
+ // The object a journey context passes to Playwright's context.addCookies.
25
+ // httpOnly, so page JavaScript cannot read the token. Lax, not Strict: a
26
+ // journey opens an emailed link from a data: URL, a cross-site navigation that
27
+ // a Strict cookie does not ride.
28
+ function writeJourneyCookie({ name, payload, origin }) {
29
+ if (!names.has(name)) {
30
+ throw new Error(`"${name}" is not a journey cookie.`);
31
+ }
32
+ if (!type.isString(payload)) {
33
+ throw new Error(
34
+ `Journey cookie payload must be a string. Received ${JSON.stringify(payload)}.`
35
+ );
36
+ }
37
+ return {
38
+ name,
39
+ value: `${journeyActorToken}.${payload}`,
40
+ url: origin,
41
+ httpOnly: true,
42
+ sameSite: 'Lax',
43
+ };
44
+ }
45
+
46
+ export default writeJourneyCookie;
@@ -0,0 +1,22 @@
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 JOURNEY_COOKIES from './journeyCookies/JOURNEY_COOKIES.js';
18
+ import forwardJourneyCookies from './journeyCookies/forwardJourneyCookies.js';
19
+ import readJourneyCookie from './journeyCookies/readJourneyCookie.js';
20
+ import writeJourneyCookie from './journeyCookies/writeJourneyCookie.js';
21
+
22
+ export { JOURNEY_COOKIES, forwardJourneyCookies, readJourneyCookie, writeJourneyCookie };
@@ -0,0 +1,145 @@
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 'node:fs';
18
+ import path from 'node:path';
19
+ import { fileURLToPath } from 'node:url';
20
+
21
+ import { journeyActorToken } from './auth/journeyActor.js';
22
+ import {
23
+ JOURNEY_COOKIES,
24
+ forwardJourneyCookies,
25
+ readJourneyCookie,
26
+ writeJourneyCookie,
27
+ } from './journeyCookies.js';
28
+
29
+ const origin = 'http://localhost:3111';
30
+ const forgedToken = '0'.repeat(journeyActorToken.length);
31
+
32
+ function header(cookies) {
33
+ return cookies.map(({ name, value }) => `${name}=${value}`).join('; ');
34
+ }
35
+
36
+ test('writeJourneyCookie writes a signed, httpOnly, Lax cookie scoped to the origin', () => {
37
+ expect(
38
+ writeJourneyCookie({ name: JOURNEY_COOKIES.mutant.name, payload: 'run1', origin })
39
+ ).toEqual({
40
+ name: 'lowdefy_journey_mutant',
41
+ value: `${journeyActorToken}.run1`,
42
+ url: origin,
43
+ httpOnly: true,
44
+ sameSite: 'Lax',
45
+ });
46
+ });
47
+
48
+ test('writeJourneyCookie throws for a name outside the journey cookie table', () => {
49
+ expect(() => writeJourneyCookie({ name: 'session', payload: 'x', origin })).toThrow(
50
+ '"session" is not a journey cookie.'
51
+ );
52
+ });
53
+
54
+ test('readJourneyCookie returns the payload of a cookie writeJourneyCookie wrote', () => {
55
+ const cookie = writeJourneyCookie({
56
+ name: JOURNEY_COOKIES.actor.name,
57
+ payload: '203.0.113.7',
58
+ origin,
59
+ });
60
+ expect(
61
+ readJourneyCookie({
62
+ cookieHeader: `session=abc; ${header([cookie])}; other=1`,
63
+ name: JOURNEY_COOKIES.actor.name,
64
+ })
65
+ ).toEqual('203.0.113.7');
66
+ });
67
+
68
+ test.each([
69
+ ['a wrong token', `lowdefy_journey_mutant=${forgedToken}.run1`],
70
+ ['a shorter token', 'lowdefy_journey_mutant=abc.run1'],
71
+ ['a missing separator', `lowdefy_journey_mutant=${journeyActorToken}`],
72
+ ['only another cookie name', `lowdefy_journey_data=${journeyActorToken}.run1`],
73
+ ['a cookie whose name only ends in the name', `x_lowdefy_journey_mutant=${journeyActorToken}.r`],
74
+ ['no cookie header', undefined],
75
+ ['an empty cookie header', ''],
76
+ ])('readJourneyCookie returns null for %s', (_, cookieHeader) => {
77
+ expect(readJourneyCookie({ cookieHeader, name: JOURNEY_COOKIES.mutant.name })).toBeNull();
78
+ });
79
+
80
+ test('forwardJourneyCookies joins the verified data and mutant cookies and leaves out the rest', () => {
81
+ const cookieHeader = header([
82
+ { name: 'session', value: 'abc' },
83
+ writeJourneyCookie({ name: JOURNEY_COOKIES.actor.name, payload: '203.0.113.7', origin }),
84
+ writeJourneyCookie({ name: JOURNEY_COOKIES.recording.name, payload: 'rec', origin }),
85
+ writeJourneyCookie({ name: JOURNEY_COOKIES.data.name, payload: 'data1', origin }),
86
+ writeJourneyCookie({ name: JOURNEY_COOKIES.mutant.name, payload: 'mutant1', origin }),
87
+ ]);
88
+ expect(forwardJourneyCookies({ cookieHeader })).toEqual(
89
+ `lowdefy_journey_data=${journeyActorToken}.data1; lowdefy_journey_mutant=${journeyActorToken}.mutant1`
90
+ );
91
+ });
92
+
93
+ test('forwardJourneyCookies leaves out a loopback cookie that fails verification', () => {
94
+ const cookieHeader = header([
95
+ { name: JOURNEY_COOKIES.data.name, value: `${forgedToken}.data1` },
96
+ writeJourneyCookie({ name: JOURNEY_COOKIES.mutant.name, payload: 'mutant1', origin }),
97
+ ]);
98
+ expect(forwardJourneyCookies({ cookieHeader })).toEqual(
99
+ `lowdefy_journey_mutant=${journeyActorToken}.mutant1`
100
+ );
101
+ });
102
+
103
+ test('forwardJourneyCookies forwards a data session the call opened itself in place of a data cookie', () => {
104
+ const cookieHeader = header([
105
+ writeJourneyCookie({ name: JOURNEY_COOKIES.data.name, payload: 'data1', origin }),
106
+ writeJourneyCookie({ name: JOURNEY_COOKIES.mutant.name, payload: 'mutant1', origin }),
107
+ ]);
108
+ expect(forwardJourneyCookies({ cookieHeader, dataSessionId: 'own1' })).toEqual(
109
+ `lowdefy_journey_data=${journeyActorToken}.own1; lowdefy_journey_mutant=${journeyActorToken}.mutant1`
110
+ );
111
+ expect(forwardJourneyCookies({ cookieHeader: undefined, dataSessionId: 'own1' })).toEqual(
112
+ `lowdefy_journey_data=${journeyActorToken}.own1`
113
+ );
114
+ });
115
+
116
+ test('forwardJourneyCookies returns an empty string with no journey cookies', () => {
117
+ expect(forwardJourneyCookies({ cookieHeader: undefined })).toEqual('');
118
+ expect(forwardJourneyCookies({ cookieHeader: 'session=abc' })).toEqual('');
119
+ });
120
+
121
+ // One writer, so a wrapper (data set, mutant) can never drop another's cookie
122
+ // from the detached hop.
123
+ test('createLowdefyContext is the only writer of loopbackHeaders in server-dev', () => {
124
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
125
+ const writers = [];
126
+ function walk(directory) {
127
+ fs.readdirSync(directory, { withFileTypes: true }).forEach((entry) => {
128
+ const entryPath = path.join(directory, entry.name);
129
+ if (entry.isDirectory()) {
130
+ walk(entryPath);
131
+ return;
132
+ }
133
+ if (!/\.(js|mjs|jsx)$/.test(entry.name) || /\.test\.mjs$/.test(entry.name)) {
134
+ return;
135
+ }
136
+ if (/loopbackHeaders\s*(=[^=]|\]\s*=|:)/.test(fs.readFileSync(entryPath, 'utf8'))) {
137
+ writers.push(path.relative(packageRoot, entryPath));
138
+ }
139
+ });
140
+ }
141
+ ['lib', 'src', 'client', 'manager'].forEach((directory) =>
142
+ walk(path.join(packageRoot, directory))
143
+ );
144
+ expect(writers).toEqual([path.join('lib', 'server', 'createLowdefyContext.js')]);
145
+ });