@lowdefy/server-dev 6.1.0 → 7.1.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 +78 -0
- package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
- package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +727 -0
- package/lib/docs/dataSets/describeDataSetResult.js +25 -0
- package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
- package/lib/docs/dataSets/describeTargetKey.js +29 -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/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 +52 -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 +133 -0
- package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +493 -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 +83 -0
- package/lib/docs/dataSets/readDataSet.test.mjs +251 -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/sessionDatabaseName.js +32 -0
- package/lib/docs/dataSets/sweepOrphanDatabases.js +36 -0
- package/lib/docs/dataSets/test/app/lowdefy.yaml +225 -0
- package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
- package/lib/docs/dataSets/test/app/tests/data/shop.yaml +12 -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 +81 -7
- package/lib/docs/getBuildStatus.test.mjs +229 -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 +92 -0
- package/lib/server/applyDataSetRedirect.test.mjs +446 -0
- package/lib/server/applyDataSetRedirectTenantReach.test.mjs +207 -0
- package/lib/server/applyDataSetRedirectWalled.test.mjs +134 -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 +331 -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 +115 -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 +25 -3
- package/manager/processes/initialBuild.mjs +17 -1
- package/manager/processes/initialBuild.test.mjs +104 -0
- package/manager/processes/lowdefyBuild.mjs +31 -7
- package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +72 -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
|
@@ -25,19 +25,42 @@ import { operatorsServer } from '@lowdefy/operators-js';
|
|
|
25
25
|
const secret = 'planted-dev-secret';
|
|
26
26
|
process.env.LOWDEFY_SECRET_TEST = secret;
|
|
27
27
|
|
|
28
|
-
jest.unstable_mockModule('@lowdefy/api', () => ({
|
|
28
|
+
jest.unstable_mockModule('@lowdefy/api', () => ({
|
|
29
|
+
createApiContext: jest.fn(),
|
|
30
|
+
createRequestSignal: jest.fn(({ clientSignal, timeoutSignal }) =>
|
|
31
|
+
timeoutSignal ? AbortSignal.any([clientSignal, timeoutSignal]) : clientSignal
|
|
32
|
+
),
|
|
33
|
+
// Returns what the server passes, so the test reads the auth-hook system context inputs.
|
|
34
|
+
createSystemContext: jest.fn((options) => options),
|
|
35
|
+
ensureMcpOauthResource: jest.fn(async () => {}),
|
|
36
|
+
normalizeInjectedCaller: jest.fn((user) => user),
|
|
37
|
+
resolveAuthentication: jest.fn(async () => {}),
|
|
38
|
+
resolvePinnedOrganization: jest.fn(async () => {}),
|
|
39
|
+
resolveTenantPreflight: jest.fn(async () => {}),
|
|
40
|
+
}));
|
|
29
41
|
jest.unstable_mockModule('../build/appMeta.js', () => ({ default: {} }));
|
|
30
42
|
jest.unstable_mockModule('../build/config.js', () => ({ default: {} }));
|
|
43
|
+
jest.unstable_mockModule('../build/auth.js', () => ({ default: {} }));
|
|
31
44
|
jest.unstable_mockModule('../build/i18n.js', () => ({ default: {} }));
|
|
32
45
|
jest.unstable_mockModule('./log/createHandleError.js', () => ({
|
|
33
46
|
default: jest.fn(() => jest.fn()),
|
|
34
47
|
}));
|
|
35
48
|
jest.unstable_mockModule('./log/createLogger.js', () => ({
|
|
36
|
-
default: jest.fn(() => ({
|
|
49
|
+
default: jest.fn(() => ({
|
|
50
|
+
debug: jest.fn(),
|
|
51
|
+
info: jest.fn(),
|
|
52
|
+
warn: jest.fn(),
|
|
53
|
+
error: jest.fn(),
|
|
54
|
+
})),
|
|
37
55
|
}));
|
|
38
56
|
jest.unstable_mockModule('./fileCache.js', () => ({ default: {} }));
|
|
39
|
-
jest.unstable_mockModule('./auth/
|
|
40
|
-
jest.unstable_mockModule('./auth/
|
|
57
|
+
jest.unstable_mockModule('./auth/getAuth.js', () => ({ default: jest.fn(() => null) }));
|
|
58
|
+
jest.unstable_mockModule('./auth/getHeadlessUser.js', () => ({
|
|
59
|
+
default: jest.fn(() => null),
|
|
60
|
+
}));
|
|
61
|
+
jest.unstable_mockModule('./auth/getMockUser.js', () => ({ default: jest.fn(() => undefined) }));
|
|
62
|
+
jest.unstable_mockModule('./auth/getStrategies.js', () => ({ default: jest.fn(() => null) }));
|
|
63
|
+
jest.unstable_mockModule('./loadDynamicJsMap.js', () => ({ default: jest.fn(() => ({})) }));
|
|
41
64
|
jest.unstable_mockModule('./log/logRequest.js', () => ({ default: jest.fn() }));
|
|
42
65
|
|
|
43
66
|
// createLowdefyContext statically imports the app build's plugin artifacts,
|
|
@@ -51,10 +74,12 @@ const fixtures = [
|
|
|
51
74
|
{ file: 'connections.js', content: 'export default {};\n' },
|
|
52
75
|
{
|
|
53
76
|
file: 'notifications.js',
|
|
54
|
-
content:
|
|
77
|
+
content:
|
|
78
|
+
'export default {};\nexport const interpolateProperties = () => {};\nexport const renderEmail = () => {};\n',
|
|
55
79
|
},
|
|
56
80
|
{ file: path.join('operators', 'server.js'), content: 'export default {};\n' },
|
|
57
81
|
{ file: path.join('operators', 'serverJsMap.js'), content: 'export default {};\n' },
|
|
82
|
+
{ file: 'steps.js', content: 'export default {};\n' },
|
|
58
83
|
{ file: 'websockets.js', content: 'export default {};\n' },
|
|
59
84
|
].map(({ file, content }) => ({ absolutePath: path.join(pluginsDir, file), content }));
|
|
60
85
|
|
|
@@ -69,9 +94,14 @@ fixtures.forEach(({ absolutePath, content }) => {
|
|
|
69
94
|
|
|
70
95
|
// A checkout that has built an app already holds real artifacts here, which import plugins this
|
|
71
96
|
// package does not depend on, so the stubs are also mocked over whatever is on disk.
|
|
97
|
+
// The connection plugin map carries each type's meta.dataSet, which the data session gate reads.
|
|
98
|
+
const connectionPlugins = {
|
|
99
|
+
MongoDBCollection: { meta: { tenant: true, dataSet: 'redirect' } },
|
|
100
|
+
Knex: {},
|
|
101
|
+
};
|
|
72
102
|
fixtures.forEach(({ absolutePath }) => {
|
|
73
103
|
jest.unstable_mockModule(absolutePath, () => ({
|
|
74
|
-
default: {},
|
|
104
|
+
default: absolutePath.endsWith(`${path.sep}connections.js`) ? connectionPlugins : {},
|
|
75
105
|
interpolateProperties: () => {},
|
|
76
106
|
renderEmail: () => {},
|
|
77
107
|
}));
|
|
@@ -82,13 +112,24 @@ afterAll(() => {
|
|
|
82
112
|
});
|
|
83
113
|
|
|
84
114
|
const { default: createLowdefyContext } = await import('./createLowdefyContext.js');
|
|
115
|
+
const { default: createSystemContext } = await import('./auth/createSystemContext.js');
|
|
116
|
+
const { journeyActorToken } = await import('./auth/journeyActor.js');
|
|
117
|
+
const { createApiContext } = await import('@lowdefy/api');
|
|
118
|
+
const { openMutantRun } = await import('./mutants/mutantRuns.js');
|
|
85
119
|
|
|
86
|
-
function createHonoContext({
|
|
120
|
+
function createHonoContext({
|
|
121
|
+
path: reqPath = '/api/request/foo',
|
|
122
|
+
requestTimeoutSignal,
|
|
123
|
+
headers = {},
|
|
124
|
+
} = {}) {
|
|
125
|
+
const variables = { requestTimeoutSignal };
|
|
87
126
|
return {
|
|
127
|
+
get: (key) => variables[key],
|
|
88
128
|
req: {
|
|
89
|
-
header: (name) => (name ?
|
|
129
|
+
header: (name) => (name ? headers[name] : headers),
|
|
90
130
|
path: reqPath,
|
|
91
131
|
method: 'POST',
|
|
132
|
+
raw: { headers: new Headers(), signal: new AbortController().signal },
|
|
92
133
|
url: `http://localhost${reqPath}`,
|
|
93
134
|
},
|
|
94
135
|
};
|
|
@@ -99,6 +140,23 @@ test('createLowdefyContext sets mode to dev', async () => {
|
|
|
99
140
|
expect(context.mode).toEqual('dev');
|
|
100
141
|
});
|
|
101
142
|
|
|
143
|
+
test('createLowdefyContext cancels the request work with the incoming request signal', async () => {
|
|
144
|
+
const c = createHonoContext();
|
|
145
|
+
const context = await createLowdefyContext({ c });
|
|
146
|
+
expect(context.signal).toBe(c.req.raw.signal);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('createLowdefyContext cancels the request work when the request timeout answers first', async () => {
|
|
150
|
+
const timeout = new AbortController();
|
|
151
|
+
const c = createHonoContext({ requestTimeoutSignal: timeout.signal });
|
|
152
|
+
const context = await createLowdefyContext({ c });
|
|
153
|
+
expect(context.signal.aborted).toBe(false);
|
|
154
|
+
const reason = new DOMException('The request timeout of 20ms was exceeded.', 'TimeoutError');
|
|
155
|
+
timeout.abort(reason);
|
|
156
|
+
expect(context.signal.aborted).toBe(true);
|
|
157
|
+
expect(context.signal.reason).toBe(reason);
|
|
158
|
+
});
|
|
159
|
+
|
|
102
160
|
test('createLowdefyContext scrubSecrets redacts a planted secret', async () => {
|
|
103
161
|
const context = await createLowdefyContext({ c: createHonoContext() });
|
|
104
162
|
expect(context.scrubSecrets(`token ${secret} end`)).toEqual('token [REDACTED] end');
|
|
@@ -115,3 +173,268 @@ test('_error on a dev context reads a caught message with a planted secret scrub
|
|
|
115
173
|
'Connect failed with [REDACTED].'
|
|
116
174
|
);
|
|
117
175
|
});
|
|
176
|
+
|
|
177
|
+
test('the auth-hook system context is built with mode dev and a scrubSecrets that redacts', () => {
|
|
178
|
+
const context = createSystemContext({ auth: null });
|
|
179
|
+
expect(context.mode).toEqual('dev');
|
|
180
|
+
expect(context.scrubSecrets(`token ${secret} end`)).toEqual('token [REDACTED] end');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('createLowdefyContext forwards the verified loopback journey cookies on loopbackHeaders', async () => {
|
|
184
|
+
const context = await createLowdefyContext({
|
|
185
|
+
c: createHonoContext({
|
|
186
|
+
headers: {
|
|
187
|
+
cookie: `session=abc; lowdefy_journey_actor=${journeyActorToken}.203.0.113.7; lowdefy_journey_mutant=${journeyActorToken}.run1`,
|
|
188
|
+
},
|
|
189
|
+
}),
|
|
190
|
+
});
|
|
191
|
+
expect(context.loopbackHeaders).toEqual({
|
|
192
|
+
cookie: `lowdefy_journey_mutant=${journeyActorToken}.run1`,
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('createLowdefyContext forwards no Cookie header when the request carries no journey cookie', async () => {
|
|
197
|
+
const context = await createLowdefyContext({ c: createHonoContext() });
|
|
198
|
+
expect(context.loopbackHeaders).toEqual({});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('createLowdefyContext serves the mutated artifact to a request whose cookie names an open mutant run', async () => {
|
|
202
|
+
const artifact = { id: 'page:form', blocks: [] };
|
|
203
|
+
createApiContext.mockImplementationOnce((context) => {
|
|
204
|
+
context.readConfigFile = async () => artifact;
|
|
205
|
+
});
|
|
206
|
+
const opened = openMutantRun({
|
|
207
|
+
mutant: {
|
|
208
|
+
buildId: 'b',
|
|
209
|
+
artifact: 'pages/form.json',
|
|
210
|
+
key: 'missing',
|
|
211
|
+
arg: null,
|
|
212
|
+
operator: 'drop-block',
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
const context = await createLowdefyContext({
|
|
216
|
+
c: createHonoContext({
|
|
217
|
+
headers: { cookie: `lowdefy_journey_mutant=${journeyActorToken}.${opened.cookiePayload}` },
|
|
218
|
+
}),
|
|
219
|
+
});
|
|
220
|
+
await context.readConfigFile('pages/form.json');
|
|
221
|
+
expect(opened.run.misses).toEqual([{ reason: 'key not found', path: 'pages/form.json' }]);
|
|
222
|
+
expect(context.loopbackHeaders.cookie).toEqual(
|
|
223
|
+
`lowdefy_journey_mutant=${journeyActorToken}.${opened.cookiePayload}`
|
|
224
|
+
);
|
|
225
|
+
opened.close();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('createLowdefyContext leaves readConfigFile alone without a mutant cookie', async () => {
|
|
229
|
+
const readConfigFile = async () => ({});
|
|
230
|
+
createApiContext.mockImplementationOnce((context) => {
|
|
231
|
+
context.readConfigFile = readConfigFile;
|
|
232
|
+
});
|
|
233
|
+
const context = await createLowdefyContext({ c: createHonoContext() });
|
|
234
|
+
expect(context.readConfigFile).toBe(readConfigFile);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const { default: dataSessionRegistry } = await import('../docs/dataSets/dataSessionRegistry.js');
|
|
238
|
+
const { default: getAuth } = await import('./auth/getAuth.js');
|
|
239
|
+
const { default: getHeadlessUser } = await import('./auth/getHeadlessUser.js');
|
|
240
|
+
const { default: getMockUser } = await import('./auth/getMockUser.js');
|
|
241
|
+
const { resolveAuthentication, resolveTenantPreflight } = await import('@lowdefy/api');
|
|
242
|
+
|
|
243
|
+
describe('data sessions', () => {
|
|
244
|
+
const realArtifact = {
|
|
245
|
+
type: 'MongoDBCollection',
|
|
246
|
+
properties: { databaseUri: { _secret: 'MONGODB_URI' }, collection: 'tickets' },
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
function registerSession(state = 'open') {
|
|
250
|
+
const session = {
|
|
251
|
+
id: 'session1',
|
|
252
|
+
name: 'staging-sample',
|
|
253
|
+
state,
|
|
254
|
+
databaseUri: 'mongodb://memory/',
|
|
255
|
+
prefix: 'ld_aaaaaaaaaaaa',
|
|
256
|
+
databaseName: 'ld_aaaaaaaaaaaa_0',
|
|
257
|
+
databases: new Map([[JSON.stringify(['secret:MONGODB_URI', null]), 'ld_aaaaaaaaaaaa_0']]),
|
|
258
|
+
databaseCount: 1,
|
|
259
|
+
work: new Set(),
|
|
260
|
+
};
|
|
261
|
+
dataSessionRegistry.set(session.id, session);
|
|
262
|
+
return session;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function mockReadConfigFile() {
|
|
266
|
+
const readConfigFile = jest.fn(async () => realArtifact);
|
|
267
|
+
createApiContext.mockImplementationOnce((context) => {
|
|
268
|
+
context.readConfigFile = readConfigFile;
|
|
269
|
+
});
|
|
270
|
+
return readConfigFile;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
afterEach(() => {
|
|
274
|
+
dataSessionRegistry.clear();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('a verified data cookie redirects connections, tracks background work, forwards the cookie and skips the tenant preflight', async () => {
|
|
278
|
+
const session = registerSession();
|
|
279
|
+
mockReadConfigFile();
|
|
280
|
+
getHeadlessUser.mockReturnValueOnce({ user: { id: 'u_1', roles: ['admin'] }, explicit: true });
|
|
281
|
+
const cookie = `lowdefy_journey_data=${journeyActorToken}.session1`;
|
|
282
|
+
const context = await createLowdefyContext({ c: createHonoContext({ headers: { cookie } }) });
|
|
283
|
+
expect((await context.readConfigFile('connections/tickets.json')).properties).toEqual({
|
|
284
|
+
databaseUri: 'mongodb://memory/',
|
|
285
|
+
databaseName: 'ld_aaaaaaaaaaaa_0',
|
|
286
|
+
collection: 'tickets',
|
|
287
|
+
});
|
|
288
|
+
expect(context.dataSet).toEqual('staging-sample');
|
|
289
|
+
expect(context.loopbackHeaders).toEqual({ cookie });
|
|
290
|
+
expect(resolveTenantPreflight).not.toHaveBeenCalled();
|
|
291
|
+
let release;
|
|
292
|
+
const work = new Promise((resolve) => {
|
|
293
|
+
release = resolve;
|
|
294
|
+
});
|
|
295
|
+
context.waitUntil(work);
|
|
296
|
+
expect(session.work.has(work)).toBe(true);
|
|
297
|
+
release();
|
|
298
|
+
await work;
|
|
299
|
+
await Promise.resolve();
|
|
300
|
+
expect(session.work.size).toBe(0);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('a data-session request refuses a connection whose type declares no dataSet, naming it', async () => {
|
|
304
|
+
registerSession();
|
|
305
|
+
createApiContext.mockImplementationOnce((context) => {
|
|
306
|
+
context.readConfigFile = async () => ({
|
|
307
|
+
connectionId: 'warehouse',
|
|
308
|
+
type: 'Knex',
|
|
309
|
+
properties: { client: 'pg', connection: 'postgres://real/' },
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
const context = await createLowdefyContext({
|
|
313
|
+
c: createHonoContext({
|
|
314
|
+
headers: { cookie: `lowdefy_journey_data=${journeyActorToken}.session1` },
|
|
315
|
+
}),
|
|
316
|
+
});
|
|
317
|
+
await expect(context.readConfigFile('connections/warehouse.json')).rejects.toThrow(
|
|
318
|
+
'Connection "warehouse" (type Knex) cannot run under data set "staging-sample": its type does not say how to redirect it.'
|
|
319
|
+
);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test('a data session passed by a dev tool call redirects connections and rides the detached hop as a data cookie', async () => {
|
|
323
|
+
const session = registerSession();
|
|
324
|
+
mockReadConfigFile();
|
|
325
|
+
const context = await createLowdefyContext({ c: createHonoContext(), dataSession: session });
|
|
326
|
+
expect((await context.readConfigFile('connections/tickets.json')).properties).toEqual({
|
|
327
|
+
databaseUri: 'mongodb://memory/',
|
|
328
|
+
databaseName: 'ld_aaaaaaaaaaaa_0',
|
|
329
|
+
collection: 'tickets',
|
|
330
|
+
});
|
|
331
|
+
expect(context.dataSet).toEqual('staging-sample');
|
|
332
|
+
expect(context.loopbackHeaders).toEqual({
|
|
333
|
+
cookie: `lowdefy_journey_data=${journeyActorToken}.session1`,
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test('a data cookie and a mutant cookie are both forwarded on loopbackHeaders', async () => {
|
|
338
|
+
registerSession();
|
|
339
|
+
const opened = openMutantRun({
|
|
340
|
+
mutant: {
|
|
341
|
+
buildId: 'b',
|
|
342
|
+
artifact: 'pages/form.json',
|
|
343
|
+
key: 'k',
|
|
344
|
+
arg: null,
|
|
345
|
+
operator: 'drop-block',
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
const data = `lowdefy_journey_data=${journeyActorToken}.session1`;
|
|
349
|
+
const mutant = `lowdefy_journey_mutant=${journeyActorToken}.${opened.cookiePayload}`;
|
|
350
|
+
const context = await createLowdefyContext({
|
|
351
|
+
c: createHonoContext({ headers: { cookie: `${mutant}; ${data}` } }),
|
|
352
|
+
});
|
|
353
|
+
expect(context.loopbackHeaders).toEqual({ cookie: `${data}; ${mutant}` });
|
|
354
|
+
opened.close();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('a wrong token or no data cookie gets no redirect, no waitUntil and no data set', async () => {
|
|
358
|
+
registerSession();
|
|
359
|
+
for (const headers of [{ cookie: 'lowdefy_journey_data=forged.session1' }, {}]) {
|
|
360
|
+
const readConfigFile = mockReadConfigFile();
|
|
361
|
+
const context = await createLowdefyContext({ c: createHonoContext({ headers }) });
|
|
362
|
+
expect(context.readConfigFile).toBe(readConfigFile);
|
|
363
|
+
expect(context.waitUntil).toBeUndefined();
|
|
364
|
+
expect(context.dataSet).toBeUndefined();
|
|
365
|
+
expect(context.loopbackHeaders).toEqual({});
|
|
366
|
+
}
|
|
367
|
+
expect(resolveTenantPreflight).toHaveBeenCalledTimes(2);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test('a verified data cookie for a closed or unknown session is answered 410 and reads no database', async () => {
|
|
371
|
+
const session = registerSession();
|
|
372
|
+
dataSessionRegistry.delete(session.id);
|
|
373
|
+
for (const id of ['session1', 'never-opened']) {
|
|
374
|
+
const readConfigFile = mockReadConfigFile();
|
|
375
|
+
const error = await createLowdefyContext({
|
|
376
|
+
c: createHonoContext({
|
|
377
|
+
headers: { cookie: `lowdefy_journey_data=${journeyActorToken}.${id}` },
|
|
378
|
+
}),
|
|
379
|
+
}).catch((caught) => caught);
|
|
380
|
+
expect(error.status).toBe(410);
|
|
381
|
+
const response = error.getResponse();
|
|
382
|
+
expect(response.status).toBe(410);
|
|
383
|
+
expect(await response.json()).toEqual({
|
|
384
|
+
name: 'DataSessionEnded',
|
|
385
|
+
message: `Data session ${id} has ended; this request outlived its journey.`,
|
|
386
|
+
});
|
|
387
|
+
expect(readConfigFile).not.toHaveBeenCalled();
|
|
388
|
+
createApiContext.mockReset();
|
|
389
|
+
}
|
|
390
|
+
expect(getAuth).not.toHaveBeenCalled();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test('a data-session request with no headless cookie gets no auth engine and no user', async () => {
|
|
394
|
+
registerSession();
|
|
395
|
+
const context = await createLowdefyContext({
|
|
396
|
+
c: createHonoContext({
|
|
397
|
+
path: '/api/detached/nightly',
|
|
398
|
+
headers: { cookie: `lowdefy_journey_data=${journeyActorToken}.session1` },
|
|
399
|
+
}),
|
|
400
|
+
});
|
|
401
|
+
expect(context.auth).toBeNull();
|
|
402
|
+
expect(context.user).toBeNull();
|
|
403
|
+
expect(getAuth).not.toHaveBeenCalled();
|
|
404
|
+
expect(resolveAuthentication).not.toHaveBeenCalled();
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
describe('who a headless caller is under a dev mock user', () => {
|
|
409
|
+
const mockUser = { id: 'mock', roles: ['mock-role'] };
|
|
410
|
+
|
|
411
|
+
test('a journey user the caller named wins over the mock user', async () => {
|
|
412
|
+
getMockUser.mockReturnValueOnce(mockUser);
|
|
413
|
+
getHeadlessUser.mockReturnValueOnce({
|
|
414
|
+
user: { id: 'lowdefy-headless', name: 'Lowdefy Headless', roles: ['admin'] },
|
|
415
|
+
explicit: true,
|
|
416
|
+
});
|
|
417
|
+
const context = await createLowdefyContext({ c: createHonoContext() });
|
|
418
|
+
expect(context.auth).toBeNull();
|
|
419
|
+
expect(context.user.roles).toEqual(['admin']);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test('the roleless headless default yields to the mock user', async () => {
|
|
423
|
+
getMockUser.mockReturnValueOnce(mockUser);
|
|
424
|
+
getHeadlessUser.mockReturnValueOnce({
|
|
425
|
+
user: { id: 'lowdefy-headless', name: 'Lowdefy Headless', roles: [] },
|
|
426
|
+
explicit: false,
|
|
427
|
+
});
|
|
428
|
+
const context = await createLowdefyContext({ c: createHonoContext() });
|
|
429
|
+
expect(context.user).toEqual(mockUser);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test('the roleless headless default is the caller when no mock user is active', async () => {
|
|
433
|
+
getHeadlessUser.mockReturnValueOnce({
|
|
434
|
+
user: { id: 'lowdefy-headless', name: 'Lowdefy Headless', roles: [] },
|
|
435
|
+
explicit: false,
|
|
436
|
+
});
|
|
437
|
+
const context = await createLowdefyContext({ c: createHonoContext() });
|
|
438
|
+
expect(context.user).toEqual({ id: 'lowdefy-headless', name: 'Lowdefy Headless', roles: [] });
|
|
439
|
+
});
|
|
440
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
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 net from 'node:net';
|
|
18
|
+
import { type } from '@lowdefy/helpers';
|
|
19
|
+
import { createClientAddressResolver } from '@lowdefy/node-utils';
|
|
20
|
+
|
|
21
|
+
import lowdefyConfig from '../build/config.js';
|
|
22
|
+
import { JOURNEY_COOKIES, readJourneyCookie } from './journeyCookies.js';
|
|
23
|
+
|
|
24
|
+
const resolveClientAddress = createClientAddressResolver({
|
|
25
|
+
trustedProxies: lowdefyConfig.trustedProxies,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// The actor cookie's payload is the address; see journeyActor.js.
|
|
29
|
+
function readJourneyActorAddress(cookieHeader) {
|
|
30
|
+
const address = readJourneyCookie({ cookieHeader, name: JOURNEY_COOKIES.actor.name });
|
|
31
|
+
if (address === null || net.isIP(address) === 0) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return address;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// The dev server resolves the client address the way the production server
|
|
38
|
+
// does - the connection peer, or the hop behind a trusted proxy - except that
|
|
39
|
+
// its own headless browser gives each journey actor an address of its own.
|
|
40
|
+
function getClientAddress(c) {
|
|
41
|
+
const journeyActorAddress = readJourneyActorAddress(c.req.header('cookie'));
|
|
42
|
+
if (!type.isNone(journeyActorAddress)) {
|
|
43
|
+
return journeyActorAddress;
|
|
44
|
+
}
|
|
45
|
+
return resolveClientAddress({
|
|
46
|
+
peerAddress: c.env?.incoming?.socket?.remoteAddress,
|
|
47
|
+
forwardedFor: c.req.header('x-forwarded-for'),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default getClientAddress;
|
|
@@ -0,0 +1,56 @@
|
|
|
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 { Hono } from 'hono';
|
|
18
|
+
import { jest } from '@jest/globals';
|
|
19
|
+
|
|
20
|
+
jest.unstable_mockModule('../build/config.js', () => ({ default: {} }));
|
|
21
|
+
|
|
22
|
+
const { default: getClientAddress } = await import('./getClientAddress.js');
|
|
23
|
+
const { journeyActorToken } = await import('./auth/journeyActor.js');
|
|
24
|
+
const { JOURNEY_COOKIES } = await import('./journeyCookies.js');
|
|
25
|
+
|
|
26
|
+
async function resolve(headers) {
|
|
27
|
+
const app = new Hono();
|
|
28
|
+
app.get('/', (c) => c.json({ clientAddress: getClientAddress(c) }));
|
|
29
|
+
const env = { incoming: { socket: { remoteAddress: '127.0.0.1' } } };
|
|
30
|
+
const response = await app.request('/', { headers }, env);
|
|
31
|
+
return (await response.json()).clientAddress;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test.each([
|
|
35
|
+
[
|
|
36
|
+
'the actor address from its headless journey cookie',
|
|
37
|
+
`${journeyActorToken}.203.0.113.7`,
|
|
38
|
+
'203.0.113.7',
|
|
39
|
+
],
|
|
40
|
+
['the peer when the cookie carries another token', `${'0'.repeat(64)}.203.0.113.7`, '127.0.0.1'],
|
|
41
|
+
['the peer when the cookie carries a shorter token', 'abc.203.0.113.7', '127.0.0.1'],
|
|
42
|
+
['the peer when the cookie has no address', `${journeyActorToken}`, '127.0.0.1'],
|
|
43
|
+
[
|
|
44
|
+
'the peer when the cookie address is not an IP',
|
|
45
|
+
`${journeyActorToken}.example.com`,
|
|
46
|
+
'127.0.0.1',
|
|
47
|
+
],
|
|
48
|
+
])('the dev getClientAddress resolves %s', async (_, value, expected) => {
|
|
49
|
+
expect(await resolve({ cookie: `session=abc; ${JOURNEY_COOKIES.actor.name}=${value}` })).toBe(
|
|
50
|
+
expected
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('the dev getClientAddress ignores X-Forwarded-For without trusted proxies', async () => {
|
|
55
|
+
expect(await resolve({ 'x-forwarded-for': '203.0.113.9' })).toBe('127.0.0.1');
|
|
56
|
+
});
|
|
@@ -53,12 +53,16 @@ test('getPageJitEnrichment scopes string-form, object-form, and args-nested _js
|
|
|
53
53
|
expect(jsEntries).not.toContain("'hashAbsent'");
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
const zap = { node: [['path', { d: 'M4 14 14 3' }]] };
|
|
57
|
+
const star = { node: [['path', { d: 'M12 2 15 9' }]] };
|
|
58
|
+
const other = { node: [['path', { d: 'M0 0' }]] };
|
|
59
|
+
|
|
56
60
|
test('getPageJitEnrichment discovers an icon name that appears only inside a _js source', () => {
|
|
57
61
|
const client = {
|
|
58
|
-
// The icon name lives only in the function body,
|
|
62
|
+
// The icon name lives only in the function body, single-quoted — the
|
|
59
63
|
// served pageConfig has this replaced by the hash, so scanning the config
|
|
60
|
-
// alone would miss it. Only
|
|
61
|
-
hashIcon:
|
|
64
|
+
// alone would miss it. Only Zap is a dynamic icon.
|
|
65
|
+
hashIcon: "return on ? 'Zap' : 'ZapOff';",
|
|
62
66
|
};
|
|
63
67
|
const pageConfig = {
|
|
64
68
|
id: 'p',
|
|
@@ -67,23 +71,37 @@ test('getPageJitEnrichment discovers an icon name that appears only inside a _js
|
|
|
67
71
|
|
|
68
72
|
const { dynamicIcons } = getPageJitEnrichment({
|
|
69
73
|
pageConfig,
|
|
70
|
-
buildContext: buildContext({ client, dynamicIconData: {
|
|
74
|
+
buildContext: buildContext({ client, dynamicIconData: { Zap: zap } }),
|
|
71
75
|
});
|
|
72
76
|
|
|
73
|
-
expect(dynamicIcons).toEqual({
|
|
77
|
+
expect(dynamicIcons).toEqual({ Zap: zap });
|
|
74
78
|
});
|
|
75
79
|
|
|
76
80
|
test('getPageJitEnrichment scopes icons referenced directly in the page config', () => {
|
|
77
|
-
const pageConfig = { id: 'p', blocks: [{ properties: { icon: '
|
|
81
|
+
const pageConfig = { id: 'p', blocks: [{ properties: { icon: 'Star' } }] };
|
|
78
82
|
|
|
79
83
|
const { dynamicIcons } = getPageJitEnrichment({
|
|
80
84
|
pageConfig,
|
|
81
|
-
buildContext: buildContext({
|
|
82
|
-
dynamicIconData: { FiStar: { tag: 'svg' }, FiOther: { tag: 'svg' } },
|
|
83
|
-
}),
|
|
85
|
+
buildContext: buildContext({ dynamicIconData: { Star: star, Other: other } }),
|
|
84
86
|
});
|
|
85
87
|
|
|
86
|
-
expect(dynamicIcons).toEqual({
|
|
88
|
+
expect(dynamicIcons).toEqual({ Star: star });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('getPageJitEnrichment scopes icons named in data-icon attributes inside HTML', () => {
|
|
92
|
+
const { dynamicIcons } = getPageJitEnrichment({
|
|
93
|
+
pageConfig: { id: 'p', blocks: [{ properties: { html: '<i data-icon="lucide:Star"></i>' } }] },
|
|
94
|
+
buildContext: buildContext({ dynamicIconData: { 'lucide:Star': star, Other: other } }),
|
|
95
|
+
});
|
|
96
|
+
expect(dynamicIcons).toEqual({ 'lucide:Star': star });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('getPageJitEnrichment scopes semantic icon names used on the page', () => {
|
|
100
|
+
const { dynamicIcons } = getPageJitEnrichment({
|
|
101
|
+
pageConfig: { id: 'p', blocks: [{ properties: { icon: 'edit' } }] },
|
|
102
|
+
buildContext: buildContext({ dynamicIconData: { edit: star, delete: other } }),
|
|
103
|
+
});
|
|
104
|
+
expect(dynamicIcons).toEqual({ edit: star });
|
|
87
105
|
});
|
|
88
106
|
|
|
89
107
|
test('getPageJitEnrichment returns {} when there is no build context', () => {
|
|
@@ -97,7 +115,7 @@ test('getPageJitEnrichment omits both fields when the page has no dynamic _js or
|
|
|
97
115
|
pageConfig,
|
|
98
116
|
buildContext: buildContext({
|
|
99
117
|
client: { hashUnused: 'return 1;' },
|
|
100
|
-
dynamicIconData: {
|
|
118
|
+
dynamicIconData: { Zap: zap },
|
|
101
119
|
}),
|
|
102
120
|
});
|
|
103
121
|
|
|
@@ -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 crypto from 'node:crypto';
|
|
18
|
+
|
|
19
|
+
// The hash a page build record keeps for a config file's content, as
|
|
20
|
+
// readConfigFile returned it: 'missing' for a file that does not exist.
|
|
21
|
+
function hashConfigContent(content) {
|
|
22
|
+
if (content === null) return 'missing';
|
|
23
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default hashConfigContent;
|
|
@@ -0,0 +1,57 @@
|
|
|
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 { execFile } from 'node:child_process';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { promisify } from 'node:util';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
|
|
24
|
+
import { jest } from '@jest/globals';
|
|
25
|
+
|
|
26
|
+
jest.setTimeout(60000);
|
|
27
|
+
|
|
28
|
+
const fixture = path.join(
|
|
29
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
30
|
+
'testFixtures/importAppCodeTwice.mjs'
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
let configDir;
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-import-app-code-'));
|
|
37
|
+
fs.writeFileSync(path.join(configDir, 'package.json'), '{ "type": "module" }');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
fs.rmSync(configDir, { recursive: true, force: true });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// The dev server's JIT page builds import app code through Vite's SSR module
|
|
45
|
+
// runner when @lowdefy/build is linked (the monorepo), and natively when it is
|
|
46
|
+
// installed from npm.
|
|
47
|
+
test.each(['vite', 'node'])(
|
|
48
|
+
'importAppCode loaded by %s imports an edited transformer again and reuses an unchanged one',
|
|
49
|
+
async (loader) => {
|
|
50
|
+
const { stdout } = await promisify(execFile)(process.execPath, [fixture, loader, configDir]);
|
|
51
|
+
expect(JSON.parse(stdout)).toEqual({
|
|
52
|
+
first: 'before',
|
|
53
|
+
edited: 'after',
|
|
54
|
+
unchangedReused: true,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
);
|