@lowdefy/server-dev 6.1.0 → 7.1.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 (624) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +2 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +222 -36
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +78 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +727 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/describeTargetKey.js +29 -0
  104. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  105. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  106. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  107. package/lib/docs/dataSets/duplicateIndexName.js +24 -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 +52 -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 +133 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +493 -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 +83 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +251 -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/sessionDatabaseName.js +32 -0
  133. package/lib/docs/dataSets/sweepOrphanDatabases.js +36 -0
  134. package/lib/docs/dataSets/test/app/lowdefy.yaml +225 -0
  135. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  136. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +12 -0
  137. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  138. package/lib/docs/devEventBus.js +136 -0
  139. package/lib/docs/devEventBus.test.mjs +269 -0
  140. package/lib/docs/devToolDefinitions.js +529 -0
  141. package/lib/docs/docSources.js +21 -0
  142. package/lib/docs/docs.test.mjs +171 -15
  143. package/lib/docs/docsIndex.test.mjs +377 -0
  144. package/lib/docs/evalOperator.js +5 -3
  145. package/lib/docs/evalOperatorHeadless.js +84 -12
  146. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  147. package/lib/docs/evalOperatorInTab.js +2 -1
  148. package/lib/docs/findConfig.js +95 -49
  149. package/lib/docs/fitResponse.js +81 -0
  150. package/lib/docs/fitResponse.test.mjs +128 -0
  151. package/lib/docs/formatFeedback.js +26 -5
  152. package/lib/docs/formatFeedback.test.mjs +48 -0
  153. package/lib/docs/getAppMap.js +7 -1
  154. package/lib/docs/getBrowser.js +150 -80
  155. package/lib/docs/getBrowser.test.mjs +407 -16
  156. package/lib/docs/getBuildId.js +61 -0
  157. package/lib/docs/getBuildId.test.mjs +84 -0
  158. package/lib/docs/getBuildStatus.js +81 -7
  159. package/lib/docs/getBuildStatus.test.mjs +229 -0
  160. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  161. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  162. package/lib/docs/getCoreDoc.js +9 -1
  163. package/lib/docs/getDoc.js +100 -0
  164. package/lib/docs/getDocsIndex.js +152 -0
  165. package/lib/docs/getHazards.js +115 -0
  166. package/lib/docs/getHazards.test.mjs +175 -0
  167. package/lib/docs/getMailOutboxDirectory.js +26 -0
  168. package/lib/docs/getOverview.js +79 -13
  169. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  170. package/lib/docs/getPageBuildStatus.js +46 -0
  171. package/lib/docs/getPluginDoc.js +21 -23
  172. package/lib/docs/getSchema.js +33 -1
  173. package/lib/docs/getStaleStatus.js +36 -0
  174. package/lib/docs/getStaleStatus.test.mjs +74 -0
  175. package/lib/docs/guardJourneyOrigin.js +39 -0
  176. package/lib/docs/inspectState.js +5 -3
  177. package/lib/docs/inspectStateFromTab.js +2 -2
  178. package/lib/docs/inspectStateHeadless.js +89 -27
  179. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  180. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  181. package/lib/docs/installHeadlessShell.js +123 -0
  182. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  183. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  184. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  185. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  186. package/lib/docs/isPageReady.js +77 -0
  187. package/lib/docs/isPageReady.test.mjs +235 -0
  188. package/lib/docs/isRunErrorEntry.js +25 -0
  189. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  190. package/lib/docs/journeyRunRecording.js +35 -0
  191. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  192. package/lib/docs/keepEventStreamAlive.js +68 -0
  193. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  194. package/lib/docs/launchBrowser.js +83 -0
  195. package/lib/docs/launchBrowser.test.mjs +175 -0
  196. package/lib/docs/listModuleDocEntries.js +69 -0
  197. package/lib/docs/listPackageDocFiles.js +67 -0
  198. package/lib/docs/listPluginDocEntries.js +111 -0
  199. package/lib/docs/listPlugins.js +9 -58
  200. package/lib/docs/listTypes.js +14 -6
  201. package/lib/docs/loadFrameworkHazards.js +40 -0
  202. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  203. package/lib/docs/loadState.js +103 -35
  204. package/lib/docs/makeModuleManifestDoc.js +174 -0
  205. package/lib/docs/matchExercisedPages.js +35 -0
  206. package/lib/docs/matchJourneyRoute.js +69 -0
  207. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  208. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  209. package/lib/docs/nextJourneySequence.js +30 -0
  210. package/lib/docs/noBrowserError.js +22 -0
  211. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  212. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  213. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  214. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  215. package/lib/docs/observe/evaluateInvariants.js +183 -0
  216. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  217. package/lib/docs/observe/findingKey.js +29 -0
  218. package/lib/docs/observe/hasNoEffect.js +31 -0
  219. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  220. package/lib/docs/observe/installStepObserver.js +72 -0
  221. package/lib/docs/observe/isAppApiUrl.js +33 -0
  222. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  223. package/lib/docs/observe/readStepObserver.js +42 -0
  224. package/lib/docs/observe/relativeSource.js +29 -0
  225. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  226. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  227. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  228. package/lib/docs/observe/usesSearchStage.js +45 -0
  229. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  230. package/lib/docs/observe/watchJourneyContext.js +75 -0
  231. package/lib/docs/observe/withAppErrors.js +31 -0
  232. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  233. package/lib/docs/openJourney.js +94 -0
  234. package/lib/docs/openJourneyEmail.js +41 -0
  235. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  236. package/lib/docs/pageBlocks.js +47 -0
  237. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  238. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  239. package/lib/docs/rails/authEngineActions.js +53 -0
  240. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  241. package/lib/docs/rails/authEngineMethods.js +52 -0
  242. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  243. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  244. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  245. package/lib/docs/rails/checkClickRails.js +56 -0
  246. package/lib/docs/rails/createDataSetRails.js +82 -0
  247. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  248. package/lib/docs/rails/findExternalBlocks.js +147 -0
  249. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  250. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  251. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  252. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  253. package/lib/docs/readConnectionPlugins.js +26 -0
  254. package/lib/docs/readDevAuthMode.js +32 -0
  255. package/lib/docs/readDocEntry.js +40 -0
  256. package/lib/docs/readDocTitle.js +26 -0
  257. package/lib/docs/readJourneyEmailMatch.js +57 -0
  258. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  259. package/lib/docs/readJourneySession.js +56 -0
  260. package/lib/docs/readMailOutbox.js +37 -0
  261. package/lib/docs/readPagePath.js +27 -0
  262. package/lib/docs/readPluginPackages.js +99 -0
  263. package/lib/docs/readProxyBuildWait.js +38 -0
  264. package/lib/docs/readRoutineSteps.js +45 -0
  265. package/lib/docs/registerShownTextEngine.js +118 -0
  266. package/lib/docs/requestRestart.js +30 -0
  267. package/lib/docs/requestRestart.test.mjs +49 -0
  268. package/lib/docs/resolvePageInstance.js +46 -0
  269. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  270. package/lib/docs/resolveSource.js +11 -7
  271. package/lib/docs/resolveSource.test.mjs +1 -1
  272. package/lib/docs/resolveToolCaller.js +62 -0
  273. package/lib/docs/reviewPage.js +51 -0
  274. package/lib/docs/reviewPageBuilds.js +56 -0
  275. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  276. package/lib/docs/runCheck.js +75 -0
  277. package/lib/docs/runCheck.test.mjs +91 -0
  278. package/lib/docs/runCheckChild.js +49 -0
  279. package/lib/docs/runEndpoint.js +185 -0
  280. package/lib/docs/runEndpoint.test.mjs +509 -0
  281. package/lib/docs/runErrorBuffers.js +99 -0
  282. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  283. package/lib/docs/runJourney.js +347 -0
  284. package/lib/docs/runJourney.test.mjs +2383 -0
  285. package/lib/docs/runJourneyAsUsers.js +44 -0
  286. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  287. package/lib/docs/runJourneySteps.js +1320 -0
  288. package/lib/docs/runRequest.js +100 -59
  289. package/lib/docs/runRequest.test.mjs +190 -0
  290. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  291. package/lib/docs/scaffoldPage.js +3 -1
  292. package/lib/docs/screenshotPage.js +163 -11
  293. package/lib/docs/screenshotPage.test.mjs +263 -1
  294. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  295. package/lib/docs/searchDocs.js +82 -30
  296. package/lib/docs/searchDocs.test.mjs +100 -0
  297. package/lib/docs/searchIcons.js +90 -0
  298. package/lib/docs/searchIcons.test.mjs +127 -0
  299. package/lib/docs/selectExercised.js +39 -0
  300. package/lib/docs/selectFinalState.js +66 -0
  301. package/lib/docs/selectFinalState.test.mjs +51 -0
  302. package/lib/docs/serverErrorStore.js +54 -0
  303. package/lib/docs/serverErrorStore.test.mjs +50 -0
  304. package/lib/docs/setupTestFixtures.mjs +133 -39
  305. package/lib/docs/shownTextSelector.js +28 -0
  306. package/lib/docs/snapshotState.js +13 -5
  307. package/lib/docs/splitSearchTerms.js +62 -0
  308. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  309. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  310. package/lib/docs/tabAvailable.js +6 -5
  311. package/lib/docs/tabChannel.js +127 -21
  312. package/lib/docs/tabChannel.test.mjs +187 -29
  313. package/lib/docs/unsettledPageNote.js +24 -0
  314. package/lib/docs/validateJourneyMail.js +36 -0
  315. package/lib/docs/validateJourneyTimeout.js +38 -0
  316. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  317. package/lib/docs/validateRecordingParam.js +42 -0
  318. package/lib/docs/validateStateSelection.js +33 -0
  319. package/lib/docs/validateStateSelection.test.mjs +33 -0
  320. package/lib/docs/validateViewport.js +51 -0
  321. package/lib/docs/validateViewport.test.mjs +54 -0
  322. package/lib/docs/waitForBuild.js +50 -0
  323. package/lib/docs/waitForBuild.test.mjs +75 -0
  324. package/lib/docs/waitForJourneyEmail.js +77 -0
  325. package/lib/docs/withBrowserSlot.js +45 -0
  326. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  327. package/lib/docs/withDataSession.js +53 -0
  328. package/lib/server/applyDataSetRedirect.js +92 -0
  329. package/lib/server/applyDataSetRedirect.test.mjs +446 -0
  330. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +207 -0
  331. package/lib/server/applyDataSetRedirectWalled.test.mjs +134 -0
  332. package/lib/server/auth/createSystemContext.js +73 -0
  333. package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
  334. package/lib/server/auth/getHeadlessUser.js +9 -8
  335. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  336. package/lib/server/auth/getMockUser.js +18 -14
  337. package/lib/server/auth/{session.js → getStrategies.js} +17 -16
  338. package/lib/server/auth/headlessUser.js +3 -2
  339. package/lib/server/auth/journeyActor.js +35 -0
  340. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  341. package/lib/server/checkPageRecord.js +44 -0
  342. package/lib/server/contextMapBudget.js +24 -0
  343. package/lib/server/contextMapBudget.test.mjs +98 -0
  344. package/lib/server/continueJitKeys.js +28 -0
  345. package/lib/server/continueJitKeys.test.mjs +43 -0
  346. package/lib/server/createDataSessionEndedError.js +33 -0
  347. package/lib/server/createDataSetRefusal.js +26 -0
  348. package/lib/server/createLowdefyContext.js +168 -41
  349. package/lib/server/createLowdefyContext.test.mjs +331 -8
  350. package/lib/server/getClientAddress.js +51 -0
  351. package/lib/server/getClientAddress.test.mjs +56 -0
  352. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  353. package/lib/server/hashConfigContent.js +26 -0
  354. package/lib/server/importAppCode.test.mjs +57 -0
  355. package/lib/server/isRoutineStep.js +27 -0
  356. package/lib/server/jitPageBuilder.js +309 -126
  357. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  358. package/lib/server/jitPageBuilder.test.mjs +218 -0
  359. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  360. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  361. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  362. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  363. package/lib/server/journeyCookies.js +22 -0
  364. package/lib/server/journeyCookies.test.mjs +145 -0
  365. package/lib/server/loadDynamicJsMap.js +47 -0
  366. package/lib/server/log/createHandleError.js +38 -2
  367. package/lib/server/log/createHandleError.test.mjs +125 -2
  368. package/lib/server/log/createLogger.js +3 -0
  369. package/lib/server/log/createLogger.test.mjs +15 -0
  370. package/lib/server/log/logRequest.js +2 -3
  371. package/lib/server/mutantDataSetWrappers.test.mjs +115 -0
  372. package/lib/server/mutants/applyMutant.js +39 -0
  373. package/lib/server/mutants/artifactPatterns.js +25 -0
  374. package/lib/server/mutants/buildAnchor.js +45 -0
  375. package/lib/server/mutants/collectSharedIds.js +41 -0
  376. package/lib/server/mutants/configPathSegments.js +38 -0
  377. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  378. package/lib/server/mutants/describeEvent.js +22 -0
  379. package/lib/server/mutants/enumerateArtifact.js +56 -0
  380. package/lib/server/mutants/findNodeByKey.js +45 -0
  381. package/lib/server/mutants/findNodeByPointer.js +49 -0
  382. package/lib/server/mutants/getArtifactKind.js +32 -0
  383. package/lib/server/mutants/groupMutantCopies.js +56 -0
  384. package/lib/server/mutants/hashArtifact.js +25 -0
  385. package/lib/server/mutants/hashMutantTarget.js +34 -0
  386. package/lib/server/mutants/listMutants.js +122 -0
  387. package/lib/server/mutants/listMutants.test.mjs +502 -0
  388. package/lib/server/mutants/mutantId.js +30 -0
  389. package/lib/server/mutants/mutantRuns.js +57 -0
  390. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  391. package/lib/server/mutants/operators/dropAction.js +47 -0
  392. package/lib/server/mutants/operators/dropBlock.js +44 -0
  393. package/lib/server/mutants/operators/dropPayload.js +52 -0
  394. package/lib/server/mutants/operators/dropStep.js +44 -0
  395. package/lib/server/mutants/operators/flipVisible.js +53 -0
  396. package/lib/server/mutants/operators/index.js +37 -0
  397. package/lib/server/mutants/operators/retargetLink.js +72 -0
  398. package/lib/server/mutants/operators/skipValidate.js +77 -0
  399. package/lib/server/mutants/operators/swapIf.js +112 -0
  400. package/lib/server/mutants/operators.test.mjs +396 -0
  401. package/lib/server/mutants/positionFreePath.js +38 -0
  402. package/lib/server/mutants/removeFromParent.js +28 -0
  403. package/lib/server/mutants/test/fixtures.mjs +173 -0
  404. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  405. package/lib/server/mutants/validateMutantParam.js +69 -0
  406. package/lib/server/mutants/validateMutantsBody.js +88 -0
  407. package/lib/server/mutants/walkArtifact.js +179 -0
  408. package/lib/server/pageBuildRecords.js +131 -0
  409. package/lib/server/pageBuildRecords.test.mjs +258 -0
  410. package/lib/server/pageCache.mjs +22 -7
  411. package/lib/server/pageCache.test.mjs +22 -7
  412. package/lib/server/pageRebuilds.test.mjs +543 -0
  413. package/lib/server/pruneJitMaps.js +36 -0
  414. package/lib/server/pruneJitMaps.test.mjs +55 -0
  415. package/lib/server/readInstanceRecord.js +33 -0
  416. package/lib/server/readInstanceRecord.test.mjs +99 -0
  417. package/lib/server/readMergedMaps.js +86 -0
  418. package/lib/server/readMergedMaps.test.mjs +79 -0
  419. package/lib/server/recording/appendRecords.js +33 -0
  420. package/lib/server/recording/errorRecordingStamp.js +34 -0
  421. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  422. package/lib/server/recording/isRecordingEnabled.js +31 -0
  423. package/lib/server/recording/readRecordingCookie.js +71 -0
  424. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  425. package/lib/server/recording/recordingCookiePayload.js +33 -0
  426. package/lib/server/recording/servedBuilds.js +46 -0
  427. package/lib/server/recording/servedBuilds.test.mjs +42 -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 +25 -3
  439. package/manager/processes/initialBuild.mjs +17 -1
  440. package/manager/processes/initialBuild.test.mjs +104 -0
  441. package/manager/processes/lowdefyBuild.mjs +31 -7
  442. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +72 -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 +60 -47
  527. package/package.original.json +60 -47
  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 +173 -3
  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 +3 -1
  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/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  621. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  622. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  623. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  624. package/src/routes/apiPage.js +0 -37
