@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
package/lib/docs/docs.test.mjs
CHANGED
|
@@ -48,6 +48,14 @@ jest.unstable_mockModule('../server/createLowdefyContext.js', () => ({
|
|
|
48
48
|
const mockBuildPageIfNeeded = jest.fn(async () => true);
|
|
49
49
|
jest.unstable_mockModule('../server/jitPageBuilder.js', () => ({
|
|
50
50
|
default: mockBuildPageIfNeeded,
|
|
51
|
+
reviewBuiltPage: async () => 'current',
|
|
52
|
+
syncBuildSignals: () => ({
|
|
53
|
+
registry: JSON.parse(
|
|
54
|
+
fs.readFileSync(path.join(fixtureDir, 'build', 'pageRegistry.json'), 'utf8')
|
|
55
|
+
),
|
|
56
|
+
eventCounter: 0,
|
|
57
|
+
generation: 1,
|
|
58
|
+
}),
|
|
51
59
|
}));
|
|
52
60
|
|
|
53
61
|
const { default: listTypes } = await import('./listTypes.js');
|
|
@@ -61,6 +69,7 @@ const { default: searchDocs } = await import('./searchDocs.js');
|
|
|
61
69
|
const { default: normalizeTypeKind } = await import('./normalizeTypeKind.js');
|
|
62
70
|
const { default: clientErrorStore } = await import('./clientErrorStore.js');
|
|
63
71
|
const { default: getBuildStatus } = await import('./getBuildStatus.js');
|
|
72
|
+
const { default: serverErrorStore } = await import('./serverErrorStore.js');
|
|
64
73
|
const { default: getPageConfig } = await import('./getPageConfig.js');
|
|
65
74
|
const { default: readPageArtifact } = await import('./readPageArtifact.js');
|
|
66
75
|
const { default: findConfig } = await import('./findConfig.js');
|
|
@@ -113,6 +122,24 @@ test('getSchema returns block schema with meta', () => {
|
|
|
113
122
|
expect(result.meta.category).toEqual('display');
|
|
114
123
|
});
|
|
115
124
|
|
|
125
|
+
test('getSchema returns hazards for a block: type-attached first, then framework-level', () => {
|
|
126
|
+
const result = getSchema({ kind: 'blocks', type: 'TestBlock' });
|
|
127
|
+
expect(result.hazards.map((hazard) => hazard.id)).toEqual([
|
|
128
|
+
'test-block-hazard',
|
|
129
|
+
'visible-false-prunes-state',
|
|
130
|
+
]);
|
|
131
|
+
// The plugin's own entry wins a duplicate id.
|
|
132
|
+
expect(result.hazards[1].message).toEqual('Plugin wording for visible: false.');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('getSchema returns hazards for a request, including a meta.hazards entry', () => {
|
|
136
|
+
const result = getSchema({ kind: 'requests', type: 'WriteRequest' });
|
|
137
|
+
expect(result.hazards.map((hazard) => hazard.id)).toEqual([
|
|
138
|
+
'write-request-hazard',
|
|
139
|
+
'state-in-request-properties',
|
|
140
|
+
]);
|
|
141
|
+
});
|
|
142
|
+
|
|
116
143
|
test('getSchema returns connection schema with request list', () => {
|
|
117
144
|
const result = getSchema({ kind: 'connections', type: 'AxiosHttp' });
|
|
118
145
|
expect(result.schema).toBeDefined();
|
|
@@ -131,6 +158,29 @@ test('getSchema returns null for unknown type', () => {
|
|
|
131
158
|
|
|
132
159
|
test('getSchema throws for kinds without schemas', () => {
|
|
133
160
|
expect(() => getSchema({ kind: 'websockets', type: 'X' })).toThrow('No schemas available');
|
|
161
|
+
expect(() => getSchema({ kind: 'tool', type: 'run_journey' })).toThrow(
|
|
162
|
+
'or journey-step for the journey step grammar'
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('getSchema throws when a type kind is given no type', () => {
|
|
167
|
+
expect(() => getSchema({ kind: 'blocks' })).toThrow(
|
|
168
|
+
'Getting a blocks schema requires a "type", the exact type name. Received undefined.'
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('getSchema returns the journey step grammar for kind journey-step', () => {
|
|
173
|
+
const result = getSchema({ kind: 'journey-step' });
|
|
174
|
+
expect(result.kind).toEqual('journey-step');
|
|
175
|
+
expect(result.schema.title).toEqual('Journey step');
|
|
176
|
+
expect(result.schema.oneOf.map((step) => step.required[0])).toContain('wait');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('getSchema returns one journey step schema when type names the step', () => {
|
|
180
|
+
const result = getSchema({ kind: 'journey-step', type: 'wait' });
|
|
181
|
+
expect(result).toMatchObject({ kind: 'journey-step', type: 'wait' });
|
|
182
|
+
expect(result.schema.properties.wait.examples).toContainEqual({ wait: { ms: 500 } });
|
|
183
|
+
expect(getSchema({ kind: 'journey-step', type: 'hover' })).toBeNull();
|
|
134
184
|
});
|
|
135
185
|
|
|
136
186
|
test('getExamples reads example yaml from plugin dist by convention', () => {
|
|
@@ -152,6 +202,13 @@ test('getPluginDoc returns null for packages without docs', () => {
|
|
|
152
202
|
expect(getPluginDoc({ packageName: 'no-such-package' })).toBeNull();
|
|
153
203
|
});
|
|
154
204
|
|
|
205
|
+
test('getPluginDoc reads only the app plugins, never a path out of node_modules', () => {
|
|
206
|
+
fs.writeFileSync(path.join(fixtureDir, 'README.md'), '# Server readme\n');
|
|
207
|
+
expect(getPluginDoc({ packageName: '..' })).toBeNull();
|
|
208
|
+
expect(getPluginDoc({ packageName: 'test-plugin/../..' })).toBeNull();
|
|
209
|
+
fs.rmSync(path.join(fixtureDir, 'README.md'));
|
|
210
|
+
});
|
|
211
|
+
|
|
155
212
|
test('getCoreDoc returns markdown by slug from docs-content', () => {
|
|
156
213
|
const doc = getCoreDoc({ slug: 'operators/_get' });
|
|
157
214
|
expect(doc.title).toEqual('_get');
|
|
@@ -163,6 +220,24 @@ test('getCoreDoc resolves a doc by kind and type name', () => {
|
|
|
163
220
|
expect(doc.slug).toEqual('operators/_get');
|
|
164
221
|
});
|
|
165
222
|
|
|
223
|
+
test('getCoreDoc returns hazards for the requested type when resolved by kind and type', () => {
|
|
224
|
+
const doc = getCoreDoc({ kind: 'operator', type: '_get' });
|
|
225
|
+
expect(doc.hazards.map((hazard) => hazard.id)).toEqual(['get-fixture-hazard']);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('getCoreDoc does not attach hazards when resolved by slug', () => {
|
|
229
|
+
const doc = getCoreDoc({ slug: 'operators/_get' });
|
|
230
|
+
expect(doc.hazards).toBeUndefined();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('getCoreDoc resolves a request doc on its connection page but keeps request hazards', () => {
|
|
234
|
+
// findDoc remaps request → connection to find the page; hazards must come
|
|
235
|
+
// from the request kind the caller asked about.
|
|
236
|
+
const doc = getCoreDoc({ kind: 'request', type: 'MongoDBFind' });
|
|
237
|
+
expect(doc.slug).toEqual('connections/mongodb');
|
|
238
|
+
expect(doc.hazards.map((hazard) => hazard.id)).toEqual(['state-in-request-properties']);
|
|
239
|
+
});
|
|
240
|
+
|
|
166
241
|
test('getCoreDoc returns null for unknown slug', () => {
|
|
167
242
|
expect(getCoreDoc({ slug: 'nope/nope' })).toBeNull();
|
|
168
243
|
});
|
|
@@ -182,6 +257,7 @@ test('getOverview includes counts and route guidance', () => {
|
|
|
182
257
|
expect(overview).toContain('2 block types');
|
|
183
258
|
expect(overview).toContain('/lowdefy-docs/schema/{kind}/{type}');
|
|
184
259
|
expect(overview).toContain('lowdefy_list_types');
|
|
260
|
+
expect(overview).not.toContain('config.basePath');
|
|
185
261
|
});
|
|
186
262
|
|
|
187
263
|
test('normalizeTypeKind maps singular and plural, rejects unknown', () => {
|
|
@@ -200,15 +276,26 @@ test('clientErrorStore caps at 50 entries, evicting the oldest first', () => {
|
|
|
200
276
|
expect(entries[49].index).toEqual(59);
|
|
201
277
|
});
|
|
202
278
|
|
|
203
|
-
test('getBuildStatus returns the build artifact plus reported client errors', () => {
|
|
204
|
-
|
|
279
|
+
test('getBuildStatus returns the build artifact plus reported client and server errors', async () => {
|
|
280
|
+
serverErrorStore.push({ name: 'RequestError', message: 'Bad filter.', source: 'pages/a.yaml:3' });
|
|
281
|
+
const result = await getBuildStatus();
|
|
205
282
|
expect(result.build.status).toEqual('ok');
|
|
206
283
|
expect(result.clientErrors.length).toEqual(50);
|
|
284
|
+
expect(result.serverErrors).toEqual([
|
|
285
|
+
{
|
|
286
|
+
name: 'RequestError',
|
|
287
|
+
message: 'Bad filter.',
|
|
288
|
+
source: 'pages/a.yaml:3',
|
|
289
|
+
buildId: '2026-01-01T00:00:00.000Z',
|
|
290
|
+
},
|
|
291
|
+
]);
|
|
292
|
+
expect(result.earlierErrors).toBeUndefined();
|
|
293
|
+
expect(result.pages.unbuilt).toEqual(expect.any(Number));
|
|
207
294
|
});
|
|
208
295
|
|
|
209
|
-
test('getBuildStatus reports unknown status when buildStatus.json is missing', () => {
|
|
296
|
+
test('getBuildStatus reports unknown status when buildStatus.json is missing', async () => {
|
|
210
297
|
fs.rmSync(path.join(fixtureDir, 'build', 'buildStatus.json'));
|
|
211
|
-
const result = getBuildStatus();
|
|
298
|
+
const result = await getBuildStatus();
|
|
212
299
|
expect(result.build.status).toEqual('unknown');
|
|
213
300
|
expect(result.build.message).toContain('No build status yet');
|
|
214
301
|
});
|
|
@@ -235,7 +322,37 @@ test('findConfig resolves a known pageId to its source file', async () => {
|
|
|
235
322
|
// A page's root block renders with blockId === pageId — open-in-editor and
|
|
236
323
|
// feedback enrichment read matches[0].location.source from this result.
|
|
237
324
|
expect(result.matches.length).toEqual(1);
|
|
238
|
-
expect(result.matches[0].location.source).toContain('pages
|
|
325
|
+
expect(result.matches[0].location.source).toContain(path.join('pages', 'home.yaml'));
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test('findConfig returns hazards per match for the matched node type', async () => {
|
|
329
|
+
const result = await findConfig({ id: 'my_button', pageId: 'home' });
|
|
330
|
+
expect(result.matches[0].hazards.map((hazard) => hazard.id)).toEqual([
|
|
331
|
+
'visible-false-prunes-state',
|
|
332
|
+
]);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('findConfig fires tenant-wall-lookup on a request over a walled connection only', async () => {
|
|
336
|
+
const walled = await findConfig({ id: 'req-tenant', pageId: 'other' });
|
|
337
|
+
expect(walled.matches.length).toEqual(1);
|
|
338
|
+
expect(walled.matches[0].hazards.map((hazard) => hazard.id)).toEqual([
|
|
339
|
+
'write-request-hazard',
|
|
340
|
+
'state-in-request-properties',
|
|
341
|
+
'tenant-wall-lookup',
|
|
342
|
+
]);
|
|
343
|
+
|
|
344
|
+
const shared = await findConfig({ id: 'req-shared', pageId: 'other' });
|
|
345
|
+
expect(shared.matches.length).toEqual(1);
|
|
346
|
+
expect(shared.matches[0].hazards.map((hazard) => hazard.id)).toEqual([
|
|
347
|
+
'write-request-hazard',
|
|
348
|
+
'state-in-request-properties',
|
|
349
|
+
]);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('findConfig resolves a request connection across pages when no pageId is given', async () => {
|
|
353
|
+
const result = await findConfig({ id: 'req-tenant' });
|
|
354
|
+
expect(result.matches.length).toEqual(1);
|
|
355
|
+
expect(result.matches[0].hazards.map((hazard) => hazard.id)).toContain('tenant-wall-lookup');
|
|
239
356
|
});
|
|
240
357
|
|
|
241
358
|
test('findConfig scans keyMap for a matching id when no pageId is given', async () => {
|
|
@@ -243,7 +360,7 @@ test('findConfig scans keyMap for a matching id when no pageId is given', async
|
|
|
243
360
|
const result = await findConfig({ id: 'my_button' });
|
|
244
361
|
expect(result.matches.length).toEqual(2);
|
|
245
362
|
expect(result.matches[0].keyPath).toEqual('root.blocks[2:my_button:Button]');
|
|
246
|
-
expect(result.matches[0].location.source).toContain('pages
|
|
363
|
+
expect(result.matches[0].location.source).toContain(`${path.join('pages', 'home.yaml')}:5`);
|
|
247
364
|
expect(result.matches[1].keyPath).toEqual('root.blocks[0:my_button:Button]');
|
|
248
365
|
expect(result.note).toContain('Pass ?pageId=');
|
|
249
366
|
});
|
|
@@ -255,14 +372,14 @@ test('findConfig with pageId only returns matches on that page', async () => {
|
|
|
255
372
|
const result = await findConfig({ id: 'my_button', pageId: 'other' });
|
|
256
373
|
expect(result.matches.length).toEqual(1);
|
|
257
374
|
expect(result.matches[0].keyPath).toEqual('root.blocks[0:my_button:Button]');
|
|
258
|
-
expect(result.matches[0].location.source).toContain('pages
|
|
375
|
+
expect(result.matches[0].location.source).toContain(`${path.join('pages', 'other.yaml')}:4`);
|
|
259
376
|
});
|
|
260
377
|
|
|
261
378
|
test('findConfig with pageId resolves the same id to each page respectively', async () => {
|
|
262
379
|
const result = await findConfig({ id: 'my_button', pageId: 'home' });
|
|
263
380
|
expect(result.matches.length).toEqual(1);
|
|
264
381
|
expect(result.matches[0].keyPath).toEqual('root.blocks[2:my_button:Button]');
|
|
265
|
-
expect(result.matches[0].location.source).toContain('pages
|
|
382
|
+
expect(result.matches[0].location.source).toContain(`${path.join('pages', 'home.yaml')}:5`);
|
|
266
383
|
});
|
|
267
384
|
|
|
268
385
|
test('findConfig scopes skeleton-built pages via the page key segment', async () => {
|
|
@@ -271,7 +388,7 @@ test('findConfig scopes skeleton-built pages via the page key segment', async ()
|
|
|
271
388
|
const result = await findConfig({ id: 'legal_button', pageId: 'legal' });
|
|
272
389
|
expect(result.matches.length).toEqual(1);
|
|
273
390
|
expect(result.matches[0].keyPath).toEqual('root.pages[2:legal].blocks[0:legal_button:Button]');
|
|
274
|
-
expect(result.matches[0].location.source).toContain('pages
|
|
391
|
+
expect(result.matches[0].location.source).toContain(`${path.join('pages', 'legal.yaml')}:3`);
|
|
275
392
|
});
|
|
276
393
|
|
|
277
394
|
test('findConfig with a skeleton-built pageId does not leak other pages ids', async () => {
|
|
@@ -293,7 +410,7 @@ test('findConfig resolves a runtime list item id to its $ placeholder config id'
|
|
|
293
410
|
const result = await findConfig({ id: 'my_list.0.item_title' });
|
|
294
411
|
expect(result.matches.length).toEqual(1);
|
|
295
412
|
expect(result.matches[0].keyPath).toContain('[0:my_list.$.item_title:Title]');
|
|
296
|
-
expect(result.matches[0].location.source).toContain('pages
|
|
413
|
+
expect(result.matches[0].location.source).toContain(`${path.join('pages', 'home.yaml')}:9`);
|
|
297
414
|
});
|
|
298
415
|
|
|
299
416
|
test('findConfig resolves a runtime list item id within a pageId scope', async () => {
|
|
@@ -400,17 +517,56 @@ test('runRequest returns a structured error instead of throwing when callRequest
|
|
|
400
517
|
expect(result.error).toEqual({ name: 'Error', message: 'boom' });
|
|
401
518
|
});
|
|
402
519
|
|
|
403
|
-
test('runRequest
|
|
520
|
+
test('runRequest writes a response larger than the inline limit to a file', async () => {
|
|
521
|
+
const rows = Array.from({ length: 500 }, (_, index) => ({ _id: index, title: 'x'.repeat(100) }));
|
|
404
522
|
mockCallRequest.mockResolvedValueOnce({
|
|
405
523
|
id: 'req-read',
|
|
406
524
|
success: true,
|
|
407
525
|
type: 'ReadOnlyRequest',
|
|
408
|
-
response:
|
|
526
|
+
response: rows,
|
|
409
527
|
});
|
|
410
528
|
const result = await runRequest({ pageId: 'home', requestId: 'req-read', honoContext: {} });
|
|
411
|
-
expect(result.
|
|
412
|
-
expect(result.
|
|
413
|
-
expect(result.
|
|
529
|
+
expect(result.response).toBeUndefined();
|
|
530
|
+
expect(result.responseItems).toEqual(500);
|
|
531
|
+
expect(path.dirname(result.responseFile)).toEqual(
|
|
532
|
+
path.join(fs.realpathSync(fixtureDir), '.lowdefy', 'responses')
|
|
533
|
+
);
|
|
534
|
+
expect(path.basename(result.responseFile).startsWith('home.req-read-')).toBe(true);
|
|
535
|
+
expect(JSON.parse(fs.readFileSync(result.responseFile, 'utf8'))).toEqual(rows);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test('runRequest builds the page before it reads the request', async () => {
|
|
539
|
+
mockBuildPageIfNeeded.mockClear();
|
|
540
|
+
mockCallRequest.mockClear();
|
|
541
|
+
await runRequest({ pageId: 'home', requestId: 'req-read', honoContext: {} });
|
|
542
|
+
expect(mockBuildPageIfNeeded).toHaveBeenCalledWith(expect.objectContaining({ pageId: 'home' }));
|
|
543
|
+
expect(mockBuildPageIfNeeded.mock.invocationCallOrder[0]).toBeLessThan(
|
|
544
|
+
mockCallRequest.mock.invocationCallOrder[0]
|
|
545
|
+
);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test('runRequest refuses with the build errors when the page fails to build', async () => {
|
|
549
|
+
const error = new Error('Page "home" build failed with 1 error(s).');
|
|
550
|
+
error.buildErrors = [
|
|
551
|
+
{
|
|
552
|
+
name: 'ConfigError',
|
|
553
|
+
message: 'Block type "Buton" was used but is not defined.',
|
|
554
|
+
source: 'pages/home.yaml:4',
|
|
555
|
+
},
|
|
556
|
+
];
|
|
557
|
+
mockBuildPageIfNeeded.mockRejectedValueOnce(error);
|
|
558
|
+
mockCallRequest.mockClear();
|
|
559
|
+
const result = await runRequest({ pageId: 'home', requestId: 'req-read', honoContext: {} });
|
|
560
|
+
expect(result.refused).toBe(true);
|
|
561
|
+
expect(result.reason).toContain('fails to build');
|
|
562
|
+
expect(result.buildErrors).toEqual([
|
|
563
|
+
{
|
|
564
|
+
type: 'ConfigError',
|
|
565
|
+
message: 'Block type "Buton" was used but is not defined.',
|
|
566
|
+
source: 'pages/home.yaml:4',
|
|
567
|
+
},
|
|
568
|
+
]);
|
|
569
|
+
expect(mockCallRequest).not.toHaveBeenCalled();
|
|
414
570
|
});
|
|
415
571
|
|
|
416
572
|
test('getAppMap includes built page detail and a note for unbuilt pages', () => {
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import os from 'os';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
|
|
21
|
+
import { serializer } from '@lowdefy/helpers';
|
|
22
|
+
|
|
23
|
+
// A server directory whose build lists an installed plugin, a local plugin
|
|
24
|
+
// linked from outside node_modules, one of Lowdefy's own plugins and a local
|
|
25
|
+
// module. resolvePluginDir reads process.cwd() at import, so the directory is
|
|
26
|
+
// made and entered before the docs modules are imported.
|
|
27
|
+
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-docs-index-test-'));
|
|
28
|
+
const serverDir = path.join(rootDir, 'server');
|
|
29
|
+
const localPluginDir = path.join(rootDir, 'plugins', 'local-tools');
|
|
30
|
+
const moduleRoot = path.join(rootDir, 'modules', 'contacts');
|
|
31
|
+
|
|
32
|
+
function write(filePath, data) {
|
|
33
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
34
|
+
fs.writeFileSync(filePath, typeof data === 'string' ? data : JSON.stringify(data));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function writeBuild(name, data) {
|
|
38
|
+
const filePath = path.join(serverDir, 'build', name);
|
|
39
|
+
write(filePath, data);
|
|
40
|
+
// A rewrite inside the same millisecond would keep the old mtime.
|
|
41
|
+
const later = new Date(Date.now() + Math.floor(Math.random() * 100000));
|
|
42
|
+
fs.utimesSync(filePath, later, later);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function moduleEntry(overrides = {}) {
|
|
46
|
+
return {
|
|
47
|
+
id: 'contacts',
|
|
48
|
+
source: 'file:../modules/contacts',
|
|
49
|
+
moduleRoot,
|
|
50
|
+
packageRoot: moduleRoot,
|
|
51
|
+
isLocal: true,
|
|
52
|
+
consumerVars: { owner_field: 'created_by' },
|
|
53
|
+
resolvedVarCache: {},
|
|
54
|
+
varDefs: {
|
|
55
|
+
collection: { type: 'string', description: 'Collection of contacts.', default: 'contacts' },
|
|
56
|
+
labels: {
|
|
57
|
+
description: 'Display labels.',
|
|
58
|
+
properties: {
|
|
59
|
+
title: { type: 'string', description: 'List title.', default: 'Contacts' },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
owner_field: { type: 'string', required: true, description: 'Field with the owner id.' },
|
|
63
|
+
sort: { description: 'Sort order.', default: { '_build.env': 'SORT' } },
|
|
64
|
+
template: {
|
|
65
|
+
description: 'Card template.',
|
|
66
|
+
default: { '~deferred': 'contacts:vars.template' },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
manifest: {
|
|
70
|
+
name: 'Contacts',
|
|
71
|
+
description: 'A contact list.',
|
|
72
|
+
exports: {
|
|
73
|
+
pages: [{ id: 'contact-list', description: 'All contacts.' }],
|
|
74
|
+
components: [{ id: 'contact-card', description: 'One contact as a card.' }],
|
|
75
|
+
},
|
|
76
|
+
components: [{ id: 'contact-card', component: { '~deferred': 'contacts:c.0' } }],
|
|
77
|
+
pages: [{ id: 'contacts/contact-list', type: 'Box' }],
|
|
78
|
+
},
|
|
79
|
+
...overrides,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
write(path.join(serverDir, 'package.json'), { name: 'server', version: '1.0.0' });
|
|
84
|
+
writeBuild('plugins/availableTypes.json', {
|
|
85
|
+
actions: {},
|
|
86
|
+
blocks: {
|
|
87
|
+
Button: { package: '@lowdefy/blocks-antd', version: '5.0.0' },
|
|
88
|
+
ButtonPlus: { package: 'fancy-blocks', version: '2.1.0' },
|
|
89
|
+
FancyCard: { package: 'fancy-blocks', version: '2.1.0' },
|
|
90
|
+
PlainCard: { package: 'fancy-blocks', version: '2.1.0' },
|
|
91
|
+
SetState: { package: 'fancy-blocks', version: '2.1.0' },
|
|
92
|
+
},
|
|
93
|
+
operators: {
|
|
94
|
+
client: { _shout: { package: 'local-tools', version: 'workspace:*' } },
|
|
95
|
+
server: { _shout: { package: 'local-tools', version: 'workspace:*' } },
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
writeBuild('customTypesMap.json', {
|
|
99
|
+
operators: { client: { _shout: { package: 'local-tools', version: 'workspace:*' } } },
|
|
100
|
+
});
|
|
101
|
+
writeBuild('installedPluginPackages.json', ['@lowdefy/blocks-antd', 'fancy-blocks', 'local-tools']);
|
|
102
|
+
writeBuild('modules.json', serializer.serialize({ contacts: moduleEntry() }));
|
|
103
|
+
|
|
104
|
+
write(path.join(serverDir, 'node_modules/@lowdefy/blocks-antd/package.json'), {
|
|
105
|
+
name: '@lowdefy/blocks-antd',
|
|
106
|
+
version: '5.0.0',
|
|
107
|
+
});
|
|
108
|
+
write(path.join(serverDir, 'node_modules/@lowdefy/blocks-antd/README.md'), '# Antd blocks\n');
|
|
109
|
+
write(path.join(serverDir, 'node_modules/fancy-blocks/package.json'), {
|
|
110
|
+
name: 'fancy-blocks',
|
|
111
|
+
version: '2.1.0',
|
|
112
|
+
});
|
|
113
|
+
write(path.join(serverDir, 'node_modules/fancy-blocks/README.md'), '# Fancy blocks\n\nCards.\n');
|
|
114
|
+
write(
|
|
115
|
+
path.join(serverDir, 'node_modules/fancy-blocks/dist/docs/FancyCard.md'),
|
|
116
|
+
'# FancyCard block\n\nA card with a gradient border.\n'
|
|
117
|
+
);
|
|
118
|
+
write(path.join(localPluginDir, 'package.json'), { name: 'local-tools', version: '0.3.1' });
|
|
119
|
+
write(path.join(localPluginDir, 'README.md'), '# Local tools\n\nShouting operators.\n');
|
|
120
|
+
write(path.join(localPluginDir, 'docs/_shout.md'), '# The _shout operator\n\nUppercases text.\n');
|
|
121
|
+
// Links out of the package, a link to nowhere and a directory named like a
|
|
122
|
+
// doc are never indexed.
|
|
123
|
+
write(path.join(rootDir, 'outside.md'), '# Outside\n\nNot part of the plugin.\n');
|
|
124
|
+
fs.symlinkSync(path.join(rootDir, 'outside.md'), path.join(localPluginDir, 'docs/leak.md'));
|
|
125
|
+
fs.symlinkSync(path.join(rootDir, 'missing.md'), path.join(localPluginDir, 'docs/broken.md'));
|
|
126
|
+
fs.mkdirSync(path.join(localPluginDir, 'docs/folder.md'));
|
|
127
|
+
fs.symlinkSync(localPluginDir, path.join(serverDir, 'node_modules/local-tools'));
|
|
128
|
+
write(path.join(moduleRoot, 'README.md'), '# Contacts module\n\nA list of people you work with.\n');
|
|
129
|
+
write(path.join(moduleRoot, 'docs/setup.md'), '# Setting up contacts\n\nCreate the collection.\n');
|
|
130
|
+
|
|
131
|
+
process.chdir(serverDir);
|
|
132
|
+
|
|
133
|
+
const { default: getDocsIndex } = await import('./getDocsIndex.js');
|
|
134
|
+
const { default: getDoc } = await import('./getDoc.js');
|
|
135
|
+
const { default: listPlugins } = await import('./listPlugins.js');
|
|
136
|
+
const { default: listTypes } = await import('./listTypes.js');
|
|
137
|
+
const { default: searchDocs } = await import('./searchDocs.js');
|
|
138
|
+
|
|
139
|
+
function appEntries() {
|
|
140
|
+
return getDocsIndex()
|
|
141
|
+
.entries.filter((entry) => entry.source !== 'core')
|
|
142
|
+
.map(({ slug, source, package: packageName, version, title }) => ({
|
|
143
|
+
slug,
|
|
144
|
+
source,
|
|
145
|
+
package: packageName,
|
|
146
|
+
version,
|
|
147
|
+
title,
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
afterAll(() => {
|
|
152
|
+
process.chdir(os.tmpdir());
|
|
153
|
+
fs.rmSync(rootDir, { recursive: true, force: true });
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('docs index lists plugin and module docs, leaving out Lowdefy plugins', () => {
|
|
157
|
+
expect(appEntries()).toEqual([
|
|
158
|
+
{
|
|
159
|
+
slug: 'plugins/fancy-blocks',
|
|
160
|
+
source: 'plugin',
|
|
161
|
+
package: 'fancy-blocks',
|
|
162
|
+
version: '2.1.0',
|
|
163
|
+
title: 'fancy-blocks',
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
slug: 'plugins/fancy-blocks/FancyCard',
|
|
167
|
+
source: 'plugin',
|
|
168
|
+
package: 'fancy-blocks',
|
|
169
|
+
version: '2.1.0',
|
|
170
|
+
title: 'FancyCard block',
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
slug: 'plugins/local-tools',
|
|
174
|
+
source: 'local-plugin',
|
|
175
|
+
package: 'local-tools',
|
|
176
|
+
version: '0.3.1',
|
|
177
|
+
title: 'local-tools',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
slug: 'plugins/local-tools/_shout',
|
|
181
|
+
source: 'local-plugin',
|
|
182
|
+
package: 'local-tools',
|
|
183
|
+
version: '0.3.1',
|
|
184
|
+
title: 'The _shout operator',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
slug: 'modules/contacts',
|
|
188
|
+
source: 'module',
|
|
189
|
+
package: 'file:../modules/contacts',
|
|
190
|
+
version: 'local',
|
|
191
|
+
title: 'Contacts module',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
slug: 'modules/contacts/setup',
|
|
195
|
+
source: 'module',
|
|
196
|
+
package: 'file:../modules/contacts',
|
|
197
|
+
version: 'local',
|
|
198
|
+
title: 'Setting up contacts',
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
slug: 'modules/contacts/manifest',
|
|
202
|
+
source: 'module',
|
|
203
|
+
package: 'file:../modules/contacts',
|
|
204
|
+
version: 'local',
|
|
205
|
+
title: 'contacts module: components, exports and vars',
|
|
206
|
+
},
|
|
207
|
+
]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('a plugin type points at its own doc, else at the package README', () => {
|
|
211
|
+
expect(getDoc({ kind: 'block', type: 'FancyCard' }).slug).toEqual(
|
|
212
|
+
'plugins/fancy-blocks/FancyCard'
|
|
213
|
+
);
|
|
214
|
+
expect(getDoc({ kind: 'block', type: 'PlainCard' }).slug).toEqual('plugins/fancy-blocks');
|
|
215
|
+
expect(getDoc({ type: '_shout' }).slug).toEqual('plugins/local-tools/_shout');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('a plugin type named like a core type gets its plugin doc, not the core prefix match', () => {
|
|
219
|
+
expect(getDoc({ kind: 'block', type: 'ButtonPlus' }).slug).toEqual('plugins/fancy-blocks');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('a plugin type named like a core type of another kind leaves the core page to the core type', () => {
|
|
223
|
+
expect(getDoc({ type: 'SetState' }).slug).toEqual('actions/setstate');
|
|
224
|
+
expect(getDoc({ kind: 'action', type: 'SetState' }).slug).toEqual('actions/setstate');
|
|
225
|
+
expect(getDoc({ kind: 'block', type: 'SetState' }).slug).toEqual('plugins/fancy-blocks');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('docs that link outside the package, link nowhere or are directories are left out', () => {
|
|
229
|
+
const slugs = getDocsIndex().entries.map((entry) => entry.slug);
|
|
230
|
+
expect(slugs).toContain('plugins/local-tools/_shout');
|
|
231
|
+
expect(slugs).not.toContain('plugins/local-tools/leak');
|
|
232
|
+
expect(slugs).not.toContain('plugins/local-tools/broken');
|
|
233
|
+
expect(slugs).not.toContain('plugins/local-tools/folder');
|
|
234
|
+
expect(getDoc({ slug: 'plugins/local-tools/leak' })).toBeNull();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('getDoc returns plugin and module docs by slug with their source', () => {
|
|
238
|
+
const readme = getDoc({ slug: 'modules/contacts' });
|
|
239
|
+
expect(readme).toEqual(
|
|
240
|
+
expect.objectContaining({
|
|
241
|
+
slug: 'modules/contacts',
|
|
242
|
+
source: 'module',
|
|
243
|
+
package: 'file:../modules/contacts',
|
|
244
|
+
version: 'local',
|
|
245
|
+
})
|
|
246
|
+
);
|
|
247
|
+
expect(readme.markdown).toContain('A list of people you work with.');
|
|
248
|
+
expect(getDoc({ slug: 'plugins/local-tools' }).markdown).toContain('Shouting operators.');
|
|
249
|
+
expect(getDoc({ slug: 'plugins/@lowdefy/blocks-antd' })).toBeNull();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('getDoc still returns core docs first', () => {
|
|
253
|
+
expect(getDoc({ slug: 'actions/setstate' }).slug).toEqual('actions/setstate');
|
|
254
|
+
expect(getDoc({ kind: 'action', type: 'SetState' }).slug).toEqual('actions/setstate');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('module manifest page lists vars, components and exports', () => {
|
|
258
|
+
const { markdown } = getDoc({ slug: 'modules/contacts/manifest' });
|
|
259
|
+
expect(markdown).toContain('A contact list.');
|
|
260
|
+
expect(markdown).toContain(
|
|
261
|
+
'- `collection` (string, default `"contacts"`): Collection of contacts.'
|
|
262
|
+
);
|
|
263
|
+
expect(markdown).toContain(' - `labels.title` (string, default `"Contacts"`): List title.');
|
|
264
|
+
expect(markdown).toContain('- `owner_field` (string, required): Field with the owner id.');
|
|
265
|
+
expect(markdown).toContain('- `sort` (default computed): Sort order.');
|
|
266
|
+
expect(markdown).toContain('- `template` (default computed): Card template.');
|
|
267
|
+
expect(markdown).toContain('- `contact-card`: One contact as a card.');
|
|
268
|
+
expect(markdown).toContain('- `contact-list`: All contacts.');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('searchDocs finds module and local plugin docs by source', () => {
|
|
272
|
+
expect(searchDocs({ query: 'owner_field', source: 'module' }).map((hit) => hit.slug)).toEqual([
|
|
273
|
+
'modules/contacts/manifest',
|
|
274
|
+
]);
|
|
275
|
+
const [hit] = searchDocs({ query: 'shouting operators', source: 'local-plugin' });
|
|
276
|
+
expect(hit).toEqual(
|
|
277
|
+
expect.objectContaining({
|
|
278
|
+
slug: 'plugins/local-tools',
|
|
279
|
+
source: 'local-plugin',
|
|
280
|
+
package: 'local-tools',
|
|
281
|
+
version: '0.3.1',
|
|
282
|
+
})
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test('listTypes and listPlugins name plugin doc slugs', () => {
|
|
287
|
+
const fancy = listTypes({ kind: 'blocks' }).find((item) => item.type === 'FancyCard');
|
|
288
|
+
expect(fancy.docSlug).toEqual('plugins/fancy-blocks/FancyCard');
|
|
289
|
+
const plugins = listPlugins();
|
|
290
|
+
expect(plugins.find((plugin) => plugin.package === 'local-tools').docSlug).toEqual(
|
|
291
|
+
'plugins/local-tools'
|
|
292
|
+
);
|
|
293
|
+
expect(plugins.find((plugin) => plugin.package === '@lowdefy/blocks-antd').docSlug).toBe(
|
|
294
|
+
undefined
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('a config-only edit leaves the docs index alone', () => {
|
|
299
|
+
const before = getDocsIndex();
|
|
300
|
+
writeBuild(
|
|
301
|
+
'modules.json',
|
|
302
|
+
serializer.serialize({
|
|
303
|
+
contacts: moduleEntry({
|
|
304
|
+
consumerVars: { owner_field: 'owner' },
|
|
305
|
+
resolvedVarCache: { 'labels.title': 'People' },
|
|
306
|
+
}),
|
|
307
|
+
})
|
|
308
|
+
);
|
|
309
|
+
writeBuild('pages/home.json', { id: 'home', type: 'Box' });
|
|
310
|
+
expect(getDocsIndex()).toBe(before);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test('a module change rebuilds the docs index', () => {
|
|
314
|
+
const before = getDocsIndex();
|
|
315
|
+
const changed = moduleEntry();
|
|
316
|
+
changed.varDefs.collection.description = 'Where contacts are stored.';
|
|
317
|
+
writeBuild('modules.json', serializer.serialize({ contacts: changed }));
|
|
318
|
+
const after = getDocsIndex();
|
|
319
|
+
expect(after).not.toBe(before);
|
|
320
|
+
expect(getDoc({ slug: 'modules/contacts/manifest' }).markdown).toContain(
|
|
321
|
+
'Where contacts are stored.'
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('a plugin change rebuilds the docs index', () => {
|
|
326
|
+
const before = getDocsIndex();
|
|
327
|
+
writeBuild('installedPluginPackages.json', [
|
|
328
|
+
'@lowdefy/blocks-antd',
|
|
329
|
+
'fancy-blocks',
|
|
330
|
+
'local-tools',
|
|
331
|
+
'x',
|
|
332
|
+
]);
|
|
333
|
+
expect(getDocsIndex()).not.toBe(before);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test('a doc file added to a local plugin or module rebuilds the docs index', () => {
|
|
337
|
+
const before = getDocsIndex();
|
|
338
|
+
write(path.join(localPluginDir, 'docs/whisper.md'), '# Whispering\n\nLowercases text.\n');
|
|
339
|
+
const afterPlugin = getDocsIndex();
|
|
340
|
+
expect(afterPlugin).not.toBe(before);
|
|
341
|
+
expect(afterPlugin.entries.map((entry) => entry.slug)).toContain('plugins/local-tools/whisper');
|
|
342
|
+
write(path.join(moduleRoot, 'docs/import.md'), '# Importing contacts\n');
|
|
343
|
+
expect(getDocsIndex().entries.map((entry) => entry.slug)).toContain('modules/contacts/import');
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test('a malformed module manifest names the bad parts on its page and breaks no docs call', () => {
|
|
347
|
+
const broken = moduleEntry({
|
|
348
|
+
id: 'broken',
|
|
349
|
+
varDefs: { good: { type: 'string' }, bad: null, nested: { properties: { inner: 'x' } } },
|
|
350
|
+
manifest: {
|
|
351
|
+
components: { card: {} },
|
|
352
|
+
exports: { pages: ['home', null, { id: 'list' }], api: 'all' },
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
writeBuild('modules.json', serializer.serialize({ contacts: moduleEntry(), broken }));
|
|
356
|
+
const { markdown } = getDoc({ slug: 'modules/broken/manifest' });
|
|
357
|
+
expect(markdown).toContain('- `good` (string)');
|
|
358
|
+
expect(markdown).toContain(
|
|
359
|
+
'- `bad`: not valid in module.lowdefy.yaml, expected an object. Received null.'
|
|
360
|
+
);
|
|
361
|
+
expect(markdown).toContain(
|
|
362
|
+
' - `nested.inner`: not valid in module.lowdefy.yaml, expected an object. Received "x".'
|
|
363
|
+
);
|
|
364
|
+
expect(markdown).toContain(
|
|
365
|
+
'- `components`: not valid in module.lowdefy.yaml, expected a list. Received {"card":{}}.'
|
|
366
|
+
);
|
|
367
|
+
expect(markdown).toContain(
|
|
368
|
+
'- `exports.pages.0`: not valid in module.lowdefy.yaml, expected an object with a string id. Received "home".'
|
|
369
|
+
);
|
|
370
|
+
expect(markdown).toContain('- `list`');
|
|
371
|
+
expect(markdown).toContain(
|
|
372
|
+
'- `exports.api`: not valid in module.lowdefy.yaml, expected a list. Received "all".'
|
|
373
|
+
);
|
|
374
|
+
expect(searchDocs({ query: 'not valid', source: 'module' }).map((hit) => hit.slug)).toEqual([
|
|
375
|
+
'modules/broken/manifest',
|
|
376
|
+
]);
|
|
377
|
+
});
|