@lowdefy/server-dev 6.1.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/App.jsx +52 -24
- package/client/Inspector.jsx +73 -30
- package/client/JourneyObserver.jsx +47 -0
- package/client/Page.jsx +54 -22
- package/client/Recorder.jsx +73 -0
- package/client/Routing.jsx +37 -13
- package/client/clientBuildImports.test.mjs +2 -0
- package/client/devStreamConnections.test.mjs +40 -0
- package/client/feedback/FeedbackMount.jsx +2 -2
- package/client/feedback/FeedbackOverlay.jsx +7 -4
- package/client/feedback/feedbackStyles.js +3 -0
- package/client/journeyObserver/startJourneyObserver.js +101 -0
- package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
- package/client/openInEditor/OpenInEditorListener.jsx +2 -0
- package/client/recorder/buildEngineRecord.js +37 -0
- package/client/recorder/buildInteractionRecord.js +64 -0
- package/client/recorder/buildTraceEvent.js +57 -0
- package/client/recorder/buildTraceEvent.test.mjs +81 -0
- package/client/recorder/chunkRecords.js +69 -0
- package/client/recorder/chunkRecords.test.mjs +59 -0
- package/client/recorder/collectEventActions.js +30 -0
- package/client/recorder/collectEventEndpoints.js +40 -0
- package/client/recorder/collectEventRequests.js +39 -0
- package/client/recorder/createPairingBuffer.js +187 -0
- package/client/recorder/createPairingBuffer.test.mjs +405 -0
- package/client/recorder/createPasswordRedactor.js +84 -0
- package/client/recorder/createRecordBatcher.js +54 -0
- package/client/recorder/createRecordBatcher.test.mjs +76 -0
- package/client/recorder/createRecorder.js +216 -0
- package/client/recorder/createRecorder.test.mjs +318 -0
- package/client/recorder/createRecordingSession.js +61 -0
- package/client/recorder/createRecordingSession.test.mjs +72 -0
- package/client/recorder/diffStateWrites.js +77 -0
- package/client/recorder/diffStateWrites.test.mjs +66 -0
- package/client/recorder/isEventCall.js +30 -0
- package/client/recorder/jsonType.js +39 -0
- package/client/recorder/keyChord.js +47 -0
- package/client/recorder/keyChord.test.mjs +50 -0
- package/client/recorder/sendRecords.js +40 -0
- package/client/recorder/toJsonSafe.js +29 -0
- package/lib/client/BuildingPage.jsx +3 -1
- package/lib/client/ErrorBar.jsx +17 -13
- package/lib/client/RedirectingPage.jsx +56 -0
- package/lib/client/auth/Auth.jsx +11 -6
- package/lib/client/auth/AuthConfigured.jsx +222 -36
- package/lib/client/auth/AuthNotConfigured.js +45 -4
- package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
- package/lib/client/auth/authTemplateSync.test.mjs +40 -0
- package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
- package/lib/client/auth/createFetchCheckingBuild.js +45 -0
- package/lib/client/bundleBuildId.js +25 -0
- package/lib/client/collectVisibleBlockIds.js +51 -0
- package/lib/client/getCspNonce.js +25 -0
- package/lib/client/getPagePath.js +36 -0
- package/lib/client/getPagePath.test.mjs +51 -0
- package/lib/client/utils/formatErrorsForCopy.js +30 -0
- package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
- package/lib/client/utils/getErrorBarColor.js +25 -0
- package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
- package/lib/client/utils/request.js +8 -1
- package/lib/client/utils/usePageConfig.js +89 -25
- package/lib/client/utils/usePageConfig.test.mjs +198 -13
- package/lib/configTree/buildConfigTree.mjs +58 -0
- package/lib/configTree/buildConfigTree.test.mjs +77 -0
- package/lib/configTree/collectConfigText.mjs +101 -0
- package/lib/configTree/collectConfigText.test.mjs +116 -0
- package/lib/configTree/prepareScratchServer.mjs +46 -0
- package/lib/configTree/runConfigTreeBuild.mjs +88 -0
- package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
- package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
- package/lib/docs/JourneyStepError.js +30 -0
- package/lib/docs/acquireBrowserSlot.js +30 -0
- package/lib/docs/addNestedEndpoints.js +54 -0
- package/lib/docs/addNestedEndpoints.test.mjs +185 -0
- package/lib/docs/addWriteFlags.js +77 -0
- package/lib/docs/buildEditedPages.js +50 -0
- package/lib/docs/buildWaitHeader.js +21 -0
- package/lib/docs/captureAnnotatedScreenshot.js +23 -12
- package/lib/docs/checkAgentFiles.js +148 -0
- package/lib/docs/checkAgentFiles.test.mjs +225 -0
- package/lib/docs/checkStepReferences.js +239 -0
- package/lib/docs/checkStepReferences.test.mjs +173 -0
- package/lib/docs/checkpointStore.js +3 -0
- package/lib/docs/clientErrorStore.js +18 -1
- package/lib/docs/clientErrorStore.test.mjs +46 -0
- package/lib/docs/collectExercised.js +52 -0
- package/lib/docs/createBrowserLifecycle.js +102 -0
- package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
- package/lib/docs/createDocsMcpServer.js +345 -388
- package/lib/docs/createDocsMcpServer.test.mjs +644 -4
- package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
- package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
- package/lib/docs/createJourneyActors.js +213 -0
- package/lib/docs/createJourneyActors.test.mjs +79 -0
- package/lib/docs/createJourneyObservations.js +80 -0
- package/lib/docs/createLeftOriginError.js +26 -0
- package/lib/docs/createNetworkCounter.js +101 -0
- package/lib/docs/dataSets/checkSharedCollectionIds.js +67 -0
- package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
- package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +679 -0
- package/lib/docs/dataSets/describeDataSetResult.js +25 -0
- package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
- package/lib/docs/dataSets/deserializeFixture.js +34 -0
- package/lib/docs/dataSets/drainSessionWork.js +37 -0
- package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
- package/lib/docs/dataSets/duplicateIndexName.js +24 -0
- package/lib/docs/dataSets/findCollectionCollisions.js +52 -0
- package/lib/docs/dataSets/getDataStore.js +106 -0
- package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
- package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
- package/lib/docs/dataSets/groupDataSetByCollection.js +44 -0
- package/lib/docs/dataSets/insertFixture.js +57 -0
- package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
- package/lib/docs/dataSets/isDataStoreGone.js +29 -0
- package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
- package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
- package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
- package/lib/docs/dataSets/openDataSession.js +119 -0
- package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +343 -0
- package/lib/docs/dataSets/pickDataStorePort.js +58 -0
- package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
- package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
- package/lib/docs/dataSets/readDataSession.js +41 -0
- package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
- package/lib/docs/dataSets/readDataSet.js +68 -0
- package/lib/docs/dataSets/readDataSet.test.mjs +220 -0
- package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
- package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
- package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
- package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
- package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
- package/lib/docs/dataSets/sweepOrphanDatabases.js +33 -0
- package/lib/docs/dataSets/test/app/lowdefy.yaml +203 -0
- package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
- package/lib/docs/dataSets/test/app/tests/data/shop.yaml +8 -0
- package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
- package/lib/docs/devEventBus.js +136 -0
- package/lib/docs/devEventBus.test.mjs +269 -0
- package/lib/docs/devToolDefinitions.js +529 -0
- package/lib/docs/docSources.js +21 -0
- package/lib/docs/docs.test.mjs +171 -15
- package/lib/docs/docsIndex.test.mjs +377 -0
- package/lib/docs/evalOperator.js +5 -3
- package/lib/docs/evalOperatorHeadless.js +84 -12
- package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
- package/lib/docs/evalOperatorInTab.js +2 -1
- package/lib/docs/findConfig.js +95 -49
- package/lib/docs/fitResponse.js +81 -0
- package/lib/docs/fitResponse.test.mjs +128 -0
- package/lib/docs/formatFeedback.js +26 -5
- package/lib/docs/formatFeedback.test.mjs +48 -0
- package/lib/docs/getAppMap.js +7 -1
- package/lib/docs/getBrowser.js +150 -80
- package/lib/docs/getBrowser.test.mjs +407 -16
- package/lib/docs/getBuildId.js +61 -0
- package/lib/docs/getBuildId.test.mjs +84 -0
- package/lib/docs/getBuildStatus.js +54 -6
- package/lib/docs/getBuildStatus.test.mjs +133 -0
- package/lib/docs/getBuildStatusAfterEdits.js +35 -0
- package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
- package/lib/docs/getCoreDoc.js +9 -1
- package/lib/docs/getDoc.js +100 -0
- package/lib/docs/getDocsIndex.js +152 -0
- package/lib/docs/getHazards.js +115 -0
- package/lib/docs/getHazards.test.mjs +175 -0
- package/lib/docs/getMailOutboxDirectory.js +26 -0
- package/lib/docs/getOverview.js +79 -13
- package/lib/docs/getOverviewBasePath.test.mjs +37 -0
- package/lib/docs/getPageBuildStatus.js +46 -0
- package/lib/docs/getPluginDoc.js +21 -23
- package/lib/docs/getSchema.js +33 -1
- package/lib/docs/getStaleStatus.js +36 -0
- package/lib/docs/getStaleStatus.test.mjs +74 -0
- package/lib/docs/guardJourneyOrigin.js +39 -0
- package/lib/docs/inspectState.js +5 -3
- package/lib/docs/inspectStateFromTab.js +2 -2
- package/lib/docs/inspectStateHeadless.js +89 -27
- package/lib/docs/inspectStateHeadless.test.mjs +20 -1
- package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
- package/lib/docs/installHeadlessShell.js +123 -0
- package/lib/docs/installHeadlessShell.test.mjs +70 -0
- package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
- package/lib/docs/isHeadlessShellIncomplete.js +34 -0
- package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
- package/lib/docs/isPageReady.js +77 -0
- package/lib/docs/isPageReady.test.mjs +235 -0
- package/lib/docs/isRunErrorEntry.js +25 -0
- package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
- package/lib/docs/journeyRunRecording.js +35 -0
- package/lib/docs/journeyRunRecording.test.mjs +39 -0
- package/lib/docs/keepEventStreamAlive.js +68 -0
- package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
- package/lib/docs/launchBrowser.js +83 -0
- package/lib/docs/launchBrowser.test.mjs +175 -0
- package/lib/docs/listModuleDocEntries.js +69 -0
- package/lib/docs/listPackageDocFiles.js +67 -0
- package/lib/docs/listPluginDocEntries.js +111 -0
- package/lib/docs/listPlugins.js +9 -58
- package/lib/docs/listTypes.js +14 -6
- package/lib/docs/loadFrameworkHazards.js +40 -0
- package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
- package/lib/docs/loadState.js +103 -35
- package/lib/docs/makeModuleManifestDoc.js +174 -0
- package/lib/docs/matchExercisedPages.js +35 -0
- package/lib/docs/matchJourneyRoute.js +69 -0
- package/lib/docs/matchJourneyRoute.test.mjs +178 -0
- package/lib/docs/mergeNetworkSnapshots.js +49 -0
- package/lib/docs/nextJourneySequence.js +30 -0
- package/lib/docs/noBrowserError.js +22 -0
- package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
- package/lib/docs/observe/claimExpectedErrors.js +67 -0
- package/lib/docs/observe/createJourneyAppErrors.js +236 -0
- package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
- package/lib/docs/observe/evaluateInvariants.js +183 -0
- package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
- package/lib/docs/observe/findingKey.js +29 -0
- package/lib/docs/observe/hasNoEffect.js +31 -0
- package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
- package/lib/docs/observe/installStepObserver.js +72 -0
- package/lib/docs/observe/isAppApiUrl.js +33 -0
- package/lib/docs/observe/parseAppApiUrl.js +37 -0
- package/lib/docs/observe/readStepObserver.js +42 -0
- package/lib/docs/observe/relativeSource.js +29 -0
- package/lib/docs/observe/resolveConfigKeySource.js +33 -0
- package/lib/docs/observe/takeJourneyEvents.js +36 -0
- package/lib/docs/observe/toAppErrorFailure.js +61 -0
- package/lib/docs/observe/usesSearchStage.js +45 -0
- package/lib/docs/observe/waitForClientErrorReports.js +31 -0
- package/lib/docs/observe/watchJourneyContext.js +75 -0
- package/lib/docs/observe/withAppErrors.js +31 -0
- package/lib/docs/observe/withAppErrors.test.mjs +101 -0
- package/lib/docs/openJourney.js +94 -0
- package/lib/docs/openJourneyEmail.js +41 -0
- package/lib/docs/openJourneyEmail.test.mjs +187 -0
- package/lib/docs/pageBlocks.js +47 -0
- package/lib/docs/pageBuildStatus.test.mjs +103 -0
- package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
- package/lib/docs/rails/authEngineActions.js +53 -0
- package/lib/docs/rails/authEngineActions.test.mjs +69 -0
- package/lib/docs/rails/authEngineMethods.js +52 -0
- package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
- package/lib/docs/rails/checkAuthActionsRan.js +66 -0
- package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
- package/lib/docs/rails/checkClickRails.js +56 -0
- package/lib/docs/rails/createDataSetRails.js +82 -0
- package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
- package/lib/docs/rails/findExternalBlocks.js +147 -0
- package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
- package/lib/docs/rails/installAuthActionRecorder.js +56 -0
- package/lib/docs/rails/readAuthActionRecorder.js +32 -0
- package/lib/docs/rails/readBuiltArtifact.js +30 -0
- package/lib/docs/readConnectionPlugins.js +26 -0
- package/lib/docs/readDevAuthMode.js +32 -0
- package/lib/docs/readDocEntry.js +40 -0
- package/lib/docs/readDocTitle.js +26 -0
- package/lib/docs/readJourneyEmailMatch.js +57 -0
- package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
- package/lib/docs/readJourneySession.js +56 -0
- package/lib/docs/readMailOutbox.js +37 -0
- package/lib/docs/readPagePath.js +27 -0
- package/lib/docs/readPluginPackages.js +99 -0
- package/lib/docs/readProxyBuildWait.js +38 -0
- package/lib/docs/readRoutineSteps.js +45 -0
- package/lib/docs/registerShownTextEngine.js +118 -0
- package/lib/docs/requestRestart.js +30 -0
- package/lib/docs/requestRestart.test.mjs +49 -0
- package/lib/docs/resolvePageInstance.js +46 -0
- package/lib/docs/resolvePageInstance.test.mjs +81 -0
- package/lib/docs/resolveSource.js +11 -7
- package/lib/docs/resolveSource.test.mjs +1 -1
- package/lib/docs/resolveToolCaller.js +62 -0
- package/lib/docs/reviewPage.js +51 -0
- package/lib/docs/reviewPageBuilds.js +56 -0
- package/lib/docs/reviewPageBuilds.test.mjs +207 -0
- package/lib/docs/runCheck.js +75 -0
- package/lib/docs/runCheck.test.mjs +91 -0
- package/lib/docs/runCheckChild.js +49 -0
- package/lib/docs/runEndpoint.js +185 -0
- package/lib/docs/runEndpoint.test.mjs +509 -0
- package/lib/docs/runErrorBuffers.js +99 -0
- package/lib/docs/runErrorBuffers.test.mjs +95 -0
- package/lib/docs/runJourney.js +347 -0
- package/lib/docs/runJourney.test.mjs +2383 -0
- package/lib/docs/runJourneyAsUsers.js +44 -0
- package/lib/docs/runJourneyDataSet.test.mjs +274 -0
- package/lib/docs/runJourneySteps.js +1320 -0
- package/lib/docs/runRequest.js +100 -59
- package/lib/docs/runRequest.test.mjs +190 -0
- package/lib/docs/saveAnnotatedScreenshot.js +1 -1
- package/lib/docs/scaffoldPage.js +3 -1
- package/lib/docs/screenshotPage.js +163 -11
- package/lib/docs/screenshotPage.test.mjs +263 -1
- package/lib/docs/screenshotPageClip.test.mjs +4 -0
- package/lib/docs/searchDocs.js +82 -30
- package/lib/docs/searchDocs.test.mjs +100 -0
- package/lib/docs/searchIcons.js +90 -0
- package/lib/docs/searchIcons.test.mjs +127 -0
- package/lib/docs/selectExercised.js +39 -0
- package/lib/docs/selectFinalState.js +66 -0
- package/lib/docs/selectFinalState.test.mjs +51 -0
- package/lib/docs/serverErrorStore.js +54 -0
- package/lib/docs/serverErrorStore.test.mjs +50 -0
- package/lib/docs/setupTestFixtures.mjs +133 -39
- package/lib/docs/shownTextSelector.js +28 -0
- package/lib/docs/snapshotState.js +13 -5
- package/lib/docs/splitSearchTerms.js +62 -0
- package/lib/docs/splitSearchTerms.test.mjs +50 -0
- package/lib/docs/stateCheckpoints.test.mjs +77 -0
- package/lib/docs/tabAvailable.js +6 -5
- package/lib/docs/tabChannel.js +127 -21
- package/lib/docs/tabChannel.test.mjs +187 -29
- package/lib/docs/unsettledPageNote.js +24 -0
- package/lib/docs/validateJourneyMail.js +36 -0
- package/lib/docs/validateJourneyTimeout.js +38 -0
- package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
- package/lib/docs/validateRecordingParam.js +42 -0
- package/lib/docs/validateStateSelection.js +33 -0
- package/lib/docs/validateStateSelection.test.mjs +33 -0
- package/lib/docs/validateViewport.js +51 -0
- package/lib/docs/validateViewport.test.mjs +54 -0
- package/lib/docs/waitForBuild.js +50 -0
- package/lib/docs/waitForBuild.test.mjs +75 -0
- package/lib/docs/waitForJourneyEmail.js +77 -0
- package/lib/docs/withBrowserSlot.js +45 -0
- package/lib/docs/withBrowserSlot.test.mjs +75 -0
- package/lib/docs/withDataSession.js +53 -0
- package/lib/server/applyDataSetRedirect.js +83 -0
- package/lib/server/applyDataSetRedirect.test.mjs +340 -0
- package/lib/server/applyDataSetRedirectTenantReach.test.mjs +204 -0
- package/lib/server/applyDataSetRedirectWalled.test.mjs +131 -0
- package/lib/server/auth/createSystemContext.js +73 -0
- package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
- package/lib/server/auth/getHeadlessUser.js +9 -8
- package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
- package/lib/server/auth/getMockUser.js +18 -14
- package/lib/server/auth/{session.js → getStrategies.js} +17 -16
- package/lib/server/auth/headlessUser.js +3 -2
- package/lib/server/auth/journeyActor.js +35 -0
- package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
- package/lib/server/checkPageRecord.js +44 -0
- package/lib/server/contextMapBudget.js +24 -0
- package/lib/server/contextMapBudget.test.mjs +98 -0
- package/lib/server/continueJitKeys.js +28 -0
- package/lib/server/continueJitKeys.test.mjs +43 -0
- package/lib/server/createDataSessionEndedError.js +33 -0
- package/lib/server/createDataSetRefusal.js +26 -0
- package/lib/server/createLowdefyContext.js +168 -41
- package/lib/server/createLowdefyContext.test.mjs +328 -8
- package/lib/server/getClientAddress.js +51 -0
- package/lib/server/getClientAddress.test.mjs +56 -0
- package/lib/server/getPageJitEnrichment.test.mjs +29 -11
- package/lib/server/hashConfigContent.js +26 -0
- package/lib/server/importAppCode.test.mjs +57 -0
- package/lib/server/isRoutineStep.js +27 -0
- package/lib/server/jitPageBuilder.js +309 -126
- package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
- package/lib/server/jitPageBuilder.test.mjs +218 -0
- package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
- package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
- package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
- package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
- package/lib/server/journeyCookies.js +22 -0
- package/lib/server/journeyCookies.test.mjs +145 -0
- package/lib/server/loadDynamicJsMap.js +47 -0
- package/lib/server/log/createHandleError.js +38 -2
- package/lib/server/log/createHandleError.test.mjs +125 -2
- package/lib/server/log/createLogger.js +3 -0
- package/lib/server/log/createLogger.test.mjs +15 -0
- package/lib/server/log/logRequest.js +2 -3
- package/lib/server/mutantDataSetWrappers.test.mjs +112 -0
- package/lib/server/mutants/applyMutant.js +39 -0
- package/lib/server/mutants/artifactPatterns.js +25 -0
- package/lib/server/mutants/buildAnchor.js +45 -0
- package/lib/server/mutants/collectSharedIds.js +41 -0
- package/lib/server/mutants/configPathSegments.js +38 -0
- package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
- package/lib/server/mutants/describeEvent.js +22 -0
- package/lib/server/mutants/enumerateArtifact.js +56 -0
- package/lib/server/mutants/findNodeByKey.js +45 -0
- package/lib/server/mutants/findNodeByPointer.js +49 -0
- package/lib/server/mutants/getArtifactKind.js +32 -0
- package/lib/server/mutants/groupMutantCopies.js +56 -0
- package/lib/server/mutants/hashArtifact.js +25 -0
- package/lib/server/mutants/hashMutantTarget.js +34 -0
- package/lib/server/mutants/listMutants.js +122 -0
- package/lib/server/mutants/listMutants.test.mjs +502 -0
- package/lib/server/mutants/mutantId.js +30 -0
- package/lib/server/mutants/mutantRuns.js +57 -0
- package/lib/server/mutants/mutantRuns.test.mjs +136 -0
- package/lib/server/mutants/operators/dropAction.js +47 -0
- package/lib/server/mutants/operators/dropBlock.js +44 -0
- package/lib/server/mutants/operators/dropPayload.js +52 -0
- package/lib/server/mutants/operators/dropStep.js +44 -0
- package/lib/server/mutants/operators/flipVisible.js +53 -0
- package/lib/server/mutants/operators/index.js +37 -0
- package/lib/server/mutants/operators/retargetLink.js +72 -0
- package/lib/server/mutants/operators/skipValidate.js +77 -0
- package/lib/server/mutants/operators/swapIf.js +112 -0
- package/lib/server/mutants/operators.test.mjs +396 -0
- package/lib/server/mutants/positionFreePath.js +38 -0
- package/lib/server/mutants/removeFromParent.js +28 -0
- package/lib/server/mutants/test/fixtures.mjs +173 -0
- package/lib/server/mutants/test/keyArtifact.mjs +63 -0
- package/lib/server/mutants/validateMutantParam.js +69 -0
- package/lib/server/mutants/validateMutantsBody.js +88 -0
- package/lib/server/mutants/walkArtifact.js +179 -0
- package/lib/server/pageBuildRecords.js +131 -0
- package/lib/server/pageBuildRecords.test.mjs +258 -0
- package/lib/server/pageCache.mjs +22 -7
- package/lib/server/pageCache.test.mjs +22 -7
- package/lib/server/pageRebuilds.test.mjs +543 -0
- package/lib/server/pruneJitMaps.js +36 -0
- package/lib/server/pruneJitMaps.test.mjs +55 -0
- package/lib/server/readInstanceRecord.js +33 -0
- package/lib/server/readInstanceRecord.test.mjs +99 -0
- package/lib/server/readMergedMaps.js +86 -0
- package/lib/server/readMergedMaps.test.mjs +79 -0
- package/lib/server/recording/appendRecords.js +33 -0
- package/lib/server/recording/errorRecordingStamp.js +34 -0
- package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
- package/lib/server/recording/isRecordingEnabled.js +31 -0
- package/lib/server/recording/readRecordingCookie.js +71 -0
- package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
- package/lib/server/recording/recordingCookiePayload.js +33 -0
- package/lib/server/recording/servedBuilds.js +46 -0
- package/lib/server/recording/servedBuilds.test.mjs +42 -0
- package/lib/server/sinkExternalConnections.js +95 -0
- package/lib/server/sinkExternalConnections.test.mjs +135 -0
- package/lib/server/skipStaleMapWrites.js +62 -0
- package/lib/server/skipStaleMapWrites.test.mjs +113 -0
- package/lib/server/startIdleGc.js +67 -0
- package/lib/server/startIdleGc.test.mjs +138 -0
- package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
- package/lib/server/trackSessionWork.js +30 -0
- package/lib/vite/createDevServerExclude.js +40 -0
- package/lib/vite/createDevServerExclude.test.mjs +79 -0
- package/manager/getContext.mjs +24 -2
- package/manager/processes/initialBuild.mjs +14 -0
- package/manager/processes/initialBuild.test.mjs +101 -0
- package/manager/processes/lowdefyBuild.mjs +30 -7
- package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
- package/manager/processes/lowdefyBuild.test.mjs +136 -1
- package/manager/processes/optimizeDependencies.mjs +59 -0
- package/manager/processes/restartServer.mjs +34 -6
- package/manager/processes/restartServer.test.mjs +162 -0
- package/manager/processes/shutdownServer.mjs +11 -0
- package/manager/processes/shutdownServer.test.mjs +45 -0
- package/manager/processes/startFirstServer.mjs +46 -0
- package/manager/processes/startFirstServer.test.mjs +326 -0
- package/manager/processes/startMailSink.mjs +135 -0
- package/manager/processes/startMailSink.test.mjs +146 -0
- package/manager/processes/startProxy.mjs +129 -12
- package/manager/processes/startProxy.test.mjs +366 -0
- package/manager/processes/startServer.mjs +47 -6
- package/manager/processes/startServer.test.mjs +155 -0
- package/manager/processes/startWatchers.mjs +6 -4
- package/manager/processes/syncServer.mjs +69 -0
- package/manager/processes/syncServer.test.mjs +141 -0
- package/manager/processes/waitForDevServer.mjs +48 -0
- package/manager/processes/waitForDevServer.test.mjs +112 -0
- package/manager/run.mjs +128 -43
- package/manager/utils/BatchChanges.mjs +21 -5
- package/manager/utils/BatchChanges.test.mjs +77 -0
- package/manager/utils/acquireDevInstance.mjs +99 -0
- package/manager/utils/acquireDevInstance.test.mjs +160 -0
- package/manager/utils/createBuildActivity.mjs +67 -0
- package/manager/utils/createBuildActivity.test.mjs +58 -0
- package/manager/utils/createChangeSignal.mjs +39 -0
- package/manager/utils/createChangeSignal.test.mjs +62 -0
- package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
- package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
- package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
- package/manager/utils/createDotPathIgnore.mjs +44 -0
- package/manager/utils/createDotPathIgnore.test.mjs +65 -0
- package/manager/utils/createIdleGc.mjs +51 -0
- package/manager/utils/createIdleGc.test.mjs +77 -0
- package/manager/utils/createRequestActivity.mjs +100 -0
- package/manager/utils/createRequestActivity.test.mjs +121 -0
- package/manager/utils/createServerArtifactTracker.mjs +83 -0
- package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
- package/manager/utils/createServerEnv.mjs +39 -0
- package/manager/utils/filesHaveSameBytes.mjs +41 -0
- package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
- package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
- package/manager/utils/importFresh.mjs +46 -0
- package/manager/utils/importFresh.test.mjs +72 -0
- package/manager/utils/importFreshWorker.mjs +20 -0
- package/manager/utils/killTaggedBrowser.mjs +35 -0
- package/manager/utils/pagePathChanged.mjs +56 -0
- package/manager/utils/pruneRecordings.mjs +85 -0
- package/manager/utils/pruneRecordings.test.mjs +117 -0
- package/manager/utils/publishBuildDirectory.mjs +121 -0
- package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
- package/manager/utils/publishBuildDirectory.test.mjs +262 -0
- package/manager/utils/readBasePath.mjs +27 -0
- package/manager/utils/readBasePath.test.mjs +43 -0
- package/manager/utils/readBuildStatusWait.mjs +67 -0
- package/manager/utils/readManagerVersion.mjs +30 -0
- package/manager/utils/readManagerVersion.test.mjs +35 -0
- package/manager/utils/readPluginDefinitions.mjs +36 -0
- package/manager/utils/resolveDevAuthUrl.mjs +58 -0
- package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
- package/manager/utils/resolvePorts.mjs +49 -0
- package/manager/utils/setupWatcher.mjs +19 -11
- package/manager/utils/startRecordingPruner.mjs +45 -0
- package/manager/utils/startRecordingPruner.test.mjs +66 -0
- package/manager/utils/waitForChildExit.mjs +53 -0
- package/manager/utils/waitForChildExit.test.mjs +70 -0
- package/manager/utils/waitForServer.mjs +44 -0
- package/manager/utils/writeBuildStatus.mjs +1 -1
- package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
- package/manager/watchers/envWatcher.mjs +9 -2
- package/manager/watchers/envWatcher.test.mjs +87 -0
- package/manager/watchers/listServerSidePackages.mjs +47 -0
- package/manager/watchers/listServerSidePackages.test.mjs +38 -0
- package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
- package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
- package/manager/watchers/pluginSourceWatcher.mjs +79 -0
- package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
- package/manager/watchers/restartRequestWatcher.mjs +71 -0
- package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
- package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
- package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
- package/manager/watchers/serverPackageWatcher.mjs +38 -0
- package/package.json +60 -47
- package/package.original.json +60 -47
- package/src/app.js +131 -20
- package/src/html/renderDevPage.js +63 -11
- package/src/html/renderDevPage.test.mjs +138 -0
- package/src/lib/getRequestPath.js +29 -0
- package/src/middleware/createSameOriginGuard.js +37 -0
- package/src/middleware/createSameOriginGuard.test.mjs +102 -0
- package/src/middleware/errorHandler.js +51 -1
- package/src/middleware/errorHandler.test.mjs +173 -3
- package/src/middleware/isRebindingSafeHost.js +44 -0
- package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
- package/src/middleware/localDevToolsOnly.js +38 -0
- package/src/middleware/localDevToolsOnly.test.mjs +61 -0
- package/src/middleware/noStore.js +27 -0
- package/src/middleware/noStore.test.mjs +48 -0
- package/src/middleware/requestTimeout.js +38 -0
- package/src/middleware/staleFlag.js +76 -0
- package/src/middleware/staleFlag.test.mjs +127 -0
- package/src/middleware/stampCaller.js +38 -0
- package/src/middleware/stampCaller.test.mjs +64 -0
- package/src/routes/agent.js +15 -3
- package/src/routes/auth.js +29 -20
- package/src/routes/auth.test.mjs +30 -56
- package/src/routes/clientError.js +13 -13
- package/src/routes/cron.js +3 -1
- package/src/routes/cronForward.js +3 -1
- package/src/routes/detached.js +22 -9
- package/src/routes/devInspect.js +17 -8
- package/src/routes/devInspect.test.mjs +44 -7
- package/src/routes/devRecording.js +113 -0
- package/src/routes/devRecording.test.mjs +253 -0
- package/src/routes/docs/buildStatus.js +10 -2
- package/src/routes/docs/buildStatus.test.mjs +46 -0
- package/src/routes/docs/content.js +5 -3
- package/src/routes/docs/evalOperator.js +15 -1
- package/src/routes/docs/events.js +50 -0
- package/src/routes/docs/events.test.mjs +117 -0
- package/src/routes/docs/find.js +9 -1
- package/src/routes/docs/icons.js +30 -0
- package/src/routes/docs/inspectState.js +16 -1
- package/src/routes/docs/journey.js +194 -0
- package/src/routes/docs/journey.test.mjs +401 -0
- package/src/routes/docs/journeyRefusals.test.mjs +71 -0
- package/src/routes/docs/loadState.js +4 -2
- package/src/routes/docs/mcp.js +34 -3
- package/src/routes/docs/mcp.test.mjs +130 -0
- package/src/routes/docs/mutants.js +78 -0
- package/src/routes/docs/mutants.test.mjs +180 -0
- package/src/routes/docs/parsePathParamsParam.js +41 -0
- package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
- package/src/routes/docs/parseUserParam.js +27 -21
- package/src/routes/docs/parseUserParam.test.mjs +17 -5
- package/src/routes/docs/restart.js +35 -0
- package/src/routes/docs/restart.test.mjs +64 -0
- package/src/routes/docs/runEndpoint.js +54 -0
- package/src/routes/docs/runEndpoint.test.mjs +165 -0
- package/src/routes/docs/runRequest.js +25 -8
- package/src/routes/docs/runRequest.test.mjs +92 -0
- package/src/routes/docs/screenshot.js +50 -2
- package/src/routes/docs/screenshot.test.mjs +117 -0
- package/src/routes/docs/search.js +15 -2
- package/src/routes/docs/snapshotState.js +17 -3
- package/src/routes/endpoints.js +27 -26
- package/src/routes/endpointsTextAnswer.test.mjs +77 -0
- package/src/routes/feedback.js +10 -13
- package/src/routes/jitPage.js +99 -35
- package/src/routes/jitPage.test.mjs +248 -0
- package/src/routes/mcp.js +82 -5
- package/src/routes/mcp.test.mjs +215 -0
- package/src/routes/mcpMemberToken.test.mjs +125 -0
- package/src/routes/mcpProtectedResourceMetadata.js +34 -0
- package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
- package/src/routes/mountDevPageRoutes.js +25 -0
- package/src/routes/mountDevPageRoutes.test.mjs +30 -0
- package/src/routes/mountOauthDiscovery.js +44 -0
- package/src/routes/mountOauthDiscovery.test.mjs +79 -0
- package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
- package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
- package/src/routes/reload.js +16 -0
- package/src/routes/request.js +6 -4
- package/src/routes/request.test.mjs +9 -5
- package/src/routes/requestBody.test.mjs +159 -0
- package/src/routes/usage.js +6 -2
- package/src/routes/user.js +27 -0
- package/src/routes/websocket.js +29 -8
- package/src/routes/websocket.test.mjs +65 -0
- package/src/routes/wellKnownFallback.js +30 -0
- package/src/routes/wellKnownFallback.test.mjs +32 -0
- package/src/websocket/devWebSocket.js +41 -26
- package/src/websocket/devWebSocket.test.mjs +157 -0
- package/vite.config.js +53 -23
- package/lib/server/auth/getDevSession.js +0 -70
- package/lib/server/auth/getDevSession.test.mjs +0 -138
- package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
- package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
- package/manager/watchers/moduleBuildWatcher.mjs +0 -79
- package/manager/watchers/serverArtifactWatcher.mjs +0 -120
- 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
|
+
});
|