@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,115 @@
|
|
|
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
|
+
import { type } from '@lowdefy/helpers';
|
|
17
|
+
|
|
18
|
+
import loadFrameworkHazards from './loadFrameworkHazards.js';
|
|
19
|
+
import normalizeTypeKind from './normalizeTypeKind.js';
|
|
20
|
+
import readBuildArtifact from './readBuildArtifact.js';
|
|
21
|
+
|
|
22
|
+
// Where each kind's plugin-declared hazards land in the build: block metas
|
|
23
|
+
// pick them from meta.hazards, request metas ride along whole, operators
|
|
24
|
+
// get a dedicated metas artifact (they carry no other meta).
|
|
25
|
+
const TYPE_ATTACHED_HAZARDS = {
|
|
26
|
+
blocks: (typeName) => readBuildArtifact({ name: 'plugins/blockMetas.json' })?.[typeName]?.hazards,
|
|
27
|
+
operators: (typeName) =>
|
|
28
|
+
readBuildArtifact({ name: 'plugins/operatorMetas.json' })?.[typeName]?.hazards,
|
|
29
|
+
requests: (typeName) =>
|
|
30
|
+
readBuildArtifact({ name: 'plugins/requestSchemas.json' })?.[typeName]?.meta?.hazards,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// The closed set of `when` conditions a framework hazard may declare. A
|
|
34
|
+
// hazard whose `when` is not here is skipped with a warning rather than
|
|
35
|
+
// guessed at — an agent must be able to rely on every hazard it is shown.
|
|
36
|
+
const WHEN_CONDITIONS = {
|
|
37
|
+
connectionTenantNotNone: ({ kind, connectionId }) => {
|
|
38
|
+
if (kind !== 'requests' || type.isNone(connectionId)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const tenantConnections = readBuildArtifact({ name: 'tenantConnections.json' }) ?? [];
|
|
42
|
+
return tenantConnections.some((connection) => connection.connectionId === connectionId);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const warnedUnknownWhen = new Set();
|
|
47
|
+
|
|
48
|
+
function normalizeKind(kind) {
|
|
49
|
+
if (type.isNone(kind)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const lowered = String(kind).toLowerCase();
|
|
53
|
+
if (lowered === 'page' || lowered === 'pages') {
|
|
54
|
+
return 'pages';
|
|
55
|
+
}
|
|
56
|
+
return normalizeTypeKind({ kind: lowered });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function hazardApplies({ hazard, kind, typeName, connectionId }) {
|
|
60
|
+
const { kinds, types, when } = hazard.appliesTo ?? {};
|
|
61
|
+
if (type.isNone(kinds) && type.isNone(types)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if (!type.isNone(kinds) && !kinds.includes(kind)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (!type.isNone(types) && !types.includes(typeName)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (type.isNone(when)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
const condition = WHEN_CONDITIONS[when];
|
|
74
|
+
if (type.isNone(condition)) {
|
|
75
|
+
if (!warnedUnknownWhen.has(when)) {
|
|
76
|
+
warnedUnknownWhen.add(when);
|
|
77
|
+
console.warn(
|
|
78
|
+
`Hazard "${hazard.id}" declares unknown when condition ${JSON.stringify(
|
|
79
|
+
when
|
|
80
|
+
)} and was skipped.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return condition({ kind, connectionId });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Hazards for a type: the plugin's own (meta.hazards, via the build
|
|
89
|
+
// artifacts) first, then the framework-level list filtered by kind/type and
|
|
90
|
+
// its `when` condition, de-duplicated by id.
|
|
91
|
+
function getHazards({ kind, type: typeName, connectionId }) {
|
|
92
|
+
const normalizedKind = normalizeKind(kind);
|
|
93
|
+
const hazards = [];
|
|
94
|
+
const seen = new Set();
|
|
95
|
+
const push = (hazard) => {
|
|
96
|
+
if (type.isNone(hazard?.id) || seen.has(hazard.id)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
seen.add(hazard.id);
|
|
100
|
+
hazards.push({ id: hazard.id, message: hazard.message, see: hazard.see ?? null });
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const readTypeAttached = TYPE_ATTACHED_HAZARDS[normalizedKind];
|
|
104
|
+
if (!type.isNone(readTypeAttached) && !type.isNone(typeName)) {
|
|
105
|
+
(readTypeAttached(typeName) ?? []).forEach(push);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
(loadFrameworkHazards() ?? [])
|
|
109
|
+
.filter((hazard) => hazardApplies({ hazard, kind: normalizedKind, typeName, connectionId }))
|
|
110
|
+
.forEach(push);
|
|
111
|
+
|
|
112
|
+
return hazards;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export default getHazards;
|
|
@@ -0,0 +1,175 @@
|
|
|
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
|
+
import { jest } from '@jest/globals';
|
|
17
|
+
|
|
18
|
+
import setupTestFixtures from './setupTestFixtures.mjs';
|
|
19
|
+
|
|
20
|
+
// getHazards reads build artifacts from process.cwd() — point it at the
|
|
21
|
+
// fixture server before the module is imported.
|
|
22
|
+
const fixtureDir = setupTestFixtures();
|
|
23
|
+
process.chdir(fixtureDir);
|
|
24
|
+
|
|
25
|
+
// The framework list ships in @lowdefy/docs-content; mock the loader so each
|
|
26
|
+
// test controls the list (and the "package not installed" case).
|
|
27
|
+
const mockLoadFrameworkHazards = jest.fn();
|
|
28
|
+
jest.unstable_mockModule('./loadFrameworkHazards.js', () => ({
|
|
29
|
+
default: mockLoadFrameworkHazards,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
const { default: getHazards } = await import('./getHazards.js');
|
|
33
|
+
|
|
34
|
+
const frameworkHazards = [
|
|
35
|
+
{
|
|
36
|
+
id: 'state-in-request-properties',
|
|
37
|
+
appliesTo: { kinds: ['requests'] },
|
|
38
|
+
message: '_state in request properties is always undefined.',
|
|
39
|
+
see: 'concepts/connections-and-requests',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'visible-false-prunes-state',
|
|
43
|
+
appliesTo: { kinds: ['blocks'] },
|
|
44
|
+
message: 'visible: false removes the block and its state.',
|
|
45
|
+
see: 'concepts/lowdefy-schema',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'link-same-page-no-onmount',
|
|
49
|
+
appliesTo: { types: ['Link'] },
|
|
50
|
+
message: 'A Link to the page already open does not remount it.',
|
|
51
|
+
see: 'actions/link',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'tenant-wall-lookup',
|
|
55
|
+
appliesTo: { kinds: ['requests'], when: 'connectionTenantNotNone' },
|
|
56
|
+
message: 'The wall injects a tenant match into every lookup.',
|
|
57
|
+
see: null,
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
let warnSpy;
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
mockLoadFrameworkHazards.mockReset();
|
|
65
|
+
mockLoadFrameworkHazards.mockReturnValue(frameworkHazards);
|
|
66
|
+
warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
warnSpy.mockRestore();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('getHazards returns type-attached hazards only when no framework hazard applies', () => {
|
|
74
|
+
mockLoadFrameworkHazards.mockReturnValue([]);
|
|
75
|
+
expect(getHazards({ kind: 'operators', type: '_get' })).toEqual([
|
|
76
|
+
{ id: 'get-fixture-hazard', message: '_get fixture hazard.', see: 'operators/_get' },
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('getHazards returns framework hazards only when the type declares none', () => {
|
|
81
|
+
const hazards = getHazards({ kind: 'blocks', type: 'Button' });
|
|
82
|
+
expect(hazards).toEqual([
|
|
83
|
+
{
|
|
84
|
+
id: 'visible-false-prunes-state',
|
|
85
|
+
message: 'visible: false removes the block and its state.',
|
|
86
|
+
see: 'concepts/lowdefy-schema',
|
|
87
|
+
},
|
|
88
|
+
]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('getHazards matches framework hazards by exact type name across kinds', () => {
|
|
92
|
+
const hazards = getHazards({ kind: 'actions', type: 'Link' });
|
|
93
|
+
expect(hazards.map((hazard) => hazard.id)).toEqual(['link-same-page-no-onmount']);
|
|
94
|
+
expect(getHazards({ kind: 'actions', type: 'SetState' })).toEqual([]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('getHazards merges both sources, type-attached first, de-duplicated by id', () => {
|
|
98
|
+
const hazards = getHazards({ kind: 'blocks', type: 'TestBlock' });
|
|
99
|
+
expect(hazards.map((hazard) => hazard.id)).toEqual([
|
|
100
|
+
'test-block-hazard',
|
|
101
|
+
'visible-false-prunes-state',
|
|
102
|
+
]);
|
|
103
|
+
expect(hazards[1].message).toEqual('Plugin wording for visible: false.');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('getHazards accepts singular kind names', () => {
|
|
107
|
+
expect(getHazards({ kind: 'block', type: 'Button' }).map((hazard) => hazard.id)).toEqual([
|
|
108
|
+
'visible-false-prunes-state',
|
|
109
|
+
]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('getHazards evaluates connectionTenantNotNone true for a request over a walled connection', () => {
|
|
113
|
+
const hazards = getHazards({
|
|
114
|
+
kind: 'requests',
|
|
115
|
+
type: 'WriteRequest',
|
|
116
|
+
connectionId: 'tenant_db',
|
|
117
|
+
});
|
|
118
|
+
expect(hazards.map((hazard) => hazard.id)).toEqual([
|
|
119
|
+
'write-request-hazard',
|
|
120
|
+
'state-in-request-properties',
|
|
121
|
+
'tenant-wall-lookup',
|
|
122
|
+
]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('getHazards evaluates connectionTenantNotNone false for a shared connection or no connectionId', () => {
|
|
126
|
+
const shared = getHazards({ kind: 'requests', type: 'WriteRequest', connectionId: 'shared_db' });
|
|
127
|
+
expect(shared.map((hazard) => hazard.id)).toEqual([
|
|
128
|
+
'write-request-hazard',
|
|
129
|
+
'state-in-request-properties',
|
|
130
|
+
]);
|
|
131
|
+
const noConnection = getHazards({ kind: 'requests', type: 'WriteRequest' });
|
|
132
|
+
expect(noConnection.map((hazard) => hazard.id)).toEqual([
|
|
133
|
+
'write-request-hazard',
|
|
134
|
+
'state-in-request-properties',
|
|
135
|
+
]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('getHazards never applies connectionTenantNotNone outside requests', () => {
|
|
139
|
+
const hazards = getHazards({ kind: 'blocks', type: 'Button', connectionId: 'tenant_db' });
|
|
140
|
+
expect(hazards.map((hazard) => hazard.id)).toEqual(['visible-false-prunes-state']);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('getHazards skips a hazard with an unknown when condition and warns once', () => {
|
|
144
|
+
mockLoadFrameworkHazards.mockReturnValue([
|
|
145
|
+
{
|
|
146
|
+
id: 'mystery',
|
|
147
|
+
appliesTo: { kinds: ['blocks'], when: 'moonIsFull' },
|
|
148
|
+
message: 'Never shown.',
|
|
149
|
+
see: null,
|
|
150
|
+
},
|
|
151
|
+
]);
|
|
152
|
+
expect(getHazards({ kind: 'blocks', type: 'Button' })).toEqual([]);
|
|
153
|
+
expect(getHazards({ kind: 'blocks', type: 'Button' })).toEqual([]);
|
|
154
|
+
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
155
|
+
expect(warnSpy.mock.calls[0][0]).toContain('"moonIsFull"');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('getHazards returns an empty list when hazards.json is unavailable and the type has none', () => {
|
|
159
|
+
mockLoadFrameworkHazards.mockReturnValue(null);
|
|
160
|
+
expect(getHazards({ kind: 'blocks', type: 'Button' })).toEqual([]);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('getHazards still returns type-attached hazards when hazards.json is unavailable', () => {
|
|
164
|
+
mockLoadFrameworkHazards.mockReturnValue(null);
|
|
165
|
+
expect(getHazards({ kind: 'blocks', type: 'TestBlock' }).map((hazard) => hazard.id)).toEqual([
|
|
166
|
+
'test-block-hazard',
|
|
167
|
+
'visible-false-prunes-state',
|
|
168
|
+
]);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('getHazards returns an empty list for kinds and types without hazards', () => {
|
|
172
|
+
expect(getHazards({ kind: 'connections', type: 'AxiosHttp' })).toEqual([]);
|
|
173
|
+
expect(getHazards({ kind: 'pages' })).toEqual([]);
|
|
174
|
+
expect(getHazards({ kind: 'nonsense', type: 'X' })).toEqual([]);
|
|
175
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import path from 'path';
|
|
18
|
+
|
|
19
|
+
// Where the dev mail sink writes each captured message (manager process) and
|
|
20
|
+
// where journeys read them (server process). Inside .lowdefy because captured
|
|
21
|
+
// mail carries sign-in tokens and must never sit in a committable spot.
|
|
22
|
+
function getMailOutboxDirectory({ configDirectory }) {
|
|
23
|
+
return path.join(configDirectory, '.lowdefy', 'mail');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default getMailOutboxDirectory;
|
package/lib/docs/getOverview.js
CHANGED
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { parsePathPattern } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
import getDocsIndex from './getDocsIndex.js';
|
|
17
20
|
import getDocsManifest from './getDocsManifest.js';
|
|
21
|
+
import lowdefyConfig from '../build/config.js';
|
|
18
22
|
import listPlugins from './listPlugins.js';
|
|
19
23
|
import readBuildArtifact from './readBuildArtifact.js';
|
|
20
24
|
|
|
@@ -31,6 +35,52 @@ function countKinds({ availableTypes }) {
|
|
|
31
35
|
};
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
// The dev server mounts every route under config.basePath, the docs tools
|
|
39
|
+
// included, so an app with a basePath must tell agents the prefix.
|
|
40
|
+
function basePathLines() {
|
|
41
|
+
const basePath = lowdefyConfig.basePath ?? '';
|
|
42
|
+
if (basePath === '') return [];
|
|
43
|
+
return [
|
|
44
|
+
`This app sets \`config.basePath: ${basePath}\`, so every route below is served under it: \`GET ${basePath}/lowdefy-docs\`, \`${basePath}/lowdefy-docs/mcp\`, \`${basePath}/api/...\`.`,
|
|
45
|
+
'',
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// A page whose path has placeholders has one instance per set of values, so
|
|
50
|
+
// the page tools need pathParams to open it.
|
|
51
|
+
function patternedPageLines() {
|
|
52
|
+
const routes = readBuildArtifact({ name: 'routes.json', deserialize: true });
|
|
53
|
+
const patterned = routes.filter((route) =>
|
|
54
|
+
parsePathPattern(route.path).some((segment) => segment.name !== undefined)
|
|
55
|
+
);
|
|
56
|
+
if (patterned.length === 0) return [];
|
|
57
|
+
return [
|
|
58
|
+
'## Pages with path parameters',
|
|
59
|
+
'',
|
|
60
|
+
'These pages are served at a URL pattern with placeholders. Pass `pathParams` with a value for each placeholder (e.g. `{"ticket_id": "1"}`) to `lowdefy_screenshot_page`, `lowdefy_inspect_state`, `lowdefy_eval_operator`, `lowdefy_snapshot_state` and `lowdefy_run_journey`, or as `?pathParams=` JSON on the GET routes. A live tab is read at the instance it shows without them.',
|
|
61
|
+
'',
|
|
62
|
+
...patterned.map((route) => `- \`${route.pageId}\`: \`${route.path}\``),
|
|
63
|
+
'',
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Plugin and module docs, with each module's pages named so an agent can
|
|
68
|
+
// read them without a search.
|
|
69
|
+
function appDocsLines() {
|
|
70
|
+
const { entries } = getDocsIndex();
|
|
71
|
+
const pluginDocs = entries.filter(
|
|
72
|
+
(entry) => entry.source === 'plugin' || entry.source === 'local-plugin'
|
|
73
|
+
);
|
|
74
|
+
const moduleDocs = entries.filter((entry) => entry.source === 'module');
|
|
75
|
+
const lines = [
|
|
76
|
+
`- ${pluginDocs.length} plugin doc pages and ${moduleDocs.length} module doc pages from this app's own plugins and modules (search them with \`source\`, read them with \`lowdefy_get_doc\`).`,
|
|
77
|
+
];
|
|
78
|
+
for (const entry of moduleDocs) {
|
|
79
|
+
lines.push(` - \`${entry.slug}\`: ${entry.title}`);
|
|
80
|
+
}
|
|
81
|
+
return lines;
|
|
82
|
+
}
|
|
83
|
+
|
|
34
84
|
function getOverview() {
|
|
35
85
|
const availableTypes = readBuildArtifact({ name: 'plugins/availableTypes.json' }) ?? {};
|
|
36
86
|
const counts = countKinds({ availableTypes });
|
|
@@ -45,12 +95,15 @@ function getOverview() {
|
|
|
45
95
|
'',
|
|
46
96
|
'This server describes everything installed in THIS project — never guess type names or properties, look them up here first.',
|
|
47
97
|
'',
|
|
98
|
+
...basePathLines(),
|
|
99
|
+
...patternedPageLines(),
|
|
48
100
|
'## What is available',
|
|
49
101
|
'',
|
|
50
102
|
`- ${counts.blocks} block types, ${counts.operators} operators, ${counts.actions} actions, ${counts.connections} connections, ${counts.requests} request types (from ${plugins.length} plugin packages, including this project's local plugins).`,
|
|
51
103
|
manifest
|
|
52
104
|
? `- ${manifest.docs.length} core documentation pages (sections: ${sections.join(', ')}).`
|
|
53
105
|
: '- Core documentation package (@lowdefy/docs-content) is not installed.',
|
|
106
|
+
...appDocsLines(),
|
|
54
107
|
'',
|
|
55
108
|
'## How to use this (recommended order)',
|
|
56
109
|
'',
|
|
@@ -58,27 +111,34 @@ function getOverview() {
|
|
|
58
111
|
'2. **Get the exact contract**: `GET /lowdefy-docs/schema/{kind}/{type}` (e.g. `/lowdefy-docs/schema/blocks/Button`) — or `lowdefy_get_schema`. JSON Schema of all properties.',
|
|
59
112
|
'3. **See real usage**: `GET /lowdefy-docs/examples/{type}` — or `lowdefy_get_examples`. YAML examples for block types.',
|
|
60
113
|
'4. **Read concept docs**: `GET /lowdefy-docs/content/{slug}` (e.g. `/lowdefy-docs/content/operators/_get`) — or `lowdefy_get_doc`. Start with `concepts/lowdefy-schema`, `concepts/blocks`, `concepts/events-and-actions`, `concepts/connections-and-requests`, `concepts/operators`.',
|
|
61
|
-
'5. **Search**: `GET /lowdefy-docs/search?q=...` — or `lowdefy_search_docs`.',
|
|
62
|
-
'6. **
|
|
114
|
+
'5. **Search**: `GET /lowdefy-docs/search?q=...` — or `lowdefy_search_docs`. The query is split into words and pages are ranked by how many match, title matches first. Each hit names its `source` (`core`, `plugin`, `local-plugin` or `module`), package and version; `&source=` narrows the search to one source.',
|
|
115
|
+
'6. **Icons**: `GET /lowdefy-docs/icons?q=...` — or `lowdefy_search_icons`. Use a semantic name (`icon: edit`); otherwise use a Lucide name in PascalCase (`icon: Receipt`) found by search. Never invent a name. In HTML use `<i data-icon="edit"></i>`, `data-tooltip` and `data-popover`.',
|
|
116
|
+
"7. **Plugin and module docs**: search and `lowdefy_get_doc` also cover the docs this app's own plugins and modules ship, local ones included: each README (`plugins/{package}`, `modules/{id}`), each `docs/*.md` file (`plugins/{package}/{file}`, `modules/{id}/{file}`), and for each module a generated page of the components, exports and vars its `module.lowdefy.yaml` declares (`modules/{id}/manifest`). `GET /lowdefy-docs/plugins` lists every installed plugin; `GET /lowdefy-docs/plugin-doc/{package}` returns all the markdown one plugin ships as one page.",
|
|
117
|
+
'',
|
|
118
|
+
'A `stale` field on a JSON response, a `> STALE:` banner on markdown, or an `X-Lowdefy-Stale` header means the last build FAILED and the served build is behind the source — this answer predates your latest edits. Call `GET /lowdefy-docs/build-status` and fix the errors before trusting anything else.',
|
|
63
119
|
'',
|
|
64
120
|
'## The feedback loop (after every edit)',
|
|
65
121
|
'',
|
|
66
122
|
'The dev server rebuilds automatically when config files change. After EVERY edit:',
|
|
67
123
|
'',
|
|
68
|
-
'1. **Check what broke**: `GET /lowdefy-docs/build-status` — or `lowdefy_build_status
|
|
124
|
+
'1. **Check what broke**: `GET /lowdefy-docs/build-status?wait=true` — or `lowdefy_build_status` with `wait: true`, which answers once your latest edit has been processed and the pages it touched have been built. Current build errors and warnings with source file locations, the pages that fail to build (`pages.failed`), plus recent browser runtime errors. Errors from before the latest edit come apart under `earlierErrors`.',
|
|
125
|
+
' Or subscribe instead of polling: `GET /lowdefy-docs/events` streams `build`, `client_error`, `server_error` and `restart` events over SSE (MCP clients receive the same as `notifications/message` from logger `lowdefy`).',
|
|
69
126
|
'2. **Verify the page**: `GET /lowdefy-docs/page-config/{pageId}` — or `lowdefy_get_page_config`. The fully built page config, or its structured build errors.',
|
|
70
|
-
|
|
71
|
-
'4. **
|
|
72
|
-
'5. **
|
|
127
|
+
"3. **See it rendered**: `GET /lowdefy-docs/screenshot/{pageId}` — or `lowdefy_screenshot_page`. PNG of the page in headless Chromium; `?viewportWidth=390&colorScheme=dark` (the tool's `width` and `colorScheme`) checks a phone layout in dark mode.",
|
|
128
|
+
'4. **Verify behaviour**: `POST /lowdefy-docs/journey` `{pageId, steps, user, urlQuery, state}` — or `lowdefy_run_journey`. Drive the page headless with declarative steps (`click`, `fill`, `select`, `press`, `back`, `goto`, `email`, `as`, `wait`, `screenshot`, `expect: { state | visible | text | url | title }`) and assert what happens. `user: "none"` injects no caller, so sign-up, sign-in and invitations run through the app\'s own auth. Targets are a blockId, or `{ blockId, row, column, text, nth }` to reach the button in a grid cell, a control by its exact text, or — with `text` alone — a confirm dialog / modal footer button rendered outside every block. A failing step stops the journey and returns `passed: false` with `failure: { index, step, expected, actual, message }` and the final page `state` — data, not an error. A final state over 10000 characters comes back as `stateOmitted` (its size and top-level keys); pass `state: [paths]` for the paths you need, `true` for all of it or `false` for none.',
|
|
129
|
+
'5. **Locate config**: `GET /lowdefy-docs/find/{id}?pageId=...` — or `lowdefy_find_config`. Which yaml file defines a page/block/request id.',
|
|
130
|
+
'6. **Scaffold**: `lowdefy_scaffold_page` creates a canonical new page file (then register it in lowdefy.yaml).',
|
|
73
131
|
'',
|
|
74
132
|
'## Live state and testing',
|
|
75
133
|
'',
|
|
76
134
|
"1. **Inspect live state**: `GET /lowdefy-docs/inspect-state/{pageId}` — or `lowdefy_inspect_state`. The actual state, request results, and event log of a running page. Reads the developer's open browser tab when connected (ask them to interact, then inspect), else runs the page headless.",
|
|
77
135
|
'2. **Evaluate operators**: `POST /lowdefy-docs/eval-operator` `{pageId, expression}` — or `lowdefy_eval_operator`. A REPL for config: run any operator expression against live page state. The operator goes in the `expression` body key (`operator` is accepted as an alias).',
|
|
78
|
-
'3. **Run a request**: `POST /lowdefy-docs/run-request` `{pageId, requestId, payload}` — or `lowdefy_run_request`. Verify the data shape a request returns. Read-only types always run; writes need `cli.agentTools.allowWriteRequests: true` in lowdefy.yaml.',
|
|
79
|
-
'4. **
|
|
80
|
-
'5. **
|
|
81
|
-
|
|
136
|
+
'3. **Run a request**: `POST /lowdefy-docs/run-request` `{pageId, requestId, payload}` — or `lowdefy_run_request`. Verify the data shape a request returns. The page is rebuilt first when it changed since its last build. Read-only types always run; writes need `cli.agentTools.allowWriteRequests: true` in lowdefy.yaml. A response over 40,000 characters is written in full to `responseFile` under `.lowdefy/responses/`; add `saveResponse: true` to always write it there.',
|
|
137
|
+
'4. **Run an endpoint**: `POST /lowdefy-docs/run-endpoint` `{endpointId, payload, user}` — or `lowdefy_run_endpoint`. Run an Api routine headlessly and see what it returns, rejects or throws — a `:reject` comes back as `status: "reject"` with the routine\'s own error, not as a failure. Always needs `cli.agentTools.allowWriteRequests: true` (routines are not classified read-only). Add `system: true` to run a scheduled or detached-only `InternalApi` routine as a system context (no `_user`, auth not checked), the way cron does — not combinable with `user`. A large response is written to `responseFile` the same way as for a request, and `saveResponse: true` always writes it there.',
|
|
138
|
+
'5. **Understand the app**: `GET /lowdefy-docs/app-map` — or `lowdefy_app_map`. Every page, menu, connection, endpoint, agent, and websocket in one call.',
|
|
139
|
+
'6. **Config checkpoints**: `POST /lowdefy-docs/checkpoints` `{label}` / `POST /lowdefy-docs/checkpoints/revert` `{id}` — or `lowdefy_checkpoint` / `lowdefy_revert_checkpoint`. Snapshot config files before risky changes; revert instantly.',
|
|
140
|
+
"7. **State checkpoints**: `POST /lowdefy-docs/state-checkpoints/snapshot` `{pageId, name}` captures live page state + request responses into .lowdefy/state-checkpoints/<name>/ (gitignored — checkpoints contain user/session data); `POST /lowdefy-docs/state-checkpoints/load` `{name, mode}` restores it — 'registry-only' mode returns a ?_checkpoint URL a human can open to test the app in that exact state. `lowdefy_checkpoint_to_mocks` converts one into e2e mocks.yaml fixtures.",
|
|
141
|
+
'8. **Restart the server**: `POST /lowdefy-docs/restart` `{reason}` — or `lowdefy_restart` (through `lowdefy mcp`: `lowdefy_dev_start` with `restart: true`, a full process restart when the hub runs the server). Only when the server seems stuck or build-status looks stale, or after secrets a wrapper (for example infisical) injects have changed. Answers before the restart lands: then call `GET /lowdefy-docs/build-status?wait=true`, which answers once the restarted server is ready. `.env` edits and local plugin code need no restart: `.env` is watched and restarts the server, and a local plugin change rebuilds, restarting the server when the plugin has server-side types.',
|
|
82
142
|
'',
|
|
83
143
|
'## Visual feedback (annotation helper)',
|
|
84
144
|
'',
|
|
@@ -93,19 +153,25 @@ function getOverview() {
|
|
|
93
153
|
'| `GET /lowdefy-docs/plugins` | Installed plugin packages and the types each provides |',
|
|
94
154
|
'| `GET /lowdefy-docs/schema/{kind}/{type}` | JSON schema for a type |',
|
|
95
155
|
'| `GET /lowdefy-docs/examples/{type}` | Example yaml for a block type |',
|
|
96
|
-
'| `GET /lowdefy-docs/content/{slug}` |
|
|
97
|
-
'| `GET /lowdefy-docs/search?q={query}` | Search
|
|
156
|
+
'| `GET /lowdefy-docs/content/{slug}` | A doc page as markdown: core, plugin or module |',
|
|
157
|
+
'| `GET /lowdefy-docs/search?q={query}&source={source}` | Search the docs, ranked by matching words |',
|
|
158
|
+
'| `GET /lowdefy-docs/icons?q={query}` | Search icon names (semantic names first, then Lucide names, then installed icon sets) |',
|
|
98
159
|
'| `GET /lowdefy-docs/plugin-doc/{package}` | Markdown shipped inside a plugin package |',
|
|
99
|
-
'| `GET /lowdefy-docs/build-status` | Current build errors/warnings + recent browser errors |',
|
|
160
|
+
'| `GET /lowdefy-docs/build-status` | Current build errors/warnings, failing pages + recent browser errors |',
|
|
161
|
+
'| `GET /lowdefy-docs/events` | SSE push channel: `restart` on connect, then `build`, `client_error`, `server_error` events as they happen (build events carry `stale`) |',
|
|
100
162
|
'| `GET /lowdefy-docs/page-config/{pageId}` | Fully built page config, or its build errors |',
|
|
101
163
|
'| `GET /lowdefy-docs/screenshot/{pageId}` | PNG screenshot of the rendered page |',
|
|
164
|
+
'| `POST /lowdefy-docs/journey` | Drive a page headless through declarative steps (click, fill, select, press, back, goto, email, as, wait, screenshot, expect) and assert state, visibility, text, url or title; screenshots returned as base64 |',
|
|
165
|
+
'| `POST /lowdefy-docs/mutants` | Config mutants on what journeys exercised `{pages, requests, endpoints, appEvents, operators}`: each with a stable `id`, its anchor and source line (for `lowdefy journeys harden`) |',
|
|
102
166
|
'| `GET /lowdefy-docs/find/{id}?pageId=` | Locate where a page/block/request id is defined |',
|
|
103
167
|
'| `GET /lowdefy-docs/inspect-state/{pageId}?source=` | Live state/requests/eventLog of a running page (tab or headless) |',
|
|
104
168
|
'| `POST /lowdefy-docs/eval-operator` | Evaluate an operator expression against live page state |',
|
|
105
169
|
'| `POST /lowdefy-docs/run-request` | Execute a request with a test payload (read-only unless opted in) |',
|
|
170
|
+
'| `POST /lowdefy-docs/run-endpoint` | Execute an Api endpoint routine with a test payload and caller, or `system: true` for a cron-style system run (needs agent write access; rejects return as data) |',
|
|
106
171
|
'| `GET /lowdefy-docs/app-map` | Whole-app graph: pages, menus, connections, endpoints, agents |',
|
|
107
172
|
'| `GET/POST /lowdefy-docs/checkpoints` + `/revert` | Config-file checkpoints: list, create, revert |',
|
|
108
173
|
'| `GET/POST /lowdefy-docs/state-checkpoints` + `/snapshot`, `/load` | State & data checkpoints: capture/restore live app state |',
|
|
174
|
+
'| `POST /lowdefy-docs/restart` | Restart the dev server process (answers before the restart lands; then `build-status?wait=true` answers once it is back) |',
|
|
109
175
|
'| `POST /lowdefy-feedback` | Annotation helper: enrich + format a feedback batch (overlay → clipboard) |',
|
|
110
176
|
'| `ALL /lowdefy-docs/mcp` | MCP endpoint (streamable HTTP) exposing all of the above as tools |',
|
|
111
177
|
];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
|
|
20
|
+
import setupTestFixtures from './setupTestFixtures.mjs';
|
|
21
|
+
|
|
22
|
+
// lib/build/config.js reads build/config.json from process.cwd() at import
|
|
23
|
+
// time, so the fixture carries the basePath before getOverview is imported.
|
|
24
|
+
const fixtureDir = setupTestFixtures();
|
|
25
|
+
fs.writeFileSync(path.join(fixtureDir, 'build/config.json'), JSON.stringify({ basePath: '/app' }));
|
|
26
|
+
fs.writeFileSync(path.join(fixtureDir, 'build', 'routes.json'), JSON.stringify([]));
|
|
27
|
+
process.chdir(fixtureDir);
|
|
28
|
+
|
|
29
|
+
const { default: getOverview } = await import('./getOverview.js');
|
|
30
|
+
|
|
31
|
+
test('getOverview tells agents the docs routes are served under the app basePath', () => {
|
|
32
|
+
const overview = getOverview();
|
|
33
|
+
|
|
34
|
+
expect(overview).toContain(
|
|
35
|
+
'This app sets `config.basePath: /app`, so every route below is served under it: `GET /app/lowdefy-docs`, `/app/lowdefy-docs/mcp`, `/app/api/...`.'
|
|
36
|
+
);
|
|
37
|
+
});
|
|
@@ -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 reviewPageBuilds from './reviewPageBuilds.js';
|
|
18
|
+
|
|
19
|
+
// The page side of build status. The dev server builds a page when it is first
|
|
20
|
+
// requested, so the config build's status says nothing about page content:
|
|
21
|
+
// this reports the pages whose latest build failed, the pages changed since
|
|
22
|
+
// they were built, and how many pages nothing has built yet. checked is the
|
|
23
|
+
// list of pages build status just built (wait: true).
|
|
24
|
+
async function getPageBuildStatus({ checked } = {}) {
|
|
25
|
+
const { edited, unbuilt, failed } = await reviewPageBuilds();
|
|
26
|
+
const status = {};
|
|
27
|
+
if (checked) {
|
|
28
|
+
status.checked = checked;
|
|
29
|
+
}
|
|
30
|
+
if (failed.length > 0) {
|
|
31
|
+
status.failed = failed;
|
|
32
|
+
}
|
|
33
|
+
if (edited.length > 0) {
|
|
34
|
+
status.changedSinceBuild = edited;
|
|
35
|
+
status.changedSinceBuildNote = checked
|
|
36
|
+
? 'These pages changed on disk after their last build, but the dev server has not rebuilt them: it has not seen the change yet. Call again; if they stay here, the file watcher missed the edit.'
|
|
37
|
+
: 'These pages, or the config build they build against, changed after their last build and have not been rebuilt, so their errors are not known yet. Call with wait: true to build them.';
|
|
38
|
+
}
|
|
39
|
+
status.unbuilt = unbuilt.length;
|
|
40
|
+
if (unbuilt.length > 0) {
|
|
41
|
+
status.unbuiltNote = `${unbuilt.length} page(s) have not been built since the dev server started, so this status does not cover them. lowdefy_check validates every page.`;
|
|
42
|
+
}
|
|
43
|
+
return status;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default getPageBuildStatus;
|
package/lib/docs/getPluginDoc.js
CHANGED
|
@@ -15,40 +15,38 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import fs from 'node:fs';
|
|
18
|
-
import path from 'node:path';
|
|
19
18
|
import { type } from '@lowdefy/helpers';
|
|
20
19
|
|
|
20
|
+
import listPackageDocFiles from './listPackageDocFiles.js';
|
|
21
|
+
import readBuildArtifact from './readBuildArtifact.js';
|
|
22
|
+
import readPluginPackages from './readPluginPackages.js';
|
|
21
23
|
import resolvePluginDir from './resolvePluginDir.js';
|
|
22
24
|
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
+
// The package name comes from the caller, and a name such as "../../x" would
|
|
26
|
+
// resolve outside node_modules, so only the app's own plugins are read.
|
|
27
|
+
function isAppPlugin({ packageName }) {
|
|
28
|
+
const installed = readBuildArtifact({ name: 'installedPluginPackages.json' }) ?? [];
|
|
29
|
+
if (installed.includes(packageName)) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return readPluginPackages().some((plugin) => plugin.package === packageName);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Every markdown file a plugin package ships, joined into one document.
|
|
25
36
|
function getPluginDoc({ packageName }) {
|
|
37
|
+
if (!isAppPlugin({ packageName })) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
26
40
|
const pluginDir = resolvePluginDir({ packageName });
|
|
27
41
|
if (type.isNone(pluginDir)) {
|
|
28
42
|
return null;
|
|
29
43
|
}
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (fs.existsSync(readmePath)) {
|
|
34
|
-
sections.push(fs.readFileSync(readmePath, 'utf8'));
|
|
35
|
-
break;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
for (const docsDir of ['docs', 'dist/docs']) {
|
|
39
|
-
const dirPath = path.join(pluginDir, docsDir);
|
|
40
|
-
if (!fs.existsSync(dirPath)) {
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
for (const fileName of fs.readdirSync(dirPath).sort()) {
|
|
44
|
-
if (fileName.endsWith('.md')) {
|
|
45
|
-
sections.push(fs.readFileSync(path.join(dirPath, fileName), 'utf8'));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (sections.length === 0) {
|
|
44
|
+
const { readme, docs } = listPackageDocFiles({ dir: pluginDir });
|
|
45
|
+
const files = type.isNone(readme) ? docs : [readme, ...docs];
|
|
46
|
+
if (files.length === 0) {
|
|
50
47
|
return null;
|
|
51
48
|
}
|
|
49
|
+
const sections = files.map((filePath) => fs.readFileSync(filePath, 'utf8'));
|
|
52
50
|
return { package: packageName, markdown: sections.join('\n\n---\n\n') };
|
|
53
51
|
}
|
|
54
52
|
|