@@ -15,6 +15,7 @@
15
15
  */
16
16
 
17
17
  import { jest } from '@jest/globals';
18
+ import { callerStamp } from '@lowdefy/helpers';
18
19
 
19
20
  // usePageConfig imports swr and useMutateCache only for the hook; the fetcher
20
21
  // under test needs neither. Mock them so the module loads in the node test env.
@@ -24,39 +25,223 @@ jest.unstable_mockModule('./useMutateCache.js', () => ({
24
25
  getReloadVersion: jest.fn(),
25
26
  }));
26
27
 
27
- const { fetchPageConfig } = await import('./usePageConfig.js');
28
+ const { getNavVersion, getReloadVersion } = await import('./useMutateCache.js');
29
+ const { fetchPageConfig, getPageConfigKey, recordDynamicPage } = await import('./usePageConfig.js');
28
30
 
29
- function mockJsonResponse(body) {
30
- return { status: 200, ok: true, json: async () => body };
31
+ beforeEach(() => {
32
+ getNavVersion.mockReturnValue(0);
33
+ getReloadVersion.mockReturnValue(0);
34
+ });
35
+
36
+ function mockJsonResponse(body, { headers = {} } = {}) {
37
+ return { status: 200, ok: true, headers: new Headers(headers), json: async () => body };
31
38
  }
32
39
 
40
+ beforeEach(() => {
41
+ const stored = {};
42
+ global.window = {
43
+ location: { reload: jest.fn() },
44
+ sessionStorage: {
45
+ getItem: (key) => stored[key] ?? null,
46
+ setItem: (key, value) => {
47
+ stored[key] = value;
48
+ },
49
+ },
50
+ };
51
+ });
52
+
33
53
  afterEach(() => {
34
54
  delete global.fetch;
55
+ delete global.window;
35
56
  });
36
57
 
37
58
  test('fetchPageConfig compiles inlined _jsEntries module text into a { hash: fn } object', async () => {
38
59
  global.fetch = jest.fn(async () =>
39
60
  mockJsonResponse({
40
- id: 'p',
41
- _jsEntries: "export default { 'h1': ({ args }) => { return args.x + 1; } };",
42
- _dynamicIcons: { FiZap: { tag: 'svg' } },
61
+ pageId: 'p',
62
+ pathParams: {},
63
+ matchedPath: 'p',
64
+ pageConfig: {
65
+ id: 'page:p',
66
+ _jsEntries: "export default { 'h1': ({ args }) => { return args.x + 1; } };",
67
+ _dynamicIcons: { Zap: { node: [['path', { d: 'M0 0' }]] } },
68
+ },
43
69
  })
44
70
  );
45
71
 
46
- const data = await fetchPageConfig('http://localhost/api/page/p');
72
+ const { pageConfig } = await fetchPageConfig('http://localhost/api/page/p', {
73
+ lowdefy: {},
74
+ user: null,
75
+ });
47
76
 
48
- expect(typeof data._jsEntries.h1).toBe('function');
49
- expect(data._jsEntries.h1({ args: { x: 1 } })).toBe(2);
77
+ expect(typeof pageConfig._jsEntries.h1).toBe('function');
78
+ expect(pageConfig._jsEntries.h1({ args: { x: 1 } })).toBe(2);
50
79
  // _dynamicIcons passes through untouched.
51
- expect(data._dynamicIcons).toEqual({ FiZap: { tag: 'svg' } });
80
+ expect(pageConfig._dynamicIcons).toEqual({ Zap: { node: [['path', { d: 'M0 0' }]] } });
52
81
  });
53
82
 
54
83
  test('fetchPageConfig issues only the page-config request — no /api/js or /api/icons fetch', async () => {
55
- global.fetch = jest.fn(async () => mockJsonResponse({ id: 'p' }));
84
+ global.fetch = jest.fn(async () =>
85
+ mockJsonResponse({
86
+ pageId: 'p',
87
+ pathParams: {},
88
+ matchedPath: 'p',
89
+ pageConfig: { id: 'page:p' },
90
+ })
91
+ );
56
92
 
57
- const data = await fetchPageConfig('http://localhost/api/page/p');
93
+ const data = await fetchPageConfig('http://localhost/api/page/p', { lowdefy: {}, user: null });
58
94
 
59
- expect(data._jsEntries).toBeUndefined();
95
+ expect(data.pageConfig._jsEntries).toBeUndefined();
60
96
  expect(global.fetch).toHaveBeenCalledTimes(1);
61
97
  expect(global.fetch.mock.calls[0][0]).toBe('http://localhost/api/page/p');
62
98
  });
99
+
100
+ test('getPageConfigKey leaves the query string out of a page not known to be dynamic', () => {
101
+ const pageUrl = '/api/page/static-page';
102
+ getNavVersion.mockReturnValue(1);
103
+ const first = getPageConfigKey({ pageUrl, search: '?file=a' });
104
+ getNavVersion.mockReturnValue(2);
105
+ const second = getPageConfigKey({ pageUrl, search: '?file=b' });
106
+
107
+ expect(first).toEqual([pageUrl, 0]);
108
+ expect(second).toEqual(first);
109
+ });
110
+
111
+ test('getPageConfigKey keeps a static page keyed without the query after its fetch', () => {
112
+ const pageUrl = '/api/page/static-fetched';
113
+ recordDynamicPage({ data: { pageConfig: { id: 'page:static-fetched' } }, pageUrl });
114
+ getNavVersion.mockReturnValue(3);
115
+
116
+ expect(getPageConfigKey({ pageUrl, search: '?q=1' })).toEqual([pageUrl, 0]);
117
+ });
118
+
119
+ test('getPageConfigKey keeps the static key for the navigation that found a page dynamic', () => {
120
+ const pageUrl = '/api/page/dynamic-first';
121
+ getNavVersion.mockReturnValue(4);
122
+ const beforeFetch = getPageConfigKey({ pageUrl, search: '?q=1' });
123
+ recordDynamicPage({ data: { pageConfig: { dynamic: true } }, pageUrl });
124
+ const afterFetch = getPageConfigKey({ pageUrl, search: '?q=1' });
125
+
126
+ expect(afterFetch).toEqual(beforeFetch);
127
+ });
128
+
129
+ test('getPageConfigKey keys a known dynamic page on the query and navigation version', () => {
130
+ const pageUrl = '/api/page/dynamic-later';
131
+ getNavVersion.mockReturnValue(5);
132
+ recordDynamicPage({ data: { pageConfig: { dynamic: true } }, pageUrl });
133
+ getNavVersion.mockReturnValue(6);
134
+ const sixth = getPageConfigKey({ pageUrl, search: '?q=1' });
135
+ // A later fetch of the known dynamic page must not move it back to the static key.
136
+ recordDynamicPage({ data: { pageConfig: { dynamic: true } }, pageUrl });
137
+ const sixthAfterFetch = getPageConfigKey({ pageUrl, search: '?q=1' });
138
+ getNavVersion.mockReturnValue(7);
139
+ const seventh = getPageConfigKey({ pageUrl, search: '?q=1' });
140
+
141
+ expect(sixth).toEqual([pageUrl, 0, '?q=1', 6]);
142
+ expect(sixthAfterFetch).toEqual(sixth);
143
+ expect(seventh).toEqual([pageUrl, 0, '?q=1', 7]);
144
+ });
145
+
146
+ test('getPageConfigKey returns to the static key when a dynamic page is no longer dynamic', () => {
147
+ const pageUrl = '/api/page/dynamic-removed';
148
+ getNavVersion.mockReturnValue(8);
149
+ recordDynamicPage({ data: { pageConfig: { dynamic: true } }, pageUrl });
150
+ getReloadVersion.mockReturnValue(1);
151
+ getNavVersion.mockReturnValue(9);
152
+ recordDynamicPage({ data: { pageConfig: { id: 'page:dynamic-removed' } }, pageUrl });
153
+
154
+ expect(getPageConfigKey({ pageUrl, search: '?q=1' })).toEqual([pageUrl, 1]);
155
+ });
156
+
157
+ test('fetchPageConfig returns the page a patterned path matched with its values', async () => {
158
+ global.fetch = jest.fn(async () =>
159
+ mockJsonResponse({
160
+ pageId: 'ticket',
161
+ pathParams: { space: 'a+b', ticket_id: '1' },
162
+ matchedPath: 'tickets/a%2Bb/1',
163
+ pageConfig: { id: 'page:ticket', path: 'tickets/{space}/{ticket_id}' },
164
+ })
165
+ );
166
+
167
+ const data = await fetchPageConfig('http://localhost/api/page/tickets/a%2Bb/1', {
168
+ lowdefy: {},
169
+ user: null,
170
+ });
171
+
172
+ expect(global.fetch.mock.calls[0][0]).toBe('http://localhost/api/page/tickets/a%2Bb/1');
173
+ expect(data).toEqual({
174
+ pageId: 'ticket',
175
+ pathParams: { space: 'a+b', ticket_id: '1' },
176
+ matchedPath: 'tickets/a%2Bb/1',
177
+ pageConfig: { id: 'page:ticket', path: 'tickets/{space}/{ticket_id}' },
178
+ });
179
+ });
180
+
181
+ test('fetchPageConfig reloads instead of settling when the server answered as another caller', async () => {
182
+ global.fetch = jest.fn(async () =>
183
+ mockJsonResponse(
184
+ { pageId: 'p', pathParams: {}, matchedPath: 'p', pageConfig: { id: 'page:p' } },
185
+ { headers: { 'x-lowdefy-caller': 'aaaaaaaaaaaaaaaa' } }
186
+ )
187
+ );
188
+ const settled = jest.fn();
189
+ fetchPageConfig('http://localhost/api/page/p', {
190
+ lowdefy: {},
191
+ user: { id: 'user_1', organization_id: 'org_1' },
192
+ }).then(settled);
193
+ await new Promise((resolve) => setTimeout(resolve, 20));
194
+ expect(global.window.location.reload).toHaveBeenCalledTimes(1);
195
+ expect(settled).not.toHaveBeenCalled();
196
+ });
197
+
198
+ test('fetchPageConfig returns the page when the server answered as the rendered caller', async () => {
199
+ global.fetch = jest.fn(async () =>
200
+ mockJsonResponse(
201
+ { pageId: 'p', pathParams: {}, matchedPath: 'p', pageConfig: { id: 'page:p' } },
202
+ { headers: { 'x-lowdefy-caller': '' } }
203
+ )
204
+ );
205
+ const data = await fetchPageConfig('http://localhost/api/page/p', { lowdefy: {}, user: null });
206
+ expect(data.pageId).toBe('p');
207
+ expect(global.window.location.reload).not.toHaveBeenCalled();
208
+ });
209
+
210
+ test('fetchPageConfig reloads when the server answered the rendered caller with other roles', async () => {
211
+ const user = { id: 'user_1', organization_id: 'org_1', roles: ['editor', 'admin'] };
212
+ global.fetch = jest.fn(async () =>
213
+ mockJsonResponse(
214
+ { pageId: 'p', pathParams: {}, matchedPath: 'p', pageConfig: { id: 'page:p' } },
215
+ { headers: { 'x-lowdefy-caller': callerStamp({ user: { ...user, roles: ['editor'] } }) } }
216
+ )
217
+ );
218
+ const settled = jest.fn();
219
+ fetchPageConfig('http://localhost/api/page/p', { lowdefy: {}, user }).then(settled);
220
+ await new Promise((resolve) => setTimeout(resolve, 20));
221
+ expect(global.window.location.reload).toHaveBeenCalledTimes(1);
222
+ expect(settled).not.toHaveBeenCalled();
223
+ });
224
+
225
+ test('fetchPageConfig keeps the caller stamp of a page answered as the rendered caller', async () => {
226
+ const user = { id: 'user_1', organization_id: 'org_1', roles: ['editor', 'admin'] };
227
+ // The fixed vector callerStamp's own tests pin, so server and client agree.
228
+ const header =
229
+ '82e92667feb5824d; session=3a1d7b6848ba4816; roles=228d50a09b1b1e2b; memberships=476954726056d64d';
230
+ global.fetch = jest.fn(async () =>
231
+ mockJsonResponse(
232
+ { pageId: 'p', pathParams: {}, matchedPath: 'p', pageConfig: { id: 'page:p' } },
233
+ { headers: { 'x-lowdefy-caller': header } }
234
+ )
235
+ );
236
+ // The first page config arrives before the page context exists.
237
+ const lowdefy = {};
238
+ const data = await fetchPageConfig('http://localhost/api/page/p', { lowdefy, user });
239
+ expect(data.pageId).toBe('p');
240
+ expect(global.window.location.reload).not.toHaveBeenCalled();
241
+ expect(lowdefy._internal.callerStamp).toEqual({
242
+ caller: '82e92667feb5824d',
243
+ session: '3a1d7b6848ba4816',
244
+ roles: '228d50a09b1b1e2b',
245
+ memberships: '476954726056d64d',
246
+ });
247
+ });
@@ -0,0 +1,58 @@
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 path from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { parseArgs } from 'node:util';
20
+ import pino from 'pino';
21
+ import { createNodeLogger } from '@lowdefy/logger/node';
22
+
23
+ import runConfigTreeBuild from './runConfigTreeBuild.mjs';
24
+
25
+ // Entry for `lowdefy journeys scope`'s base and head builds and the journey
26
+ // readers' config text set: the CLI spawns
27
+ // `node <dev>/lib/configTree/buildConfigTree.mjs --config <dir> --out <dir>`
28
+ // from the installed dev server, which holds @lowdefy/build and the app's
29
+ // plugins. Exits 1 when the build fails; <out>/result.json says why.
30
+ async function buildConfigTree() {
31
+ const { values } = parseArgs({
32
+ options: { config: { type: 'string' }, out: { type: 'string' } },
33
+ });
34
+ const logger = createNodeLogger({
35
+ name: 'lowdefy config tree build',
36
+ level: process.env.LOWDEFY_LOG_LEVEL ?? 'info',
37
+ base: { pid: undefined, hostname: undefined },
38
+ destination: pino.destination({ dest: 1, sync: true }),
39
+ });
40
+ if (!values.config || !values.out) {
41
+ logger.error('buildConfigTree needs --config <dir> and --out <dir>.');
42
+ process.exitCode = 1;
43
+ return;
44
+ }
45
+ const result = await runConfigTreeBuild({
46
+ configDirectory: path.resolve(values.config),
47
+ outDirectory: path.resolve(values.out),
48
+ devDirectory: path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..'),
49
+ logger,
50
+ refResolver: process.env.LOWDEFY_BUILD_REF_RESOLVER,
51
+ skipPluginChecks: process.env.LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS === 'true',
52
+ });
53
+ if (result.status !== 'ok') {
54
+ process.exitCode = 1;
55
+ }
56
+ }
57
+
58
+ await buildConfigTree();
@@ -0,0 +1,77 @@
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 path from 'node:path';
18
+ import { jest } from '@jest/globals';
19
+
20
+ const mockRunConfigTreeBuild = jest.fn();
21
+ let mockArgs = [];
22
+
23
+ // parseArgs reads the real process.argv, which the test cannot set: jest gives
24
+ // it a copy. The real parseArgs runs on the test's arguments instead.
25
+ const actualUtil = await import('node:util');
26
+ jest.unstable_mockModule('node:util', () => ({
27
+ ...actualUtil,
28
+ default: actualUtil.default,
29
+ parseArgs: (config) => actualUtil.parseArgs({ ...config, args: mockArgs }),
30
+ }));
31
+
32
+ jest.unstable_mockModule('./runConfigTreeBuild.mjs', () => ({
33
+ default: mockRunConfigTreeBuild,
34
+ }));
35
+
36
+ const savedSkip = process.env.LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS;
37
+ const savedExitCode = process.exitCode;
38
+ let loadCount = 0;
39
+
40
+ // buildConfigTree.mjs runs its build when imported, so each import is a fresh
41
+ // module instance.
42
+ async function runScript() {
43
+ loadCount += 1;
44
+ await import(`./buildConfigTree.mjs?case=${loadCount}`);
45
+ }
46
+
47
+ beforeEach(() => {
48
+ mockArgs = ['--config', '/app', '--out', '/out'];
49
+ delete process.env.LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS;
50
+ mockRunConfigTreeBuild.mockReset();
51
+ mockRunConfigTreeBuild.mockResolvedValue({ status: 'ok', errors: [], warnings: [] });
52
+ });
53
+
54
+ afterAll(() => {
55
+ process.exitCode = savedExitCode;
56
+ if (savedSkip === undefined) {
57
+ delete process.env.LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS;
58
+ } else {
59
+ process.env.LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS = savedSkip;
60
+ }
61
+ });
62
+
63
+ test('buildConfigTree passes skipPluginChecks true when LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS is true', async () => {
64
+ process.env.LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS = 'true';
65
+ await runScript();
66
+ expect(mockRunConfigTreeBuild).toHaveBeenCalledTimes(1);
67
+ const [args] = mockRunConfigTreeBuild.mock.calls[0];
68
+ expect(args.configDirectory).toEqual(path.resolve('/app'));
69
+ expect(args.outDirectory).toEqual(path.resolve('/out'));
70
+ expect(args.skipPluginChecks).toBe(true);
71
+ });
72
+
73
+ test('buildConfigTree passes skipPluginChecks false when LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS is unset', async () => {
74
+ await runScript();
75
+ expect(mockRunConfigTreeBuild).toHaveBeenCalledTimes(1);
76
+ expect(mockRunConfigTreeBuild.mock.calls[0][0].skipPluginChecks).toBe(false);
77
+ });
@@ -0,0 +1,101 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import { type } from '@lowdefy/helpers';
20
+ import { normaliseClickText } from '@lowdefy/node-utils';
21
+
22
+ // Build markers: their values are key and reference ids, not text the app
23
+ // shows.
24
+ const MARKER_KEYS = new Set(['~k', '~r', '~l', '~ignoreBuildChecks']);
25
+ const ANTD_LOCALE_NAME = /^[A-Za-z]{2,3}(_[A-Za-z0-9]+)*$/;
26
+
27
+ // Strings only: a number leaf is not config text, so a page size of 42 never
28
+ // resolves a grid cell showing "42".
29
+ function addStringLeaves({ value, texts }) {
30
+ if (type.isString(value)) {
31
+ const text = normaliseClickText(value);
32
+ if (!type.isNone(text)) texts.add(text);
33
+ return;
34
+ }
35
+ if (type.isArray(value)) {
36
+ value.forEach((item) => addStringLeaves({ value: item, texts }));
37
+ return;
38
+ }
39
+ if (type.isObject(value)) {
40
+ Object.entries(value).forEach(([key, item]) => {
41
+ if (MARKER_KEYS.has(key)) return;
42
+ addStringLeaves({ value: item, texts });
43
+ });
44
+ }
45
+ }
46
+
47
+ function readJson(filePath) {
48
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
49
+ }
50
+
51
+ function listPageArtifacts({ buildDirectory }) {
52
+ const pagesDirectory = path.join(buildDirectory, 'pages');
53
+ if (!fs.existsSync(pagesDirectory)) return [];
54
+ return fs
55
+ .readdirSync(pagesDirectory, { withFileTypes: true })
56
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
57
+ .map((entry) => path.join(pagesDirectory, entry.name))
58
+ .sort();
59
+ }
60
+
61
+ // antd shows its own strings (OK, Cancel, pagination) in the locale the app
62
+ // declares for it; an app with no i18n, or a locale with no antd name, gets
63
+ // antd's default, en_US.
64
+ function listAntdLocales({ i18n }) {
65
+ const locales = i18n.locales ?? [];
66
+ if (locales.length === 0) return ['en_US'];
67
+ const names = locales.map((locale) =>
68
+ type.isString(locale.antd) && ANTD_LOCALE_NAME.test(locale.antd) ? locale.antd : 'en_US'
69
+ );
70
+ return [...new Set(names)].sort();
71
+ }
72
+
73
+ async function importAntdLocale(name) {
74
+ const module = await import(`antd/locale/${name}.js`);
75
+ return module.default?.default ?? module.default ?? module;
76
+ }
77
+
78
+ // The app's config text set: every string an element of this app can show
79
+ // that comes from the repository, never from data. Read after a full build
80
+ // from its artifacts: every page's string leaves (operator arguments
81
+ // included, so both branches of an _if count), menus.json, every locale's
82
+ // messages in i18n.json, every plugin's default messages for every locale it
83
+ // ships (i18n.json keeps only declared locales), and antd's locale strings.
84
+ // Returns a sorted array of distinct strings, normalised as clicked text is.
85
+ async function collectConfigText({ buildDirectory, messagesMap }) {
86
+ const texts = new Set();
87
+ listPageArtifacts({ buildDirectory }).forEach((filePath) => {
88
+ addStringLeaves({ value: readJson(filePath), texts });
89
+ });
90
+ addStringLeaves({ value: readJson(path.join(buildDirectory, 'menus.json')), texts });
91
+ const i18n = readJson(path.join(buildDirectory, 'i18n.json'));
92
+ addStringLeaves({ value: i18n.messages ?? {}, texts });
93
+ (i18n.locales ?? []).forEach((locale) => addStringLeaves({ value: locale.label, texts }));
94
+ addStringLeaves({ value: messagesMap, texts });
95
+ for (const name of listAntdLocales({ i18n })) {
96
+ addStringLeaves({ value: await importAntdLocale(name), texts });
97
+ }
98
+ return [...texts].sort();
99
+ }
100
+
101
+ export default collectConfigText;
@@ -0,0 +1,116 @@
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 os from 'node:os';
19
+ import path from 'node:path';
20
+
21
+ import collectConfigText from './collectConfigText.mjs';
22
+
23
+ let buildDirectory;
24
+ beforeEach(() => {
25
+ buildDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-config-text-'));
26
+ fs.mkdirSync(path.join(buildDirectory, 'pages'));
27
+ });
28
+ afterEach(() => {
29
+ fs.rmSync(buildDirectory, { recursive: true, force: true });
30
+ });
31
+
32
+ function writeArtifact(name, value) {
33
+ fs.writeFileSync(path.join(buildDirectory, name), JSON.stringify(value));
34
+ }
35
+
36
+ const homePage = {
37
+ id: 'home',
38
+ type: 'Box',
39
+ '~k': 'k:1',
40
+ blocks: [
41
+ {
42
+ id: 'assign_button',
43
+ type: 'Button',
44
+ properties: { title: ' Assign\n to me ', size: 42 },
45
+ '~r': 'r:1',
46
+ },
47
+ {
48
+ id: 'status_title',
49
+ type: 'Title',
50
+ properties: {
51
+ content: { _if: { test: { _state: 'open' }, then: 'Open items', else: 'Closed items' } },
52
+ },
53
+ },
54
+ ],
55
+ };
56
+
57
+ test('collectConfigText collects page strings, both _if branches, menus and every locale', async () => {
58
+ writeArtifact('pages/home.json', homePage);
59
+ writeArtifact('menus.json', [
60
+ {
61
+ id: 'default',
62
+ links: [{ id: 'home', type: 'MenuLink', properties: { title: 'Home page' } }],
63
+ },
64
+ ]);
65
+ writeArtifact('i18n.json', {
66
+ defaultLocale: 'en-US',
67
+ locales: [
68
+ { code: 'en-US', label: 'English', antd: 'en_US' },
69
+ { code: 'de-DE', label: 'Deutsch', antd: 'de_DE' },
70
+ ],
71
+ messages: { 'en-US': { save: 'Save' }, 'de-DE': { save: 'Speichern' } },
72
+ });
73
+ const texts = await collectConfigText({
74
+ buildDirectory,
75
+ messagesMap: {
76
+ '@acme/blocks': { 'en-US': { clear: 'Clear all' }, 'fr-FR': { clear: 'Tout effacer' } },
77
+ },
78
+ });
79
+ expect(texts).toEqual(expect.arrayContaining(['Assign to me', 'Open items', 'Closed items']));
80
+ expect(texts).toEqual(expect.arrayContaining(['Home page', 'Save', 'Speichern', 'Deutsch']));
81
+ expect(texts).toEqual(expect.arrayContaining(['Clear all', 'Tout effacer']));
82
+ expect(texts).toEqual([...texts].sort());
83
+ expect(new Set(texts).size).toEqual(texts.length);
84
+ });
85
+
86
+ test('collectConfigText leaves out numbers, build markers and empty strings', async () => {
87
+ writeArtifact('pages/home.json', { ...homePage, empty: ' ' });
88
+ writeArtifact('menus.json', []);
89
+ writeArtifact('i18n.json', {});
90
+ const texts = await collectConfigText({ buildDirectory, messagesMap: {} });
91
+ expect(texts).not.toContain('42');
92
+ expect(texts).not.toContain('k:1');
93
+ expect(texts).not.toContain('r:1');
94
+ expect(texts).not.toContain('');
95
+ });
96
+
97
+ test("collectConfigText adds antd's en_US strings for an app with no i18n", async () => {
98
+ writeArtifact('pages/home.json', homePage);
99
+ writeArtifact('menus.json', []);
100
+ writeArtifact('i18n.json', {});
101
+ const texts = await collectConfigText({ buildDirectory, messagesMap: {} });
102
+ expect(texts).toEqual(expect.arrayContaining(['OK', 'Cancel', 'Next Page']));
103
+ });
104
+
105
+ test("collectConfigText adds antd's strings for each declared locale's antd name", async () => {
106
+ writeArtifact('pages/home.json', homePage);
107
+ writeArtifact('menus.json', []);
108
+ writeArtifact('i18n.json', {
109
+ defaultLocale: 'de-DE',
110
+ locales: [{ code: 'de-DE', antd: 'de_DE' }],
111
+ messages: { 'de-DE': { save: 'Speichern' } },
112
+ });
113
+ const texts = await collectConfigText({ buildDirectory, messagesMap: {} });
114
+ expect(texts).toContain('Abbrechen');
115
+ expect(texts).not.toContain('Next Page');
116
+ });
@@ -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 fs from 'node:fs';
18
+ import path from 'node:path';
19
+
20
+ // A build writes into its server directory as well as its build directory:
21
+ // it rewrites package.json, cleans and refills public/ from public_default/,
22
+ // rewrites lowdefy-build/tailwind/ and copies agent files in. A base or head
23
+ // build for the journeys scope gets a scratch server directory of its own, so the
24
+ // running dev server's directory (devDirectory) is only ever read: a copy of
25
+ // its package.json and public_default/, and a symlink to its node_modules,
26
+ // where block source collection resolves plugin packages.
27
+ async function prepareScratchServer({ devDirectory, serverDirectory }) {
28
+ await fs.promises.rm(serverDirectory, { recursive: true, force: true });
29
+ await fs.promises.mkdir(serverDirectory, { recursive: true });
30
+ await fs.promises.copyFile(
31
+ path.join(devDirectory, 'package.json'),
32
+ path.join(serverDirectory, 'package.json')
33
+ );
34
+ await fs.promises.cp(
35
+ path.join(devDirectory, 'public_default'),
36
+ path.join(serverDirectory, 'public_default'),
37
+ { recursive: true }
38
+ );
39
+ await fs.promises.symlink(
40
+ path.join(devDirectory, 'node_modules'),
41
+ path.join(serverDirectory, 'node_modules'),
42
+ 'dir'
43
+ );
44
+ }
45
+
46
+ export default prepareScratchServer;