@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,239 @@
|
|
|
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
|
+
import { getStepKey, normaliseBlockId } from '@lowdefy/node-utils';
|
|
19
|
+
|
|
20
|
+
import getPageConfig from './getPageConfig.js';
|
|
21
|
+
import JourneyStepError from './JourneyStepError.js';
|
|
22
|
+
import pageBlocks from './pageBlocks.js';
|
|
23
|
+
import readBuildArtifact from './readBuildArtifact.js';
|
|
24
|
+
import readPageArtifact from './readPageArtifact.js';
|
|
25
|
+
|
|
26
|
+
const TARGET_STEPS = ['click', 'open', 'fill', 'select'];
|
|
27
|
+
const TARGET_EXPECTATIONS = ['visible', 'hidden', 'text'];
|
|
28
|
+
|
|
29
|
+
// Lists the known ids in a failure when there are few enough to read.
|
|
30
|
+
function describeKnown({ ids, noun }) {
|
|
31
|
+
if (ids.length === 0) {
|
|
32
|
+
return `It has no ${noun}s.`;
|
|
33
|
+
}
|
|
34
|
+
if (ids.length > 20) {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
return `Known ${noun}s: ${ids.join(', ')}.`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function unknownReference({ message, expected, actual }) {
|
|
41
|
+
return new JourneyStepError(message, { expected, actual });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The blockId a step's target names, or undefined for a target with none (a
|
|
45
|
+
// page-wide `text` or `containing` target) and for steps that name no target.
|
|
46
|
+
function readTargetBlockId(step) {
|
|
47
|
+
const key = getStepKey(step);
|
|
48
|
+
let target;
|
|
49
|
+
if (TARGET_STEPS.includes(key)) {
|
|
50
|
+
target = step[key];
|
|
51
|
+
} else if (key === 'expect') {
|
|
52
|
+
const expectation = getStepKey(step.expect);
|
|
53
|
+
if (TARGET_EXPECTATIONS.includes(expectation)) {
|
|
54
|
+
target = step.expect[expectation];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (type.isString(target)) {
|
|
58
|
+
return target;
|
|
59
|
+
}
|
|
60
|
+
return target?.blockId;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The Lowdefy page the actor's tab shows, as its id and the ids of the blocks
|
|
64
|
+
// its engine holds, or null when none shows (an opened email, a page that left
|
|
65
|
+
// the app) or the app is between pages (isPageReady), when the id still names
|
|
66
|
+
// the page being left. The engine's ids include blocks the build cannot name: a
|
|
67
|
+
// Dynamic block's content, which the server resolves when the page is fetched.
|
|
68
|
+
async function readShownPage({ page }) {
|
|
69
|
+
try {
|
|
70
|
+
return await page.evaluate(() => {
|
|
71
|
+
const lowdefy = window.lowdefy;
|
|
72
|
+
if (!lowdefy?.pageId || lowdefy._devNavigating === true) return null;
|
|
73
|
+
const shownKey = lowdefy.pageInstances?.[lowdefy.pageId]?.at(-1);
|
|
74
|
+
const blocks = lowdefy.contexts?.[shownKey]?._internal?.RootSlots?.map ?? {};
|
|
75
|
+
return { pageId: lowdefy.pageId, blockIds: Object.keys(blocks) };
|
|
76
|
+
});
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function builtBlockIds(built) {
|
|
83
|
+
return [built.blockId, ...pageBlocks(built).map((block) => block.blockId)];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function hasBlock({ ids, blockId }) {
|
|
87
|
+
const normalised = normaliseBlockId(blockId);
|
|
88
|
+
return ids.some((id) => normaliseBlockId(id) === normalised);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// A block of the page the journey opened is not a misspelling either: the app
|
|
92
|
+
// may have sent the person elsewhere (a guarded page redirecting home, a
|
|
93
|
+
// refused page showing the not-found page), and the step then fails as not
|
|
94
|
+
// visible, which says what happened. The opened page's own id always counts,
|
|
95
|
+
// so a page the server refused (and never built) still fails that way.
|
|
96
|
+
function isBlockOfJourneyPage({ blockId, journeyPageId }) {
|
|
97
|
+
if (blockId === journeyPageId) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
const built = readPageArtifact({ pageId: journeyPageId });
|
|
101
|
+
return built !== null && hasBlock({ ids: builtBlockIds(built), blockId });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function checkBlockId({ blockId, shownPage, journeyPageId }) {
|
|
105
|
+
const { pageId } = shownPage;
|
|
106
|
+
const known = builtBlockIds(readPageArtifact({ pageId }));
|
|
107
|
+
if (hasBlock({ ids: [...known, ...shownPage.blockIds], blockId })) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (pageId !== journeyPageId && isBlockOfJourneyPage({ blockId, journeyPageId })) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
throw unknownReference({
|
|
114
|
+
message: `Page "${pageId}" has no block "${blockId}". ${describeKnown({
|
|
115
|
+
ids: known,
|
|
116
|
+
noun: 'block',
|
|
117
|
+
})}`.trim(),
|
|
118
|
+
expected: `a block "${blockId}" on page "${pageId}"`,
|
|
119
|
+
actual: `no block "${blockId}"`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// A request is counted on a page the journey may not have visited yet, so that
|
|
124
|
+
// page is built first, as a visit would build it.
|
|
125
|
+
async function checkRequestId({ requestId, pageId, currentPageId }) {
|
|
126
|
+
if (pageId !== currentPageId) {
|
|
127
|
+
const config = await getPageConfig({ pageId });
|
|
128
|
+
if (config === null) {
|
|
129
|
+
throw unknownReference({
|
|
130
|
+
message: `There is no page "${pageId}" to count request "${requestId}" on.`,
|
|
131
|
+
expected: `a page "${pageId}"`,
|
|
132
|
+
actual: `no page "${pageId}"`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const request = readBuildArtifact({ name: `pages/${pageId}/requests/${requestId}.json` });
|
|
137
|
+
if (request !== null) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const built = readPageArtifact({ pageId });
|
|
141
|
+
const known = (built?.requests ?? []).map((item) => item.requestId);
|
|
142
|
+
throw unknownReference({
|
|
143
|
+
message: `Page "${pageId}" has no request "${requestId}". ${describeKnown({
|
|
144
|
+
ids: known,
|
|
145
|
+
noun: 'request',
|
|
146
|
+
})}`.trim(),
|
|
147
|
+
expected: `a request "${requestId}" on page "${pageId}"`,
|
|
148
|
+
actual: `no request "${requestId}"`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function checkEndpointId({ endpointId }) {
|
|
153
|
+
if (readBuildArtifact({ name: `api/${endpointId}.json` }) !== null) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
throw unknownReference({
|
|
157
|
+
message: `The app has no endpoint "${endpointId}".`,
|
|
158
|
+
expected: `an endpoint "${endpointId}"`,
|
|
159
|
+
actual: `no endpoint "${endpointId}"`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function checkPageId({ pageId }) {
|
|
164
|
+
const registry = readBuildArtifact({ name: 'pageRegistry.json' });
|
|
165
|
+
if (Object.prototype.hasOwnProperty.call(registry, pageId)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
throw unknownReference({
|
|
169
|
+
message: `The app has no page "${pageId}". ${describeKnown({
|
|
170
|
+
ids: Object.keys(registry),
|
|
171
|
+
noun: 'page',
|
|
172
|
+
})}`.trim(),
|
|
173
|
+
expected: `a page "${pageId}"`,
|
|
174
|
+
actual: `no page "${pageId}"`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// On a data set run, an `as` name opens that data set user, so a name that is
|
|
179
|
+
// neither the journey's first actor nor one of its users is a misspelling.
|
|
180
|
+
// Without a data set any name is a new browser as the journey's user.
|
|
181
|
+
function checkActorName({ journey, name }) {
|
|
182
|
+
if (type.isUndefined(journey.dataSetUsers) || name === journey.mainActor) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (Object.prototype.hasOwnProperty.call(journey.dataSetUsers, name)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const users = Object.keys(journey.dataSetUsers);
|
|
189
|
+
throw unknownReference({
|
|
190
|
+
message: `"as" names "${name}", who is neither "${
|
|
191
|
+
journey.mainActor
|
|
192
|
+
}" nor a user of the data set. ${describeKnown({ ids: users, noun: 'user' })}`.trim(),
|
|
193
|
+
expected: `"${journey.mainActor}" or a data set user`,
|
|
194
|
+
actual: `no data set user "${name}"`,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Checked at the start of every step, before it acts, against the current
|
|
199
|
+
// build: a misspelt blockId, requestId, endpointId, goto pageId or `as` name
|
|
200
|
+
// fails its step and names the unknown id, instead of passing a step that
|
|
201
|
+
// asserts something is absent (expect.hidden, expect.calls with count 0) or
|
|
202
|
+
// opening the not-found page. Ids are read from the actor's current page (a
|
|
203
|
+
// block also from the page the journey opened); when no Lowdefy page shows, or
|
|
204
|
+
// the app is between pages, block and request checks are skipped and the
|
|
205
|
+
// step's own wait reports what it finds.
|
|
206
|
+
async function checkStepReferences({ journey, page, step }) {
|
|
207
|
+
const key = getStepKey(step);
|
|
208
|
+
if (key === 'as') {
|
|
209
|
+
checkActorName({ journey, name: step.as });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (key === 'goto') {
|
|
213
|
+
checkPageId({ pageId: type.isString(step.goto) ? step.goto : step.goto.pageId });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const calls = key === 'expect' && getStepKey(step.expect) === 'calls' ? step.expect.calls : null;
|
|
217
|
+
if (calls !== null && !type.isUndefined(calls.endpoint)) {
|
|
218
|
+
checkEndpointId({ endpointId: calls.endpoint });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const blockId = readTargetBlockId(step);
|
|
222
|
+
const requestId = key === 'wait' ? step.wait.request : calls?.request;
|
|
223
|
+
if (type.isUndefined(blockId) && type.isUndefined(requestId)) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const shownPage = await readShownPage({ page });
|
|
227
|
+
const currentPageId = shownPage?.pageId ?? null;
|
|
228
|
+
if (!type.isUndefined(blockId) && shownPage !== null) {
|
|
229
|
+
checkBlockId({ blockId, shownPage, journeyPageId: journey.pageId });
|
|
230
|
+
}
|
|
231
|
+
if (!type.isUndefined(requestId)) {
|
|
232
|
+
const pageId = calls?.pageId ?? currentPageId;
|
|
233
|
+
if (pageId !== null) {
|
|
234
|
+
await checkRequestId({ requestId, pageId, currentPageId });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export default checkStepReferences;
|
|
@@ -0,0 +1,173 @@
|
|
|
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 { jest } from '@jest/globals';
|
|
22
|
+
|
|
23
|
+
// getPageConfig JIT-builds a page the journey has not visited; here the
|
|
24
|
+
// fixture build already holds every page it knows.
|
|
25
|
+
const mockGetPageConfig = jest.fn(async ({ pageId }) =>
|
|
26
|
+
['home', 'second'].includes(pageId) ? {} : null
|
|
27
|
+
);
|
|
28
|
+
jest.unstable_mockModule('./getPageConfig.js', () => ({ default: mockGetPageConfig }));
|
|
29
|
+
|
|
30
|
+
const { default: checkStepReferences } = await import('./checkStepReferences.js');
|
|
31
|
+
|
|
32
|
+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-check-step-references-'));
|
|
33
|
+
const previousCwd = process.cwd();
|
|
34
|
+
|
|
35
|
+
function write(name, value) {
|
|
36
|
+
const filePath = path.join(directory, 'build', name);
|
|
37
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
38
|
+
fs.writeFileSync(filePath, JSON.stringify(value));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
beforeAll(() => {
|
|
42
|
+
write('pageRegistry.json', { home: {}, second: {} });
|
|
43
|
+
write('pages/home.json', {
|
|
44
|
+
id: 'page:home',
|
|
45
|
+
blockId: 'home',
|
|
46
|
+
pageId: 'home',
|
|
47
|
+
areas: {
|
|
48
|
+
content: {
|
|
49
|
+
blocks: [
|
|
50
|
+
{ blockId: 'title' },
|
|
51
|
+
{
|
|
52
|
+
blockId: 'rows',
|
|
53
|
+
areas: { content: { blocks: [{ blockId: 'rows.$.name' }] } },
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
write('pages/home/requests/save_item.json', { requestId: 'save_item' });
|
|
60
|
+
write('pages/second/requests/load.json', { requestId: 'load' });
|
|
61
|
+
write('api/notify.json', { endpointId: 'notify' });
|
|
62
|
+
process.chdir(directory);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterAll(() => {
|
|
66
|
+
process.chdir(previousCwd);
|
|
67
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// The page's evaluate answers what readShownPage reads from window.lowdefy:
|
|
71
|
+
// the shown page's id and the ids of the blocks its engine holds.
|
|
72
|
+
function pageShowing(pageId, { blockIds = [] } = {}) {
|
|
73
|
+
return { evaluate: async () => ({ pageId, blockIds }) };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function journeyWith(overrides = {}) {
|
|
77
|
+
return { pageId: 'home', mainActor: 'main', dataSetUsers: undefined, ...overrides };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function check(step, { page = pageShowing('home'), journey = journeyWith() } = {}) {
|
|
81
|
+
try {
|
|
82
|
+
await checkStepReferences({ journey, page, step });
|
|
83
|
+
return undefined;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
test.each([
|
|
90
|
+
[{ click: 'title' }],
|
|
91
|
+
[{ fill: { blockId: 'rows.0.name', value: 'x' } }],
|
|
92
|
+
[{ expect: { hidden: { blockId: 'title', containing: 'gone' } } }],
|
|
93
|
+
[{ expect: { visible: 'home' } }],
|
|
94
|
+
[{ click: { text: 'Save' } }],
|
|
95
|
+
[{ wait: { request: 'save_item' } }],
|
|
96
|
+
[{ wait: { ms: 10 } }],
|
|
97
|
+
[{ expect: { calls: { request: 'save_item', count: 0 } } }],
|
|
98
|
+
[{ expect: { calls: { request: 'load', pageId: 'second', count: 0 } } }],
|
|
99
|
+
[{ expect: { calls: { endpoint: 'notify', count: 0 } } }],
|
|
100
|
+
[{ expect: { state: { path: 'anything', equals: null } } }],
|
|
101
|
+
[{ goto: 'second' }],
|
|
102
|
+
[{ goto: { pageId: 'second' } }],
|
|
103
|
+
[{ as: 'anyone' }],
|
|
104
|
+
])('checkStepReferences passes %j', async (step) => {
|
|
105
|
+
expect(await check(step)).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test.each([
|
|
109
|
+
[{ expect: { hidden: 'nosuchblock' } }, 'no block "nosuchblock"'],
|
|
110
|
+
[{ click: { blockId: 'nosuchblock', text: 'Go' } }, 'no block "nosuchblock"'],
|
|
111
|
+
[{ wait: { request: 'nosuchrequest' } }, 'no request "nosuchrequest"'],
|
|
112
|
+
[{ expect: { calls: { request: 'nosuchrequest', count: 0 } } }, 'no request "nosuchrequest"'],
|
|
113
|
+
[
|
|
114
|
+
{ expect: { calls: { request: 'save_item', pageId: 'second', count: 0 } } },
|
|
115
|
+
'no request "save_item"',
|
|
116
|
+
],
|
|
117
|
+
[
|
|
118
|
+
{ expect: { calls: { request: 'save_item', pageId: 'nosuchpage', count: 0 } } },
|
|
119
|
+
'no page "nosuchpage"',
|
|
120
|
+
],
|
|
121
|
+
[{ expect: { calls: { endpoint: 'nosuchendpoint', count: 0 } } }, 'no endpoint "nosuchendpoint"'],
|
|
122
|
+
[{ goto: 'nosuchpage' }, 'no page "nosuchpage"'],
|
|
123
|
+
])('checkStepReferences fails %j naming the unknown id', async (step, actual) => {
|
|
124
|
+
const error = await check(step);
|
|
125
|
+
expect(error.name).toBe('JourneyStepError');
|
|
126
|
+
expect(error.actual).toBe(actual);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('checkStepReferences lists the known blocks of a small page', async () => {
|
|
130
|
+
const error = await check({ expect: { hidden: 'nosuchblock' } });
|
|
131
|
+
expect(error.message).toBe(
|
|
132
|
+
'Page "home" has no block "nosuchblock". Known blocks: home, title, rows, rows.$.name.'
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('checkStepReferences skips block and request checks when no Lowdefy page shows', async () => {
|
|
137
|
+
const page = { evaluate: async () => null };
|
|
138
|
+
expect(await check({ expect: { hidden: 'nosuchblock' } }, { page })).toBeUndefined();
|
|
139
|
+
expect(await check({ wait: { request: 'nosuchrequest' } }, { page })).toBeUndefined();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('checkStepReferences fails an as name that is not a user of the data set', async () => {
|
|
143
|
+
const journey = journeyWith({ dataSetUsers: { member: {}, owner: {} } });
|
|
144
|
+
const error = await check({ as: 'membr' }, { journey });
|
|
145
|
+
expect(error.actual).toBe('no data set user "membr"');
|
|
146
|
+
expect(error.message).toBe(
|
|
147
|
+
'"as" names "membr", who is neither "main" nor a user of the data set. Known users: member, owner.'
|
|
148
|
+
);
|
|
149
|
+
expect(await check({ as: 'member' }, { journey })).toBeUndefined();
|
|
150
|
+
expect(await check({ as: 'main' }, { journey })).toBeUndefined();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('checkStepReferences accepts a block of the page the journey opened when the app moved elsewhere', async () => {
|
|
154
|
+
const page = pageShowing('second');
|
|
155
|
+
write('pages/second.json', { id: 'page:second', blockId: 'second', pageId: 'second' });
|
|
156
|
+
expect(await check({ expect: { visible: 'title' } }, { page })).toBeUndefined();
|
|
157
|
+
expect(
|
|
158
|
+
await check(
|
|
159
|
+
{ expect: { visible: 'refused' } },
|
|
160
|
+
{ page, journey: journeyWith({ pageId: 'refused' }) }
|
|
161
|
+
)
|
|
162
|
+
).toBeUndefined();
|
|
163
|
+
const error = await check({ expect: { visible: 'nosuchblock' } }, { page });
|
|
164
|
+
expect(error.message).toBe('Page "second" has no block "nosuchblock". Known blocks: second.');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('checkStepReferences accepts a block the shown page holds but the build does not name', async () => {
|
|
168
|
+
// A Dynamic block's content is resolved by the server when the page is fetched.
|
|
169
|
+
const page = pageShowing('home', { blockIds: ['title', 'dynamic_content', 'dynamic_save'] });
|
|
170
|
+
expect(await check({ click: 'dynamic_save' }, { page })).toBeUndefined();
|
|
171
|
+
const error = await check({ expect: { hidden: 'nosuchblock' } }, { page });
|
|
172
|
+
expect(error.actual).toBe('no block "nosuchblock"');
|
|
173
|
+
});
|
|
@@ -139,6 +139,8 @@ function writeCheckpoint({ name, snapshot, notes, overwrite }) {
|
|
|
139
139
|
parts.push('state.json');
|
|
140
140
|
writeJsonFile(path.join(dir, 'urlQuery.json'), snapshot.urlQuery ?? '');
|
|
141
141
|
parts.push('urlQuery.json');
|
|
142
|
+
writeJsonFile(path.join(dir, 'pathParams.json'), snapshot.pathParams ?? {});
|
|
143
|
+
parts.push('pathParams.json');
|
|
142
144
|
writeJsonFile(path.join(dir, 'inputs.json'), snapshot.input ?? {});
|
|
143
145
|
parts.push('inputs.json');
|
|
144
146
|
writeJsonFile(path.join(dir, 'user.json'), snapshot.user ?? null);
|
|
@@ -177,6 +179,7 @@ function readCheckpoint({ name }) {
|
|
|
177
179
|
checkpoint,
|
|
178
180
|
state: readJsonFile(path.join(dir, 'state.json'), {}),
|
|
179
181
|
urlQuery: readJsonFile(path.join(dir, 'urlQuery.json'), ''),
|
|
182
|
+
pathParams: readJsonFile(path.join(dir, 'pathParams.json'), {}),
|
|
180
183
|
input: readJsonFile(path.join(dir, 'inputs.json'), {}),
|
|
181
184
|
user: readJsonFile(path.join(dir, 'user.json'), null),
|
|
182
185
|
global: readJsonFile(path.join(dir, 'global.json'), {}),
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { publish } from './devEventBus.js';
|
|
18
|
+
import getBuildId from './getBuildId.js';
|
|
19
|
+
import isRunErrorEntry from './isRunErrorEntry.js';
|
|
20
|
+
import { recordRunError } from './runErrorBuffers.js';
|
|
21
|
+
|
|
17
22
|
// Module-level ring buffer of recent client-reported errors — feeds the
|
|
18
23
|
// getBuildStatus feedback endpoint so agents can see browser errors without
|
|
19
24
|
// tailing server logs. Deliberately in-memory only: entries are lost on
|
|
@@ -22,11 +27,23 @@ const MAX_ENTRIES = 50;
|
|
|
22
27
|
|
|
23
28
|
const entries = [];
|
|
24
29
|
|
|
30
|
+
// Each entry is stamped with the build it happened under (see getBuildId).
|
|
31
|
+
// An entry a journey run caused goes to that run's own
|
|
32
|
+
// buffer only (see runErrorBuffers), or is dropped when the run has ended:
|
|
33
|
+
// kept out of this ring and the event bus, it never reaches build-status or
|
|
34
|
+
// an agent's event stream, and a run that trips many errors cannot evict the
|
|
35
|
+
// developer's own. The run reports its errors in its own result.
|
|
25
36
|
function push(entry) {
|
|
26
|
-
|
|
37
|
+
const stamped = { ...entry, buildId: getBuildId() };
|
|
38
|
+
if (isRunErrorEntry(stamped)) {
|
|
39
|
+
recordRunError({ ...stamped, store: 'client' });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
entries.push(stamped);
|
|
27
43
|
if (entries.length > MAX_ENTRIES) {
|
|
28
44
|
entries.shift();
|
|
29
45
|
}
|
|
46
|
+
publish({ type: 'client_error', ...stamped });
|
|
30
47
|
}
|
|
31
48
|
|
|
32
49
|
function list() {
|
|
@@ -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 { jest } from '@jest/globals';
|
|
18
|
+
|
|
19
|
+
const mockPublish = jest.fn();
|
|
20
|
+
jest.unstable_mockModule('./devEventBus.js', () => ({
|
|
21
|
+
publish: mockPublish,
|
|
22
|
+
}));
|
|
23
|
+
jest.unstable_mockModule('./getBuildId.js', () => ({
|
|
24
|
+
default: () => '2026-01-01T00:00:00.000Z',
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const { default: clientErrorStore } = await import('./clientErrorStore.js');
|
|
28
|
+
|
|
29
|
+
test('clientErrorStore push adds an entry stamped with the current build, lists it, and publishes it as a client_error event', () => {
|
|
30
|
+
const entry = { timestamp: '2026-01-01T00:00:00.000Z', name: 'OperatorError', message: 'bad' };
|
|
31
|
+
const stamped = { ...entry, buildId: '2026-01-01T00:00:00.000Z' };
|
|
32
|
+
clientErrorStore.push(entry);
|
|
33
|
+
expect(clientErrorStore.list()).toEqual([stamped]);
|
|
34
|
+
expect(mockPublish).toHaveBeenCalledWith({ type: 'client_error', ...stamped });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('clientErrorStore caps at 50 entries and drops the oldest', () => {
|
|
38
|
+
for (let i = 0; i < 60; i++) {
|
|
39
|
+
clientErrorStore.push({ index: i });
|
|
40
|
+
}
|
|
41
|
+
const entries = clientErrorStore.list();
|
|
42
|
+
expect(entries.length).toEqual(50);
|
|
43
|
+
expect(entries[0].index).toEqual(10);
|
|
44
|
+
expect(entries[49].index).toEqual(59);
|
|
45
|
+
expect(mockPublish).toHaveBeenCalledTimes(60);
|
|
46
|
+
});
|
|
@@ -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 addNestedEndpoints from './addNestedEndpoints.js';
|
|
18
|
+
import addWriteFlags from './addWriteFlags.js';
|
|
19
|
+
import matchExercisedPages from './matchExercisedPages.js';
|
|
20
|
+
import mergeNetworkSnapshots from './mergeNetworkSnapshots.js';
|
|
21
|
+
|
|
22
|
+
// What one journey run touched, across all of its actors: the network path
|
|
23
|
+
// its browsers measured, the endpoints their routines reached through CallApi,
|
|
24
|
+
// which requests and endpoints write, and what its pages observed: the events
|
|
25
|
+
// that completed and the blocks that were ever visible.
|
|
26
|
+
async function collectExercised({ snapshots, observed, readConfigFile, requestSchemas }) {
|
|
27
|
+
const network = mergeNetworkSnapshots({ snapshots });
|
|
28
|
+
const { endpoints, unfollowed } = await addNestedEndpoints({
|
|
29
|
+
endpoints: network.endpoints,
|
|
30
|
+
readConfigFile,
|
|
31
|
+
});
|
|
32
|
+
const withWrites = await addWriteFlags({
|
|
33
|
+
requests: network.requests,
|
|
34
|
+
endpoints,
|
|
35
|
+
readConfigFile,
|
|
36
|
+
requestSchemas,
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
pages: matchExercisedPages({
|
|
40
|
+
routes: await readConfigFile('routes.json'),
|
|
41
|
+
pagePaths: network.pagePaths,
|
|
42
|
+
}),
|
|
43
|
+
appEvents: network.appEvents,
|
|
44
|
+
requests: withWrites.requests,
|
|
45
|
+
endpoints: withWrites.endpoints,
|
|
46
|
+
unfollowed,
|
|
47
|
+
events: observed.events,
|
|
48
|
+
rendered: observed.rendered,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default collectExercised;
|
|
@@ -0,0 +1,102 @@
|
|
|
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
|
+
// One shared browser per dev server child, launched on first use and closed
|
|
20
|
+
// once unused for idleTimeout ms, so a server that lives for hours does not
|
|
21
|
+
// hold a browser it used once. The close waits for three things: no open
|
|
22
|
+
// context, no launch in flight, and no getBrowser() call for idleTimeout ms.
|
|
23
|
+
// The last one covers a caller between getBrowser() and its first
|
|
24
|
+
// newContext, which holds the browser without a context yet.
|
|
25
|
+
function createBrowserLifecycle({ launch, idleTimeout }) {
|
|
26
|
+
// Cached as a promise so concurrent calls awaiting a launch share it.
|
|
27
|
+
let browserPromise = null;
|
|
28
|
+
let launching = 0;
|
|
29
|
+
let openContexts = 0;
|
|
30
|
+
let idleTimer = null;
|
|
31
|
+
|
|
32
|
+
function closeIfIdle() {
|
|
33
|
+
idleTimer = null;
|
|
34
|
+
if (type.isNone(browserPromise) || openContexts > 0 || launching > 0) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const closing = browserPromise;
|
|
38
|
+
browserPromise = null;
|
|
39
|
+
closing.then((browser) => browser.close()).catch(() => {});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function armIdleClose() {
|
|
43
|
+
clearTimeout(idleTimer);
|
|
44
|
+
idleTimer = setTimeout(closeIfIdle, idleTimeout);
|
|
45
|
+
// Never the reason the process stays up.
|
|
46
|
+
idleTimer.unref();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function startLaunch() {
|
|
50
|
+
launching += 1;
|
|
51
|
+
const launched = launch();
|
|
52
|
+
browserPromise = launched;
|
|
53
|
+
launched
|
|
54
|
+
.catch(() => {
|
|
55
|
+
// Forget a failed launch so the next call retries instead of
|
|
56
|
+
// replaying the rejection forever.
|
|
57
|
+
if (browserPromise === launched) {
|
|
58
|
+
browserPromise = null;
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
.finally(() => {
|
|
62
|
+
launching -= 1;
|
|
63
|
+
// A launch can outlast the idle timeout (it may wait for the shell
|
|
64
|
+
// download), so the countdown starts again once it settles.
|
|
65
|
+
armIdleClose();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function getBrowser() {
|
|
70
|
+
armIdleClose();
|
|
71
|
+
if (type.isNone(browserPromise)) {
|
|
72
|
+
startLaunch();
|
|
73
|
+
}
|
|
74
|
+
const current = browserPromise;
|
|
75
|
+
const browser = await current;
|
|
76
|
+
if (browser.isConnected()) {
|
|
77
|
+
return browser;
|
|
78
|
+
}
|
|
79
|
+
// The browser crashed or was closed under us. Concurrent callers that
|
|
80
|
+
// all saw it share one relaunch.
|
|
81
|
+
if (browserPromise === current) {
|
|
82
|
+
startLaunch();
|
|
83
|
+
}
|
|
84
|
+
return browserPromise;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Counts a context until it closes. Playwright emits close for every way a
|
|
88
|
+
// context ends: context.close(), browser.close() or a browser crash.
|
|
89
|
+
function trackContext(context) {
|
|
90
|
+
openContexts += 1;
|
|
91
|
+
context.on('close', () => {
|
|
92
|
+
openContexts -= 1;
|
|
93
|
+
if (openContexts === 0) {
|
|
94
|
+
armIdleClose();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { getBrowser, trackContext };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default createBrowserLifecycle;
|