@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
|
@@ -0,0 +1,111 @@
|
|
|
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 findBuildFilesOutsideWatch from './findBuildFilesOutsideWatch.mjs';
|
|
22
|
+
|
|
23
|
+
let root;
|
|
24
|
+
let buildDirectory;
|
|
25
|
+
let configDirectory;
|
|
26
|
+
|
|
27
|
+
function writeRefMap(refMap) {
|
|
28
|
+
fs.writeFileSync(path.join(buildDirectory, 'refMap.json'), JSON.stringify(refMap));
|
|
29
|
+
return path.join(buildDirectory, 'refMap.json');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function writeJitMaps(name, refMap) {
|
|
33
|
+
const filePath = path.join(buildDirectory, 'jitMaps', name);
|
|
34
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
35
|
+
fs.writeFileSync(filePath, JSON.stringify({ keyMap: {}, refMap }));
|
|
36
|
+
return filePath;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
root = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-build-files-'));
|
|
41
|
+
buildDirectory = path.join(root, 'app', '.lowdefy', 'server', 'build');
|
|
42
|
+
configDirectory = path.join(root, 'app');
|
|
43
|
+
fs.mkdirSync(buildDirectory, { recursive: true });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('findBuildFilesOutsideWatch returns files the build read outside the watched directories', () => {
|
|
51
|
+
const refMapFile = writeRefMap({
|
|
52
|
+
1: { parent: null },
|
|
53
|
+
2: { parent: '1', path: 'pages.yaml' },
|
|
54
|
+
3: { parent: '2', path: '../shared/header.yaml' },
|
|
55
|
+
4: { parent: null, path: path.join(root, 'modules', 'layout', 'components', 'page.yaml') },
|
|
56
|
+
5: { parent: '4', path: path.join(root, 'modules', 'shared', 'title-block.yaml') },
|
|
57
|
+
6: { parent: '4', path: path.join(root, 'modules', 'shared', 'title-block.yaml') },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const files = findBuildFilesOutsideWatch({
|
|
61
|
+
mapsFiles: [refMapFile],
|
|
62
|
+
configDirectory,
|
|
63
|
+
watchRoots: [configDirectory, path.join(root, 'modules', 'layout')],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(files).toEqual([
|
|
67
|
+
path.join(root, 'shared', 'header.yaml'),
|
|
68
|
+
path.join(root, 'modules', 'shared', 'title-block.yaml'),
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('findBuildFilesOutsideWatch skips refs without a file path', () => {
|
|
73
|
+
const refMapFile = writeRefMap({
|
|
74
|
+
1: { parent: null },
|
|
75
|
+
2: { parent: '1', path: null, original: { resolver: 'resolver.js' } },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const files = findBuildFilesOutsideWatch({
|
|
79
|
+
mapsFiles: [refMapFile],
|
|
80
|
+
configDirectory,
|
|
81
|
+
watchRoots: [configDirectory],
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(files).toEqual([]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("findBuildFilesOutsideWatch reads a JIT page build's refs from its jitMaps file alone", () => {
|
|
88
|
+
writeRefMap({ 1: { parent: null, path: '../shared/skeleton.yaml' } });
|
|
89
|
+
const jitMapsFile = writeJitMaps('abc123-1-1.json', {
|
|
90
|
+
p_abc123_1: { parent: null, path: 'pages/home.yaml' },
|
|
91
|
+
p_abc123_2: { parent: 'p_abc123_1', path: '../shared/header.yaml' },
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const files = findBuildFilesOutsideWatch({
|
|
95
|
+
mapsFiles: [jitMapsFile],
|
|
96
|
+
configDirectory,
|
|
97
|
+
watchRoots: [configDirectory],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(files).toEqual([path.join(root, 'shared', 'header.yaml')]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('findBuildFilesOutsideWatch skips a jitMaps file pruned before it was read', () => {
|
|
104
|
+
const files = findBuildFilesOutsideWatch({
|
|
105
|
+
mapsFiles: [path.join(buildDirectory, 'jitMaps', 'gone-1-1.json')],
|
|
106
|
+
configDirectory,
|
|
107
|
+
watchRoots: [configDirectory],
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(files).toEqual([]);
|
|
111
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { pathToFileURL } from 'node:url';
|
|
18
|
+
import { Worker } from 'node:worker_threads';
|
|
19
|
+
|
|
20
|
+
// Node keeps every module it loads for the life of the process, and an ES
|
|
21
|
+
// module can not be dropped from that cache, so importing a changed file again
|
|
22
|
+
// in the manager returns the old module and the old modules it imports. A
|
|
23
|
+
// plugin's type list is read from its built files, which change while the dev
|
|
24
|
+
// server runs (a new block, request or operator type), so each read imports
|
|
25
|
+
// the file in a new worker thread, which starts with an empty module cache.
|
|
26
|
+
// Resolves to the module's default export (its namespace if it has none),
|
|
27
|
+
// copied to the manager as structured-clone data.
|
|
28
|
+
function importFresh(filePath) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const worker = new Worker(new URL('./importFreshWorker.mjs', import.meta.url), {
|
|
31
|
+
workerData: { url: pathToFileURL(filePath).href },
|
|
32
|
+
});
|
|
33
|
+
worker.once('message', (value) => {
|
|
34
|
+
resolve(value);
|
|
35
|
+
worker.terminate();
|
|
36
|
+
});
|
|
37
|
+
worker.once('error', reject);
|
|
38
|
+
worker.once('exit', (code) => {
|
|
39
|
+
if (code !== 0) {
|
|
40
|
+
reject(new Error(`Importing "${filePath}" stopped with exit code ${code}.`));
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default importFresh;
|
|
@@ -0,0 +1,72 @@
|
|
|
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 importFresh from './importFresh.mjs';
|
|
22
|
+
|
|
23
|
+
let pluginDir;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
pluginDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-import-fresh-'));
|
|
27
|
+
fs.writeFileSync(path.join(pluginDir, 'package.json'), JSON.stringify({ type: 'module' }));
|
|
28
|
+
fs.writeFileSync(
|
|
29
|
+
path.join(pluginDir, 'types.js'),
|
|
30
|
+
"import * as metas from './metas.js';\nexport default { blocks: Object.keys(metas) };\n"
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
fs.rmSync(pluginDir, { recursive: true, force: true });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('importFresh returns the default export of the module', async () => {
|
|
39
|
+
fs.writeFileSync(path.join(pluginDir, 'metas.js'), 'export const Card = {};\n');
|
|
40
|
+
|
|
41
|
+
const types = await importFresh(path.join(pluginDir, 'types.js'));
|
|
42
|
+
|
|
43
|
+
expect(types).toEqual({ blocks: ['Card'] });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('importFresh sees a change to a module the imported file imports', async () => {
|
|
47
|
+
fs.writeFileSync(path.join(pluginDir, 'metas.js'), 'export const Card = {};\n');
|
|
48
|
+
const before = await importFresh(path.join(pluginDir, 'types.js'));
|
|
49
|
+
|
|
50
|
+
fs.writeFileSync(
|
|
51
|
+
path.join(pluginDir, 'metas.js'),
|
|
52
|
+
'export const Card = {};\nexport const TaskList = {};\n'
|
|
53
|
+
);
|
|
54
|
+
const after = await importFresh(path.join(pluginDir, 'types.js'));
|
|
55
|
+
|
|
56
|
+
expect(before).toEqual({ blocks: ['Card'] });
|
|
57
|
+
expect(after).toEqual({ blocks: ['Card', 'TaskList'] });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('importFresh returns the namespace of a module without a default export', async () => {
|
|
61
|
+
fs.writeFileSync(path.join(pluginDir, 'messages.js'), "export const hello = 'Hello';\n");
|
|
62
|
+
|
|
63
|
+
const messages = await importFresh(path.join(pluginDir, 'messages.js'));
|
|
64
|
+
|
|
65
|
+
expect(messages).toEqual({ hello: 'Hello' });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('importFresh rejects when the module throws', async () => {
|
|
69
|
+
fs.writeFileSync(path.join(pluginDir, 'broken.js'), "throw new Error('Broken plugin.');\n");
|
|
70
|
+
|
|
71
|
+
await expect(importFresh(path.join(pluginDir, 'broken.js'))).rejects.toThrow('Broken plugin.');
|
|
72
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
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 { parentPort, workerData } from 'node:worker_threads';
|
|
18
|
+
|
|
19
|
+
const module = await import(workerData.url);
|
|
20
|
+
parentPort.postMessage(module.default ?? { ...module });
|
|
@@ -0,0 +1,35 @@
|
|
|
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 { spawn } from 'child_process';
|
|
18
|
+
|
|
19
|
+
// Kills the browser a Vite child launched, found by the tag the manager gave
|
|
20
|
+
// that child (see launchBrowser.js). A child that exits normally already
|
|
21
|
+
// takes its browser with it; this is for one killed outright, whose system
|
|
22
|
+
// Chrome runs in its own process group and is left running. Chrome's helper
|
|
23
|
+
// processes do not carry the tag; they exit with the browser process.
|
|
24
|
+
function killTaggedBrowser({ tag }) {
|
|
25
|
+
// Windows has no pkill. Best effort, as for the other process clean-up:
|
|
26
|
+
// the headless shell, the default browser, exits with its parent anyway.
|
|
27
|
+
if (process.platform === 'win32') {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// pkill exits 1 when nothing matched, the usual case.
|
|
31
|
+
const pkill = spawn('pkill', ['-f', '--', `--lowdefy-browser-tag=${tag}`], { stdio: 'ignore' });
|
|
32
|
+
pkill.on('error', () => {});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default killTaggedBrowser;
|
|
@@ -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 fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { resolvePagePath } from '@lowdefy/build/dev';
|
|
20
|
+
|
|
21
|
+
// Whether a changed page file now declares a path other than the one its page
|
|
22
|
+
// has in routes.json (adding or removing a path included). Page files are not
|
|
23
|
+
// skeleton sources, so without this the old URL would keep matching and the
|
|
24
|
+
// new one would 404 until the config is rebuilt.
|
|
25
|
+
async function pagePathChanged({ changedFiles, context }) {
|
|
26
|
+
const changed = new Set(changedFiles);
|
|
27
|
+
const pageIds = [...context.pageRegistry.values()]
|
|
28
|
+
.filter((pageEntry) => changed.has(pageEntry.refPath))
|
|
29
|
+
.map((pageEntry) => pageEntry.pageId);
|
|
30
|
+
if (pageIds.length === 0) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const routes = JSON.parse(
|
|
34
|
+
fs.readFileSync(path.join(context.directories.build, 'routes.json'), 'utf8')
|
|
35
|
+
);
|
|
36
|
+
const routePaths = new Map(routes.map((route) => [route.pageId, route.path]));
|
|
37
|
+
for (const pageId of pageIds) {
|
|
38
|
+
let pagePath;
|
|
39
|
+
try {
|
|
40
|
+
pagePath = await resolvePagePath({
|
|
41
|
+
pageId,
|
|
42
|
+
pageRegistry: context.pageRegistry,
|
|
43
|
+
context: context.buildContext,
|
|
44
|
+
});
|
|
45
|
+
} catch {
|
|
46
|
+
// A page file that does not resolve is reported by its page build.
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (pagePath !== routePaths.get(pageId)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default pagePathChanged;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
|
|
20
|
+
// Only what the dev server records. traces/production/ is the production
|
|
21
|
+
// mining cache, pruned by its own command on its own schedule.
|
|
22
|
+
const PRUNED_SOURCES = ['dev', 'journey'];
|
|
23
|
+
const DATE_DIRECTORY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
24
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
25
|
+
|
|
26
|
+
function listDateDirectories({ tracesDirectory }) {
|
|
27
|
+
return PRUNED_SOURCES.flatMap((source) => {
|
|
28
|
+
const sourceDirectory = path.join(tracesDirectory, source);
|
|
29
|
+
if (!fs.existsSync(sourceDirectory)) return [];
|
|
30
|
+
return fs
|
|
31
|
+
.readdirSync(sourceDirectory, { withFileTypes: true })
|
|
32
|
+
.filter((entry) => entry.isDirectory() && DATE_DIRECTORY_PATTERN.test(entry.name))
|
|
33
|
+
.map((entry) => ({ date: entry.name, path: path.join(sourceDirectory, entry.name) }));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function listTraceFiles({ dateDirectories }) {
|
|
38
|
+
return dateDirectories.flatMap((directory) =>
|
|
39
|
+
fs
|
|
40
|
+
.readdirSync(directory.path, { withFileTypes: true })
|
|
41
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl'))
|
|
42
|
+
.map((entry) => {
|
|
43
|
+
const filePath = path.join(directory.path, entry.name);
|
|
44
|
+
const stat = fs.statSync(filePath);
|
|
45
|
+
return { path: filePath, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Recordings are kept 7 days or 200 MB, whichever is smaller. Date
|
|
51
|
+
// directories older than maxAgeDays go first, then the oldest files by mtime
|
|
52
|
+
// until the rest fit, never the newest file (the tab recording right now).
|
|
53
|
+
function pruneRecordings({
|
|
54
|
+
configDirectory,
|
|
55
|
+
now = Date.now(),
|
|
56
|
+
maxAgeDays = 7,
|
|
57
|
+
maxBytes = 200 * 1024 * 1024,
|
|
58
|
+
}) {
|
|
59
|
+
const tracesDirectory = path.join(configDirectory, '.lowdefy', 'traces');
|
|
60
|
+
const cutoff = new Date(now - maxAgeDays * DAY_MS).toISOString().slice(0, 10);
|
|
61
|
+
const deleted = { directories: [], files: [] };
|
|
62
|
+
|
|
63
|
+
const dateDirectories = listDateDirectories({ tracesDirectory });
|
|
64
|
+
const kept = [];
|
|
65
|
+
dateDirectories.forEach((directory) => {
|
|
66
|
+
if (directory.date < cutoff) {
|
|
67
|
+
fs.rmSync(directory.path, { recursive: true, force: true });
|
|
68
|
+
deleted.directories.push(directory.path);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
kept.push(directory);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const files = listTraceFiles({ dateDirectories: kept }).sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
75
|
+
let total = files.reduce((sum, file) => sum + file.size, 0);
|
|
76
|
+
for (const file of files.slice(0, -1)) {
|
|
77
|
+
if (total <= maxBytes) break;
|
|
78
|
+
fs.rmSync(file.path, { force: true });
|
|
79
|
+
deleted.files.push(file.path);
|
|
80
|
+
total -= file.size;
|
|
81
|
+
}
|
|
82
|
+
return deleted;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default pruneRecordings;
|
|
@@ -0,0 +1,117 @@
|
|
|
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 pruneRecordings from './pruneRecordings.mjs';
|
|
22
|
+
|
|
23
|
+
const NOW = Date.parse('2026-10-03T12:00:00Z');
|
|
24
|
+
let configDirectory;
|
|
25
|
+
|
|
26
|
+
function tracePath(...segments) {
|
|
27
|
+
return path.join(configDirectory, '.lowdefy', 'traces', ...segments);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function writeFile({ segments, size = 10, mtimeMs = NOW }) {
|
|
31
|
+
const filePath = tracePath(...segments);
|
|
32
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
33
|
+
fs.writeFileSync(filePath, 'x'.repeat(size));
|
|
34
|
+
fs.utimesSync(filePath, mtimeMs / 1000, mtimeMs / 1000);
|
|
35
|
+
return filePath;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-prune-'));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
fs.rmSync(configDirectory, { recursive: true, force: true });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('pruneRecordings deletes date directories older than 7 days under dev and journey', () => {
|
|
47
|
+
['dev', 'journey'].forEach((source) => {
|
|
48
|
+
writeFile({ segments: [source, '2026-09-25', '20260925T100000Z-old000.jsonl'] });
|
|
49
|
+
writeFile({ segments: [source, '2026-09-26', '20260926T100000Z-keep00.jsonl'] });
|
|
50
|
+
});
|
|
51
|
+
const deleted = pruneRecordings({ configDirectory, now: NOW });
|
|
52
|
+
expect(deleted.directories).toHaveLength(2);
|
|
53
|
+
['dev', 'journey'].forEach((source) => {
|
|
54
|
+
expect(fs.existsSync(tracePath(source, '2026-09-25'))).toBe(false);
|
|
55
|
+
expect(fs.existsSync(tracePath(source, '2026-09-26', '20260926T100000Z-keep00.jsonl'))).toBe(
|
|
56
|
+
true
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('pruneRecordings trims the oldest files by mtime until the rest fit, keeping the newest', () => {
|
|
62
|
+
const oldest = writeFile({
|
|
63
|
+
segments: ['dev', '2026-10-03', '20261003T080000Z-aaaaaa.jsonl'],
|
|
64
|
+
size: 40,
|
|
65
|
+
mtimeMs: NOW - 3000,
|
|
66
|
+
});
|
|
67
|
+
const middle = writeFile({
|
|
68
|
+
segments: ['journey', '2026-10-03', '20261003T090000Z-bbbbbb.jsonl'],
|
|
69
|
+
size: 40,
|
|
70
|
+
mtimeMs: NOW - 2000,
|
|
71
|
+
});
|
|
72
|
+
const newest = writeFile({
|
|
73
|
+
segments: ['dev', '2026-10-03', '20261003T100000Z-cccccc.jsonl'],
|
|
74
|
+
size: 40,
|
|
75
|
+
mtimeMs: NOW - 1000,
|
|
76
|
+
});
|
|
77
|
+
const deleted = pruneRecordings({ configDirectory, now: NOW, maxBytes: 90 });
|
|
78
|
+
expect(deleted.files).toEqual([oldest]);
|
|
79
|
+
expect(fs.existsSync(middle)).toBe(true);
|
|
80
|
+
expect(fs.existsSync(newest)).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('pruneRecordings never deletes the newest file, even when it alone is over the limit', () => {
|
|
84
|
+
const older = writeFile({
|
|
85
|
+
segments: ['dev', '2026-10-03', '20261003T080000Z-aaaaaa.jsonl'],
|
|
86
|
+
size: 50,
|
|
87
|
+
mtimeMs: NOW - 2000,
|
|
88
|
+
});
|
|
89
|
+
const newest = writeFile({
|
|
90
|
+
segments: ['dev', '2026-10-03', '20261003T100000Z-cccccc.jsonl'],
|
|
91
|
+
size: 200,
|
|
92
|
+
mtimeMs: NOW - 1000,
|
|
93
|
+
});
|
|
94
|
+
pruneRecordings({ configDirectory, now: NOW, maxBytes: 100 });
|
|
95
|
+
expect(fs.existsSync(older)).toBe(false);
|
|
96
|
+
expect(fs.existsSync(newest)).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('pruneRecordings leaves foreign files and the production cache alone', () => {
|
|
100
|
+
const production = writeFile({
|
|
101
|
+
segments: ['production', '2026-01-01', 'events.jsonl'],
|
|
102
|
+
size: 500,
|
|
103
|
+
});
|
|
104
|
+
const foreignDirectory = writeFile({ segments: ['dev', 'scratch', 'notes.jsonl'], size: 500 });
|
|
105
|
+
const foreignFile = writeFile({ segments: ['dev', '2026-10-03', 'notes.txt'], size: 500 });
|
|
106
|
+
const loose = writeFile({ segments: ['dev', 'old.jsonl'], size: 500, mtimeMs: NOW - 99999 });
|
|
107
|
+
writeFile({ segments: ['dev', '2026-10-03', '20261003T100000Z-cccccc.jsonl'], size: 10 });
|
|
108
|
+
const deleted = pruneRecordings({ configDirectory, now: NOW, maxBytes: 1 });
|
|
109
|
+
expect(deleted).toEqual({ directories: [], files: [] });
|
|
110
|
+
[production, foreignDirectory, foreignFile, loose].forEach((filePath) => {
|
|
111
|
+
expect(fs.existsSync(filePath)).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('pruneRecordings does nothing when there are no traces', () => {
|
|
116
|
+
expect(pruneRecordings({ configDirectory, now: NOW })).toEqual({ directories: [], files: [] });
|
|
117
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
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/promises';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
|
|
20
|
+
import filesHaveSameBytes from './filesHaveSameBytes.mjs';
|
|
21
|
+
|
|
22
|
+
async function listFiles(directory) {
|
|
23
|
+
const entries = await fs.readdir(directory, { recursive: true, withFileTypes: true });
|
|
24
|
+
return entries
|
|
25
|
+
.filter((entry) => entry.isFile())
|
|
26
|
+
.map((entry) => path.relative(directory, path.join(entry.parentPath, entry.name)));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// The dev server reads build artifacts from disk on every request. Emptying
|
|
30
|
+
// the build directory and writing the new build into it leaves a window where
|
|
31
|
+
// artifacts are missing, so the build is written to a staging directory and
|
|
32
|
+
// moved over the live one file by file. A rename replaces a file in one step,
|
|
33
|
+
// so a reader always finds each artifact, from the old build or the new one.
|
|
34
|
+
// A staged file whose bytes equal the live one is left in staging, so the
|
|
35
|
+
// live file keeps its mtime and Vite, which watches the build directory, does
|
|
36
|
+
// not re-process every client artifact after each config build. Live files
|
|
37
|
+
// the new build did not write are removed next. The page registry
|
|
38
|
+
// is moved last: the JIT page builder rebuilds its cached build context, and
|
|
39
|
+
// drops its built pages, when the registry changes, so the registry must
|
|
40
|
+
// arrive after every other new file and after the old build's pages are gone.
|
|
41
|
+
// It is moved even when its bytes are unchanged: the JIT page builder learns
|
|
42
|
+
// of a config build only from its identity (inode and mtime), which a rename
|
|
43
|
+
// always changes.
|
|
44
|
+
// The live directory itself is never replaced, so file watchers on it keep
|
|
45
|
+
// working.
|
|
46
|
+
const pageRegistryFile = 'pageRegistry.json';
|
|
47
|
+
|
|
48
|
+
// idCounter.json is moved first. A JIT page build writes keyMap.json and refMap.json
|
|
49
|
+
// only while the live idCounter.json still names the config build it started from
|
|
50
|
+
// (skipStaleMapWrites), so the new idCounter.json must be live before the new maps
|
|
51
|
+
// arrive, or a page build of the old config could write its maps over them.
|
|
52
|
+
// It is moved even when its bytes are unchanged, so the order holds for every
|
|
53
|
+
// build.
|
|
54
|
+
const idCounterFile = 'idCounter.json';
|
|
55
|
+
|
|
56
|
+
// Files the manager and the dev tools write into the build directory to
|
|
57
|
+
// signal each other. The build does not write them, so they are never in the
|
|
58
|
+
// staged build, and removing them as stale would drop the signal: a restart
|
|
59
|
+
// request (.restart), a page invalidation or client reload queued behind the
|
|
60
|
+
// build, or the build status the dev tools read.
|
|
61
|
+
const controlFiles = new Set(['.restart', 'buildStatus.json', 'invalidatePages', 'reload']);
|
|
62
|
+
|
|
63
|
+
// On Windows a rename over a file another process has open (the dev server
|
|
64
|
+
// reading it, a virus scanner, the search indexer) fails for a moment with
|
|
65
|
+
// EPERM, EBUSY or EACCES, so each rename is retried for a few seconds before
|
|
66
|
+
// the publish fails.
|
|
67
|
+
const LOCKED_CODES = new Set(['EACCES', 'EBUSY', 'EPERM']);
|
|
68
|
+
const RENAME_DELAYS_MS = [25, 50, 100, 200, 400, 500, 500, 500, 500, 500];
|
|
69
|
+
const RM_OPTIONS = { force: true, recursive: true, maxRetries: 5 };
|
|
70
|
+
|
|
71
|
+
async function renameWithRetry({ from, to }) {
|
|
72
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
73
|
+
try {
|
|
74
|
+
await fs.rename(from, to);
|
|
75
|
+
return;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (!LOCKED_CODES.has(error.code) || attempt === RENAME_DELAYS_MS.length) {
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
await new Promise((resolve) => setTimeout(resolve, RENAME_DELAYS_MS[attempt]));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function moveFile({ buildDirectory, stagingDirectory, file }) {
|
|
86
|
+
const target = path.join(buildDirectory, file);
|
|
87
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
88
|
+
await renameWithRetry({ from: path.join(stagingDirectory, file), to: target });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function publishBuildDirectory({ buildDirectory, stagingDirectory }) {
|
|
92
|
+
await fs.mkdir(buildDirectory, { recursive: true });
|
|
93
|
+
const stagedFiles = await listFiles(stagingDirectory);
|
|
94
|
+
const liveFiles = await listFiles(buildDirectory);
|
|
95
|
+
|
|
96
|
+
const firstFiles = stagedFiles.filter((file) => file === idCounterFile);
|
|
97
|
+
const otherFiles = stagedFiles.filter(
|
|
98
|
+
(file) => file !== idCounterFile && file !== pageRegistryFile
|
|
99
|
+
);
|
|
100
|
+
for (const file of firstFiles) {
|
|
101
|
+
await moveFile({ buildDirectory, stagingDirectory, file });
|
|
102
|
+
}
|
|
103
|
+
for (const file of otherFiles) {
|
|
104
|
+
const unchanged = await filesHaveSameBytes({
|
|
105
|
+
a: path.join(stagingDirectory, file),
|
|
106
|
+
b: path.join(buildDirectory, file),
|
|
107
|
+
});
|
|
108
|
+
if (!unchanged) {
|
|
109
|
+
await moveFile({ buildDirectory, stagingDirectory, file });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const staged = new Set(stagedFiles);
|
|
114
|
+
const staleFiles = liveFiles.filter((file) => !staged.has(file) && !controlFiles.has(file));
|
|
115
|
+
await Promise.all(staleFiles.map((file) => fs.rm(path.join(buildDirectory, file), RM_OPTIONS)));
|
|
116
|
+
|
|
117
|
+
await moveFile({ buildDirectory, stagingDirectory, file: pageRegistryFile });
|
|
118
|
+
await fs.rm(stagingDirectory, RM_OPTIONS);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default publishBuildDirectory;
|