@lowdefy/server-dev 6.0.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +63 -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 +224 -39
- 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/getAuth.js +46 -0
- 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/{getAuthConfig.js → getStrategies.js} +12 -10
- 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 +174 -41
- package/lib/server/createLowdefyContext.test.mjs +437 -0
- 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 +77 -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/scrubSecrets.js +23 -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 +61 -49
- package/package.original.json +61 -49
- 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 +196 -14
- 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 +4 -2
- 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/lib/server/auth/session.js +0 -39
- 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
package/lib/docs/evalOperator.js
CHANGED
|
@@ -22,25 +22,27 @@ import resolveSource from './resolveSource.js';
|
|
|
22
22
|
// headless-only. Falls back to headless whenever the tab path isn't usable
|
|
23
23
|
// (no tab connected, or `source: 'tab'` was requested but it errored) so
|
|
24
24
|
// agents always get an answer.
|
|
25
|
-
async function evalOperator({ origin, pageId, expression, source, user }) {
|
|
25
|
+
async function evalOperator({ origin, pageId, pathParams, expression, source, user, data }) {
|
|
26
26
|
const { tryTab, error, invalidInput } = resolveSource({
|
|
27
27
|
name: 'evalOperator',
|
|
28
28
|
pageId,
|
|
29
|
+
pathParams,
|
|
29
30
|
source,
|
|
30
31
|
user,
|
|
32
|
+
data,
|
|
31
33
|
});
|
|
32
34
|
if (error) {
|
|
33
35
|
return { error, invalidInput };
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
if (tryTab) {
|
|
37
|
-
const result = await evalOperatorInTab({ pageId, expression });
|
|
39
|
+
const result = await evalOperatorInTab({ pageId, pathParams, expression });
|
|
38
40
|
if (!result?.error) {
|
|
39
41
|
return { ...result, source: 'tab' };
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
const result = await evalOperatorHeadless({ origin, pageId, expression, user });
|
|
45
|
+
const result = await evalOperatorHeadless({ origin, pageId, pathParams, expression, user, data });
|
|
44
46
|
return { ...result, source: 'headless' };
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -17,15 +17,32 @@
|
|
|
17
17
|
import { type } from '@lowdefy/helpers';
|
|
18
18
|
|
|
19
19
|
import { getBrowser, openPage, buildPageUrl } from './getBrowser.js';
|
|
20
|
+
import noBrowserError from './noBrowserError.js';
|
|
21
|
+
import resolvePageInstance from './resolvePageInstance.js';
|
|
22
|
+
import resolveToolCaller from './resolveToolCaller.js';
|
|
23
|
+
import unsettledPageNote from './unsettledPageNote.js';
|
|
24
|
+
import withBrowserSlot from './withBrowserSlot.js';
|
|
25
|
+
import withDataSession from './withDataSession.js';
|
|
20
26
|
|
|
21
27
|
// Evaluates an operator expression against the live client state of a
|
|
22
|
-
// headless Chromium tab navigated to the page's own route
|
|
28
|
+
// headless Chromium tab navigated to the page instance's own route (`pathParams`
|
|
29
|
+
// fill a patterned page's placeholders), read under the instance key, using the page's
|
|
23
30
|
// own WebParser instance so results match runtime exactly. Mirrors
|
|
24
31
|
// Inspector.jsx's evalExpression (the live-tab equivalent).
|
|
25
|
-
async function evalOperatorHeadless({
|
|
32
|
+
async function evalOperatorHeadless({
|
|
33
|
+
origin,
|
|
34
|
+
pageId,
|
|
35
|
+
pathParams,
|
|
36
|
+
expression,
|
|
37
|
+
user,
|
|
38
|
+
data,
|
|
39
|
+
timeout = 15000,
|
|
40
|
+
}) {
|
|
26
41
|
if (type.isNone(origin) || !type.isString(origin)) {
|
|
27
42
|
return {
|
|
28
|
-
error: `evalOperatorHeadless requires an "origin" string. Received ${JSON.stringify(
|
|
43
|
+
error: `evalOperatorHeadless requires an "origin" string. Received ${JSON.stringify(
|
|
44
|
+
origin
|
|
45
|
+
)}.`,
|
|
29
46
|
};
|
|
30
47
|
}
|
|
31
48
|
if (type.isNone(pageId) || !type.isString(pageId)) {
|
|
@@ -37,30 +54,77 @@ async function evalOperatorHeadless({ origin, pageId, expression, user, timeout
|
|
|
37
54
|
return { error: 'evalOperatorHeadless requires an "expression".' };
|
|
38
55
|
}
|
|
39
56
|
|
|
57
|
+
const instance = resolvePageInstance({ pageId, pathParams });
|
|
58
|
+
if (!type.isUndefined(instance.error)) {
|
|
59
|
+
return { error: instance.error, invalidInput: true };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const caller = await resolveToolCaller({ user, data });
|
|
63
|
+
if (!type.isUndefined(caller.error)) {
|
|
64
|
+
return caller;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return withBrowserSlot({
|
|
68
|
+
task: () =>
|
|
69
|
+
withDataSession({
|
|
70
|
+
dataSet: caller.dataSet,
|
|
71
|
+
task: ({ dataCookie }) =>
|
|
72
|
+
evalOperatorInBrowser({
|
|
73
|
+
origin,
|
|
74
|
+
pageId,
|
|
75
|
+
pathParams,
|
|
76
|
+
instance,
|
|
77
|
+
expression,
|
|
78
|
+
user: caller.user,
|
|
79
|
+
dataCookie,
|
|
80
|
+
timeout,
|
|
81
|
+
}),
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The part of evalOperatorHeadless that runs in the browser, inside a browser slot.
|
|
87
|
+
async function evalOperatorInBrowser({
|
|
88
|
+
origin,
|
|
89
|
+
pageId,
|
|
90
|
+
pathParams,
|
|
91
|
+
instance,
|
|
92
|
+
expression,
|
|
93
|
+
user,
|
|
94
|
+
dataCookie,
|
|
95
|
+
timeout,
|
|
96
|
+
}) {
|
|
40
97
|
let browser;
|
|
41
98
|
try {
|
|
42
99
|
browser = await getBrowser();
|
|
43
100
|
} catch (error) {
|
|
44
|
-
return {
|
|
45
|
-
error: `No Chromium available. Run: npx playwright install chromium (${error.message})`,
|
|
46
|
-
};
|
|
101
|
+
return { error: noBrowserError(error) };
|
|
47
102
|
}
|
|
48
103
|
|
|
49
|
-
const url = buildPageUrl({ origin, pageId });
|
|
104
|
+
const url = buildPageUrl({ origin, pageId, path: instance.path, pathParams });
|
|
50
105
|
|
|
51
106
|
let context;
|
|
52
107
|
try {
|
|
53
|
-
const opened = await openPage({
|
|
108
|
+
const opened = await openPage({
|
|
109
|
+
browser,
|
|
110
|
+
origin,
|
|
111
|
+
pageId,
|
|
112
|
+
path: instance.path,
|
|
113
|
+
pathParams,
|
|
114
|
+
user,
|
|
115
|
+
dataCookie,
|
|
116
|
+
timeout,
|
|
117
|
+
});
|
|
54
118
|
context = opened.context;
|
|
55
119
|
// Passed through as JSON so the page-side parser always receives a plain
|
|
56
120
|
// value, matching how it arrives at Inspector.jsx's eval-request handler.
|
|
57
121
|
const expressionJson = JSON.stringify(expression);
|
|
58
122
|
const result = await opened.page.evaluate(
|
|
59
|
-
({ id, exprJson }) => {
|
|
123
|
+
({ id, instanceKey, path, exprJson }) => {
|
|
60
124
|
const lowdefy = window.lowdefy;
|
|
61
|
-
const pageContext = lowdefy?.contexts?.[
|
|
125
|
+
const pageContext = lowdefy?.contexts?.[instanceKey];
|
|
62
126
|
if (!pageContext) {
|
|
63
|
-
return { error: `No live context for page "${id}".` };
|
|
127
|
+
return { error: `No live context for page "${id}" at "/${path}".` };
|
|
64
128
|
}
|
|
65
129
|
const input = JSON.parse(exprJson);
|
|
66
130
|
const { output, errors } = pageContext._internal.parser.parse({
|
|
@@ -72,8 +136,16 @@ async function evalOperatorHeadless({ origin, pageId, expression, user, timeout
|
|
|
72
136
|
errors: errors.map((error) => error.message),
|
|
73
137
|
};
|
|
74
138
|
},
|
|
75
|
-
{
|
|
139
|
+
{
|
|
140
|
+
id: pageId,
|
|
141
|
+
instanceKey: instance.instanceKey,
|
|
142
|
+
path: instance.path,
|
|
143
|
+
exprJson: expressionJson,
|
|
144
|
+
}
|
|
76
145
|
);
|
|
146
|
+
if (!opened.ready) {
|
|
147
|
+
return { ...result, ready: false, note: unsettledPageNote({ timeout }) };
|
|
148
|
+
}
|
|
77
149
|
return result;
|
|
78
150
|
} catch (error) {
|
|
79
151
|
return { error: `Failed to evaluate operator at "${url}": ${error.message}` };
|
|
@@ -25,7 +25,10 @@ import { jest } from '@jest/globals';
|
|
|
25
25
|
// screenshotPage.test.mjs, which exercises the same getBrowser.js singleton.
|
|
26
26
|
jest.unstable_mockModule('playwright-core', () => ({
|
|
27
27
|
chromium: { launch: jest.fn().mockRejectedValue(new Error("Executable doesn't exist")) },
|
|
28
|
+
selectors: { register: jest.fn(async () => {}) },
|
|
28
29
|
}));
|
|
30
|
+
// A missing shell would otherwise start a real download.
|
|
31
|
+
jest.unstable_mockModule('./installHeadlessShell.js', () => ({ default: () => null }));
|
|
29
32
|
|
|
30
33
|
// lib/build/config.js reads build/config.json from process.cwd() at import
|
|
31
34
|
// time — chdir into a fixture that has one before getBrowser.js (which
|
|
@@ -35,6 +38,7 @@ const originalCwd = process.cwd();
|
|
|
35
38
|
const fixtureDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-eval-operator-test-'));
|
|
36
39
|
fs.mkdirSync(path.join(fixtureDir, 'build'), { recursive: true });
|
|
37
40
|
fs.writeFileSync(path.join(fixtureDir, 'build', 'config.json'), JSON.stringify({ basePath: '' }));
|
|
41
|
+
fs.writeFileSync(path.join(fixtureDir, 'build', 'routes.json'), JSON.stringify([]));
|
|
38
42
|
process.chdir(fixtureDir);
|
|
39
43
|
|
|
40
44
|
const { default: evalOperatorHeadless } = await import('./evalOperatorHeadless.js');
|
|
@@ -68,5 +72,7 @@ test('evalOperatorHeadless returns an actionable error when no browser is availa
|
|
|
68
72
|
pageId: 'home',
|
|
69
73
|
expression: { _state: 'x' },
|
|
70
74
|
});
|
|
71
|
-
expect(result.error).toMatch(
|
|
75
|
+
expect(result.error).toMatch(
|
|
76
|
+
/No Chromium available. Run: npx playwright install chromium-headless-shell/
|
|
77
|
+
);
|
|
72
78
|
});
|
|
@@ -26,12 +26,13 @@ import { requestFromTab } from './tabChannel.js';
|
|
|
26
26
|
// (see tabChannel.js) — `{ value, errors }` on success, or an `{ error }`
|
|
27
27
|
// object if no tab is connected, the request timed out, or Inspector.jsx
|
|
28
28
|
// itself failed to evaluate the expression.
|
|
29
|
-
async function evalOperatorInTab({ pageId, expression }) {
|
|
29
|
+
async function evalOperatorInTab({ pageId, pathParams, expression }) {
|
|
30
30
|
if (type.isNone(expression)) {
|
|
31
31
|
throw new Error('evalOperatorInTab requires an "expression".');
|
|
32
32
|
}
|
|
33
33
|
const response = await requestFromTab({
|
|
34
34
|
pageId,
|
|
35
|
+
pathParams,
|
|
35
36
|
event: 'eval-request',
|
|
36
37
|
payload: { expression },
|
|
37
38
|
});
|
package/lib/docs/findConfig.js
CHANGED
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import path from 'node:path';
|
|
18
|
-
import { resolveConfigLocation } from '@lowdefy/errors';
|
|
18
|
+
import { ConfigError, resolveConfigLocation } from '@lowdefy/errors';
|
|
19
19
|
import { type } from '@lowdefy/helpers';
|
|
20
20
|
|
|
21
21
|
import buildPageIfNeeded from '../server/jitPageBuilder.js';
|
|
22
|
+
import readMergedMaps from '../server/readMergedMaps.js';
|
|
23
|
+
import getHazards from './getHazards.js';
|
|
22
24
|
import readBuildArtifact from './readBuildArtifact.js';
|
|
23
25
|
|
|
24
26
|
const MAX_MATCHES = 20;
|
|
@@ -45,16 +47,11 @@ function buildIdPattern(id) {
|
|
|
45
47
|
// (`root.pages[N:pageId]...`) carry the page segment inline, so a containment
|
|
46
48
|
// pattern covers those.
|
|
47
49
|
function buildPageScope({ pageId, keyMap }) {
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
// a page discriminator. Those pages' keys carry the page segment inline, so
|
|
52
|
-
// the pattern branch identifies them instead.
|
|
53
|
-
if (!type.isNone(rootKeyId) && rootKeyId === findGlobalRootId(keyMap)) {
|
|
54
|
-
rootKeyId = null;
|
|
55
|
-
}
|
|
50
|
+
// A page built during the skeleton build (e.g. the default 404 page) has
|
|
51
|
+
// no JIT root at all — its keys carry the page segment inline, so the
|
|
52
|
+
// pattern branch identifies them instead.
|
|
56
53
|
return {
|
|
57
|
-
rootKeyId,
|
|
54
|
+
rootKeyId: findPageRootKeyId({ pageId, keyMap }),
|
|
58
55
|
pagePattern: new RegExp(`\\bpages\\[\\d+:${escapeRegExp(pageId)}(:[^\\]]*)?\\]`),
|
|
59
56
|
};
|
|
60
57
|
}
|
|
@@ -68,38 +65,32 @@ function findGlobalRootId(keyMap) {
|
|
|
68
65
|
return null;
|
|
69
66
|
}
|
|
70
67
|
|
|
68
|
+
// A built page artifact mixes keys from two trees: the skeleton build keys
|
|
69
|
+
// the page stub (its `auth`, for one), chaining to the global config root,
|
|
70
|
+
// while the JIT build keys the page content under its own root. Only the
|
|
71
|
+
// JIT root discriminates pages, so skip keys that chain to the global root.
|
|
71
72
|
function findPageRootKeyId({ pageId, keyMap }) {
|
|
72
73
|
const artifact = readBuildArtifact({ name: `pages/${pageId}.json`, deserialize: true });
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const globalRootId = findGlobalRootId(keyMap);
|
|
75
|
+
for (const keyId of collectKeyIds(artifact)) {
|
|
76
|
+
const rootKeyId = resolveChainRootId({ keyId, keyMap });
|
|
77
|
+
if (rootKeyId !== globalRootId) {
|
|
78
|
+
return rootKeyId;
|
|
79
|
+
}
|
|
76
80
|
}
|
|
77
|
-
return
|
|
81
|
+
return null;
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
function
|
|
84
|
+
function collectKeyIds(node, keyIds = []) {
|
|
81
85
|
if (type.isString(node?.['~k'])) {
|
|
82
|
-
|
|
86
|
+
keyIds.push(node['~k']);
|
|
83
87
|
}
|
|
84
88
|
if (type.isArray(node)) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return keyId;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
if (!type.isObject(node)) {
|
|
94
|
-
return null;
|
|
89
|
+
node.forEach((item) => collectKeyIds(item, keyIds));
|
|
90
|
+
} else if (type.isObject(node)) {
|
|
91
|
+
Object.keys(node).forEach((key) => collectKeyIds(node[key], keyIds));
|
|
95
92
|
}
|
|
96
|
-
|
|
97
|
-
const keyId = findFirstKeyId(node[key]);
|
|
98
|
-
if (keyId) {
|
|
99
|
-
return keyId;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return null;
|
|
93
|
+
return keyIds;
|
|
103
94
|
}
|
|
104
95
|
|
|
105
96
|
// Walk ~k_parent to the top of the entry's subtree. addKeys assigns the tree
|
|
@@ -129,7 +120,46 @@ function belongsToPage({ keyId, entry, keyMap, pageScope }) {
|
|
|
129
120
|
return resolveChainRootId({ keyId, keyMap }) === pageScope.rootKeyId;
|
|
130
121
|
}
|
|
131
122
|
|
|
132
|
-
|
|
123
|
+
// A key path ends in the matched node's own segment, `blocks[2:my_button:Button]`
|
|
124
|
+
// (addKeys recArray writes `name[index:id:type]`), which is all the kind and
|
|
125
|
+
// type information hazards need.
|
|
126
|
+
const NODE_SEGMENT = /([A-Za-z_]+)\[\d+:([^\]:]+)(?::([^\]]*))?\]$/;
|
|
127
|
+
const SEGMENT_KINDS = {
|
|
128
|
+
blocks: 'blocks',
|
|
129
|
+
connections: 'connections',
|
|
130
|
+
pages: 'pages',
|
|
131
|
+
requests: 'requests',
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// writeRequests strips connectionId off the page's requests array after
|
|
135
|
+
// writing the per-request artifact, so a request's connection is only
|
|
136
|
+
// readable there. Without a pageId the match's page is not known, so the
|
|
137
|
+
// registry's pages are tried in turn — a requestId is unique within a page.
|
|
138
|
+
function findRequestConnectionId({ requestId, requestPageIds }) {
|
|
139
|
+
for (const pageId of requestPageIds) {
|
|
140
|
+
const request = readBuildArtifact({ name: `pages/${pageId}/requests/${requestId}.json` });
|
|
141
|
+
if (!type.isNone(request)) {
|
|
142
|
+
return request.connectionId ?? null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function hazardsForMatch({ keyPath, requestPageIds }) {
|
|
149
|
+
const segment = NODE_SEGMENT.exec(keyPath);
|
|
150
|
+
if (type.isNone(segment)) {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
const [, arrayName, id, nodeType] = segment;
|
|
154
|
+
const kind = SEGMENT_KINDS[arrayName] ?? null;
|
|
155
|
+
let connectionId = null;
|
|
156
|
+
if (kind === 'requests') {
|
|
157
|
+
connectionId = findRequestConnectionId({ requestId: id, requestPageIds });
|
|
158
|
+
}
|
|
159
|
+
return getHazards({ kind, type: nodeType ?? null, connectionId });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function scanKeyMap({ id, keyMap, refMap, configDirectory, pageScope, requestPageIds }) {
|
|
133
163
|
const pattern = buildIdPattern(id);
|
|
134
164
|
const matches = [];
|
|
135
165
|
for (const [keyId, entry] of Object.entries(keyMap)) {
|
|
@@ -148,6 +178,7 @@ function scanKeyMap({ id, keyMap, refMap, configDirectory, pageScope }) {
|
|
|
148
178
|
matches.push({
|
|
149
179
|
keyPath: entry.key,
|
|
150
180
|
location: resolveConfigLocation({ configKey: keyId, keyMap, refMap, configDirectory }),
|
|
181
|
+
hazards: hazardsForMatch({ keyPath: entry.key, requestPageIds }),
|
|
151
182
|
});
|
|
152
183
|
}
|
|
153
184
|
return matches;
|
|
@@ -163,8 +194,8 @@ function deIndexId(id) {
|
|
|
163
194
|
return id.replace(/\.\d+(?=\.|$)/g, () => '.$');
|
|
164
195
|
}
|
|
165
196
|
|
|
166
|
-
function scanKeyMapWithDeIndex({ id, keyMap, refMap, configDirectory, pageScope }) {
|
|
167
|
-
const matches = scanKeyMap({ id, keyMap, refMap, configDirectory, pageScope });
|
|
197
|
+
function scanKeyMapWithDeIndex({ id, keyMap, refMap, configDirectory, pageScope, requestPageIds }) {
|
|
198
|
+
const matches = scanKeyMap({ id, keyMap, refMap, configDirectory, pageScope, requestPageIds });
|
|
168
199
|
if (matches.length > 0) {
|
|
169
200
|
return matches;
|
|
170
201
|
}
|
|
@@ -172,17 +203,25 @@ function scanKeyMapWithDeIndex({ id, keyMap, refMap, configDirectory, pageScope
|
|
|
172
203
|
if (deIndexedId === id) {
|
|
173
204
|
return matches;
|
|
174
205
|
}
|
|
175
|
-
return scanKeyMap({
|
|
206
|
+
return scanKeyMap({
|
|
207
|
+
id: deIndexedId,
|
|
208
|
+
keyMap,
|
|
209
|
+
refMap,
|
|
210
|
+
configDirectory,
|
|
211
|
+
pageScope,
|
|
212
|
+
requestPageIds,
|
|
213
|
+
});
|
|
176
214
|
}
|
|
177
215
|
|
|
178
216
|
// Locates a block/request/connection/etc by id, or a page by pageId. Block
|
|
179
|
-
// content only
|
|
180
|
-
// lib/server/jitPageBuilder.js),
|
|
181
|
-
//
|
|
217
|
+
// content is only in the key maps once its page has been JIT-built (see
|
|
218
|
+
// lib/server/jitPageBuilder.js), and the dev server keeps the jitMaps/ files of
|
|
219
|
+
// its current and previous build contexts only, so pass `pageId` to force-build
|
|
220
|
+
// and scan a specific page. Never throws on not-found — agents should be able to treat
|
|
182
221
|
// "no matches" as a normal result and try another id or pageId.
|
|
183
222
|
async function findConfig({ id, pageId }) {
|
|
184
223
|
if (type.isNone(id) || !type.isString(id)) {
|
|
185
|
-
throw new
|
|
224
|
+
throw new ConfigError(
|
|
186
225
|
`findConfig requires an "id" string. Received ${JSON.stringify(id)}. ` +
|
|
187
226
|
'Use GET /lowdefy-docs/find/:id.'
|
|
188
227
|
);
|
|
@@ -190,6 +229,7 @@ async function findConfig({ id, pageId }) {
|
|
|
190
229
|
|
|
191
230
|
const pageRegistry = readBuildArtifact({ name: 'pageRegistry.json' }) ?? {};
|
|
192
231
|
const configDirectory = process.env.LOWDEFY_DIRECTORY_CONFIG || process.cwd();
|
|
232
|
+
const buildDirectory = path.join(process.cwd(), 'build');
|
|
193
233
|
|
|
194
234
|
// A page's root block renders with blockId === pageId, so open-in-editor
|
|
195
235
|
// and feedback enrichment hit this branch for it — they read
|
|
@@ -202,6 +242,7 @@ async function findConfig({ id, pageId }) {
|
|
|
202
242
|
matches.push({
|
|
203
243
|
keyPath: `pages[${id}]`,
|
|
204
244
|
location: { source: path.resolve(configDirectory, refPath) },
|
|
245
|
+
hazards: getHazards({ kind: 'pages' }),
|
|
205
246
|
});
|
|
206
247
|
}
|
|
207
248
|
return { kind: 'page', pageId: id, file: refPath, matches };
|
|
@@ -217,7 +258,6 @@ async function findConfig({ id, pageId }) {
|
|
|
217
258
|
};
|
|
218
259
|
}
|
|
219
260
|
|
|
220
|
-
const buildDirectory = path.join(process.cwd(), 'build');
|
|
221
261
|
try {
|
|
222
262
|
await buildPageIfNeeded({ pageId, buildDirectory, configDirectory });
|
|
223
263
|
} catch (error) {
|
|
@@ -227,14 +267,14 @@ async function findConfig({ id, pageId }) {
|
|
|
227
267
|
};
|
|
228
268
|
}
|
|
229
269
|
|
|
230
|
-
const keyMap =
|
|
231
|
-
const refMap = readBuildArtifact({ name: 'refMap.json' }) ?? {};
|
|
270
|
+
const { keyMap, refMap } = await readMergedMaps({ buildDirectory });
|
|
232
271
|
const matches = scanKeyMapWithDeIndex({
|
|
233
272
|
id,
|
|
234
273
|
keyMap,
|
|
235
274
|
refMap,
|
|
236
275
|
configDirectory,
|
|
237
276
|
pageScope: buildPageScope({ pageId, keyMap }),
|
|
277
|
+
requestPageIds: [pageId],
|
|
238
278
|
});
|
|
239
279
|
return matches.length > 0
|
|
240
280
|
? { matches }
|
|
@@ -246,14 +286,20 @@ async function findConfig({ id, pageId }) {
|
|
|
246
286
|
};
|
|
247
287
|
}
|
|
248
288
|
|
|
249
|
-
const keyMap =
|
|
250
|
-
const
|
|
251
|
-
|
|
289
|
+
const { keyMap, refMap } = await readMergedMaps({ buildDirectory });
|
|
290
|
+
const matches = scanKeyMapWithDeIndex({
|
|
291
|
+
id,
|
|
292
|
+
keyMap,
|
|
293
|
+
refMap,
|
|
294
|
+
configDirectory,
|
|
295
|
+
requestPageIds: Object.keys(pageRegistry),
|
|
296
|
+
});
|
|
252
297
|
return {
|
|
253
298
|
matches,
|
|
254
299
|
note:
|
|
255
|
-
'Scanned without a pageId —
|
|
256
|
-
'
|
|
300
|
+
'Scanned without a pageId — only pages built since the last two page edits are ' +
|
|
301
|
+
'covered; content on other pages was not scanned. Pass ?pageId= to force-build and ' +
|
|
302
|
+
'scan a specific page.',
|
|
257
303
|
};
|
|
258
304
|
}
|
|
259
305
|
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
|
|
21
|
+
// Largest response returned inline, in serialized characters. The tool result
|
|
22
|
+
// is pretty-printed, which adds roughly half again, so this keeps a result
|
|
23
|
+
// inside the output budget of common MCP clients.
|
|
24
|
+
const MAX_INLINE_RESPONSE_CHARS = 40_000;
|
|
25
|
+
|
|
26
|
+
// Saved responses kept per app; the oldest beyond this are deleted on write.
|
|
27
|
+
const MAX_SAVED_RESPONSES = 20;
|
|
28
|
+
|
|
29
|
+
function pruneResponseFiles({ directory }) {
|
|
30
|
+
const files = fs
|
|
31
|
+
.readdirSync(directory)
|
|
32
|
+
.filter((fileName) => fileName.endsWith('.json'))
|
|
33
|
+
.map((fileName) => {
|
|
34
|
+
const filePath = path.join(directory, fileName);
|
|
35
|
+
return { filePath, mtime: fs.statSync(filePath).mtimeMs };
|
|
36
|
+
})
|
|
37
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
38
|
+
files.slice(MAX_SAVED_RESPONSES).forEach(({ filePath }) => fs.rmSync(filePath, { force: true }));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function writeResponseFile({ name, response }) {
|
|
42
|
+
const configDirectory = process.env.LOWDEFY_DIRECTORY_CONFIG || process.cwd();
|
|
43
|
+
const directory = path.join(configDirectory, '.lowdefy', 'responses');
|
|
44
|
+
const safeName = name.replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
45
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
46
|
+
const suffix = crypto.randomBytes(3).toString('hex');
|
|
47
|
+
const filePath = path.join(directory, `${safeName}-${stamp}-${suffix}.json`);
|
|
48
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
49
|
+
fs.writeFileSync(filePath, JSON.stringify(response, null, 2));
|
|
50
|
+
pruneResponseFiles({ directory });
|
|
51
|
+
return filePath;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Returns the result untouched when its response fits inline. A larger
|
|
55
|
+
// response, or any response when saveResponse is set, is written in full to
|
|
56
|
+
// .lowdefy/responses/ in the config directory, and the result carries the
|
|
57
|
+
// file's path and size in its place, so nothing is cut and an agent can read
|
|
58
|
+
// or query the whole value.
|
|
59
|
+
function fitResponse({ result, name, saveResponse = false }) {
|
|
60
|
+
// A request can resolve with no response; it is written as null.
|
|
61
|
+
const { response = null, ...rest } = result;
|
|
62
|
+
const json = JSON.stringify(response);
|
|
63
|
+
if (!saveResponse && json.length <= MAX_INLINE_RESPONSE_CHARS) {
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
const fitted = {
|
|
67
|
+
...rest,
|
|
68
|
+
responseFile: writeResponseFile({ name, response }),
|
|
69
|
+
responseChars: json.length,
|
|
70
|
+
};
|
|
71
|
+
if (Array.isArray(response)) {
|
|
72
|
+
fitted.responseItems = response.length;
|
|
73
|
+
}
|
|
74
|
+
fitted.note = saveResponse
|
|
75
|
+
? 'The full response was written to responseFile.'
|
|
76
|
+
: `The response is ${json.length} characters, over the ${MAX_INLINE_RESPONSE_CHARS} returned inline, so it was written in full to responseFile. Read or query that file instead.`;
|
|
77
|
+
return fitted;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { MAX_INLINE_RESPONSE_CHARS, MAX_SAVED_RESPONSES };
|
|
81
|
+
export default fitResponse;
|
|
@@ -0,0 +1,128 @@
|
|
|
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 fitResponse, { MAX_INLINE_RESPONSE_CHARS, MAX_SAVED_RESPONSES } from './fitResponse.js';
|
|
22
|
+
|
|
23
|
+
let configDirectory;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-fit-response-'));
|
|
27
|
+
process.env.LOWDEFY_DIRECTORY_CONFIG = configDirectory;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
delete process.env.LOWDEFY_DIRECTORY_CONFIG;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('fitResponse returns the result untouched when the response fits', () => {
|
|
35
|
+
const result = { refused: false, response: [{ _id: 1 }], status: 'success' };
|
|
36
|
+
|
|
37
|
+
expect(fitResponse({ result, name: 'home.get_rows' })).toBe(result);
|
|
38
|
+
expect(fs.existsSync(path.join(configDirectory, '.lowdefy'))).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('fitResponse keeps a null response untouched', () => {
|
|
42
|
+
const result = { response: null, status: 'success' };
|
|
43
|
+
|
|
44
|
+
expect(fitResponse({ result, name: 'home.get_rows' })).toBe(result);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('fitResponse keeps a result with no response untouched', () => {
|
|
48
|
+
// A failed routine, or one that ends without :return, has no response.
|
|
49
|
+
const result = {
|
|
50
|
+
error: { '~e': { name: 'ServiceError', message: 'Something went wrong.' } },
|
|
51
|
+
response: undefined,
|
|
52
|
+
status: 'error',
|
|
53
|
+
success: false,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
expect(fitResponse({ result, name: 'search' })).toBe(result);
|
|
57
|
+
expect(fs.existsSync(path.join(configDirectory, '.lowdefy'))).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('fitResponse writes an oversized response to a file and returns its path in its place', () => {
|
|
61
|
+
const rows = Array.from({ length: 500 }, (_, index) => ({ _id: index, title: 'x'.repeat(100) }));
|
|
62
|
+
const result = { response: rows, status: 'success', error: null };
|
|
63
|
+
|
|
64
|
+
const fitted = fitResponse({ result, name: 'tasks/list.get_rows' });
|
|
65
|
+
|
|
66
|
+
expect(fitted.response).toBeUndefined();
|
|
67
|
+
expect(fitted.status).toBe('success');
|
|
68
|
+
expect(fitted.error).toBeNull();
|
|
69
|
+
expect(fitted.responseItems).toBe(500);
|
|
70
|
+
expect(fitted.responseChars).toBe(JSON.stringify(rows).length);
|
|
71
|
+
expect(fitted.responseChars).toBeGreaterThan(MAX_INLINE_RESPONSE_CHARS);
|
|
72
|
+
expect(fitted.note).toContain('written in full to responseFile');
|
|
73
|
+
expect(path.dirname(fitted.responseFile)).toBe(
|
|
74
|
+
path.join(configDirectory, '.lowdefy', 'responses')
|
|
75
|
+
);
|
|
76
|
+
expect(path.basename(fitted.responseFile).startsWith('tasks_list.get_rows-')).toBe(true);
|
|
77
|
+
expect(JSON.parse(fs.readFileSync(fitted.responseFile, 'utf8'))).toEqual(rows);
|
|
78
|
+
expect(result.response).toBe(rows);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('fitResponse writes a small response to a file when saveResponse is set', () => {
|
|
82
|
+
const result = { response: { total: 3 }, status: 'success' };
|
|
83
|
+
|
|
84
|
+
const fitted = fitResponse({ result, name: 'report', saveResponse: true });
|
|
85
|
+
|
|
86
|
+
expect(fitted.response).toBeUndefined();
|
|
87
|
+
expect(fitted.responseItems).toBeUndefined();
|
|
88
|
+
expect(fitted.note).toBe('The full response was written to responseFile.');
|
|
89
|
+
expect(JSON.parse(fs.readFileSync(fitted.responseFile, 'utf8'))).toEqual({ total: 3 });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('fitResponse writes a missing response as null when saveResponse is set', () => {
|
|
93
|
+
const result = { status: 'success' };
|
|
94
|
+
|
|
95
|
+
const fitted = fitResponse({ result, name: 'home.update_row', saveResponse: true });
|
|
96
|
+
|
|
97
|
+
expect(fitted.responseChars).toBe(4);
|
|
98
|
+
expect(JSON.parse(fs.readFileSync(fitted.responseFile, 'utf8'))).toBeNull();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('fitResponse gives every saved response its own file', () => {
|
|
102
|
+
const files = Array.from({ length: 5 }, (_, index) =>
|
|
103
|
+
fitResponse({ result: { response: { index } }, name: 'same', saveResponse: true })
|
|
104
|
+
).map(({ responseFile }) => responseFile);
|
|
105
|
+
|
|
106
|
+
expect(new Set(files).size).toBe(5);
|
|
107
|
+
files.forEach((file, index) => {
|
|
108
|
+
expect(JSON.parse(fs.readFileSync(file, 'utf8'))).toEqual({ index });
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('fitResponse keeps only the most recent saved responses', () => {
|
|
113
|
+
const directory = path.join(configDirectory, '.lowdefy', 'responses');
|
|
114
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
115
|
+
const old = path.join(directory, 'old-response.json');
|
|
116
|
+
fs.writeFileSync(old, '{}');
|
|
117
|
+
const longAgo = new Date('2000-01-01T00:00:00.000Z');
|
|
118
|
+
fs.utimesSync(old, longAgo, longAgo);
|
|
119
|
+
|
|
120
|
+
const files = Array.from({ length: MAX_SAVED_RESPONSES }, (_, index) =>
|
|
121
|
+
fitResponse({ result: { response: { index } }, name: 'report', saveResponse: true })
|
|
122
|
+
).map(({ responseFile }) => responseFile);
|
|
123
|
+
|
|
124
|
+
const kept = fs.readdirSync(directory).map((fileName) => path.join(directory, fileName));
|
|
125
|
+
expect(kept).toHaveLength(MAX_SAVED_RESPONSES);
|
|
126
|
+
expect(kept).not.toContain(old);
|
|
127
|
+
expect(kept.sort()).toEqual([...files].sort());
|
|
128
|
+
});
|