@lowdefy/server-dev 5.6.0 → 6.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/{pages/_app.js → client/App.jsx} +21 -33
- package/client/DevStreamContext.js +30 -0
- package/client/Inspector.jsx +226 -0
- package/{lib/client/Page.js → client/Page.jsx} +4 -8
- package/client/Reload.jsx +85 -0
- package/client/Routing.jsx +117 -0
- package/client/clientBuildImports.test.mjs +61 -0
- package/client/feedback/FeedbackMount.jsx +83 -0
- package/client/feedback/FeedbackOverlay.jsx +901 -0
- package/client/feedback/captureTabScreenshot.js +84 -0
- package/client/feedback/copyToClipboard.js +45 -0
- package/client/feedback/drawAnnotationsSvg.js +119 -0
- package/client/feedback/elementInspect.js +99 -0
- package/client/feedback/feedbackStyles.js +301 -0
- package/client/feedback/pinResponsiveImages.js +59 -0
- package/client/feedback/sendFeedback.js +39 -0
- package/client/feedback/useFeedbackToggle.js +70 -0
- package/client/main.jsx +42 -0
- package/client/openInEditor/OpenInEditorListener.jsx +268 -0
- package/client/openInEditor/findBlockLocation.js +51 -0
- package/lib/build/app.js +8 -1
- package/lib/build/appMeta.js +8 -1
- package/lib/build/auth.js +8 -1
- package/lib/build/config.js +8 -1
- package/lib/build/i18n.js +8 -1
- package/lib/build/logger.js +8 -1
- package/lib/build/theme.js +8 -1
- package/lib/client/auth/{Auth.js → Auth.jsx} +11 -2
- package/lib/client/auth/{AuthConfigured.js → AuthConfigured.jsx} +22 -10
- package/lib/client/setPageId.js +6 -5
- package/lib/client/utils/useMutateCache.js +14 -1
- package/lib/client/utils/usePageConfig.js +45 -43
- package/lib/client/utils/usePageConfig.test.mjs +62 -0
- package/lib/docs/captureAnnotatedScreenshot.js +88 -0
- package/lib/docs/checkpointPaths.js +91 -0
- package/lib/docs/checkpointStore.js +240 -0
- package/lib/docs/checkpointToMocks.js +65 -0
- package/lib/docs/clientErrorStore.js +36 -0
- package/lib/docs/configCheckpoints.test.mjs +156 -0
- package/lib/docs/createConfigCheckpoint.js +92 -0
- package/lib/docs/createDocsMcpServer.js +531 -0
- package/lib/docs/createDocsMcpServer.test.mjs +141 -0
- package/lib/docs/devMockRegistry.js +68 -0
- package/lib/docs/docs.test.mjs +448 -0
- package/lib/docs/enrichFeedback.js +71 -0
- package/lib/docs/evalOperator.js +47 -0
- package/lib/docs/evalOperator.test.mjs +157 -0
- package/lib/docs/evalOperatorHeadless.js +87 -0
- package/lib/docs/evalOperatorHeadless.test.mjs +72 -0
- package/lib/docs/evalOperatorInTab.js +48 -0
- package/lib/docs/findConfig.js +260 -0
- package/lib/docs/formatFeedback.js +121 -0
- package/lib/docs/formatFeedback.test.mjs +204 -0
- package/lib/docs/getAppMap.js +180 -0
- package/lib/docs/getBrowser.js +127 -0
- package/lib/docs/getBrowser.test.mjs +122 -0
- package/lib/docs/getBuildStatus.js +37 -0
- package/lib/docs/getCoreDoc.js +63 -0
- package/lib/docs/getDocsManifest.js +40 -0
- package/lib/docs/getExamples.js +58 -0
- package/lib/docs/getOverview.js +115 -0
- package/lib/docs/getPageConfig.js +53 -0
- package/lib/docs/getPluginDoc.js +55 -0
- package/lib/docs/getSchema.js +65 -0
- package/lib/docs/inspectState.js +47 -0
- package/lib/docs/inspectState.test.mjs +140 -0
- package/lib/docs/inspectStateFromTab.js +40 -0
- package/lib/docs/inspectStateHeadless.js +84 -0
- package/lib/docs/inspectStateHeadless.test.mjs +60 -0
- package/lib/docs/isWriteRequestsAllowed.js +50 -0
- package/lib/docs/listConfigCheckpoints.js +55 -0
- package/lib/docs/listPlugins.js +80 -0
- package/lib/docs/listTypes.js +105 -0
- package/lib/docs/loadState.js +152 -0
- package/lib/docs/mapPageBuildErrors.js +28 -0
- package/lib/docs/normalizeTypeKind.js +40 -0
- package/{pages/api/endpoints/[...endpointId].js → lib/docs/readBuildArtifact.js} +15 -12
- package/lib/docs/readPageArtifact.js +26 -0
- package/lib/docs/resolvePluginDir.js +49 -0
- package/lib/docs/resolveSource.js +48 -0
- package/lib/docs/resolveSource.test.mjs +100 -0
- package/lib/docs/revertConfigCheckpoint.js +73 -0
- package/lib/docs/runRequest.js +141 -0
- package/lib/docs/saveAnnotatedScreenshot.js +43 -0
- package/lib/docs/scaffoldPage.js +75 -0
- package/lib/docs/scaffoldPage.test.mjs +97 -0
- package/lib/docs/screenshotPage.js +110 -0
- package/lib/docs/screenshotPage.test.mjs +59 -0
- package/lib/docs/screenshotPageClip.test.mjs +102 -0
- package/lib/docs/searchDocs.js +67 -0
- package/lib/docs/setupTestFixtures.mjs +335 -0
- package/lib/docs/snapshotState.js +59 -0
- package/lib/docs/stateCheckpoints.test.mjs +366 -0
- package/lib/docs/tabAvailable.js +28 -0
- package/lib/docs/tabChannel.js +119 -0
- package/lib/docs/tabChannel.test.mjs +130 -0
- package/lib/server/auth/{getAuthOptions.js → getAuthConfig.js} +9 -4
- package/lib/server/auth/getDevSession.js +70 -0
- package/lib/server/auth/getDevSession.test.mjs +138 -0
- package/lib/server/auth/getHeadlessUser.js +40 -0
- package/lib/server/auth/{getMockSession.js → getMockUser.js} +8 -21
- package/lib/server/auth/headlessUser.js +29 -0
- package/lib/server/auth/resolveHeadlessUser.js +38 -0
- package/lib/server/auth/resolveHeadlessUser.test.mjs +78 -0
- package/lib/server/auth/session.js +39 -0
- package/lib/server/auth/strategies.js +47 -0
- package/lib/server/createLowdefyContext.js +128 -0
- package/lib/server/createLowdefyContext.test.mjs +117 -0
- package/lib/server/getPageJitEnrichment.test.mjs +105 -0
- package/lib/server/jitPageBuilder.js +125 -12
- package/lib/server/log/createHandleError.test.mjs +126 -0
- package/lib/server/log/createLogger.test.mjs +62 -0
- package/lib/server/scrubSecrets.js +23 -0
- package/manager/getContext.mjs +3 -7
- package/manager/processes/initialBuild.mjs +0 -2
- package/manager/processes/installPlugins.mjs +19 -11
- package/manager/processes/lowdefyBuild.mjs +29 -7
- package/manager/processes/lowdefyBuild.test.mjs +102 -0
- package/manager/processes/readDotEnv.mjs +13 -1
- package/manager/processes/shutdownServer.mjs +6 -6
- package/manager/processes/startProxy.mjs +160 -0
- package/manager/processes/startProxy.test.mjs +102 -0
- package/manager/processes/startServer.mjs +33 -16
- package/manager/processes/startWatchers.mjs +2 -2
- package/manager/processes/warnAuthUrlPortMismatch.mjs +47 -0
- package/manager/processes/warnAuthUrlPortMismatch.test.mjs +93 -0
- package/manager/run.mjs +50 -0
- package/manager/utils/createCustomPluginMessagesMap.mjs +8 -1
- package/manager/utils/createCustomPluginTypesMap.mjs +1 -0
- package/manager/utils/formatNoticeBox.mjs +30 -0
- package/manager/utils/{getNextBin.mjs → getViteBin.mjs} +7 -10
- package/manager/utils/updatePageTailwindCss.mjs +7 -1
- package/manager/utils/writeBuildStatus.mjs +39 -0
- package/manager/utils/writeBuildStatus.test.mjs +76 -0
- package/manager/watchers/lowdefyBuildWatcher.mjs +0 -1
- package/manager/watchers/moduleBuildWatcher.mjs +0 -1
- package/manager/watchers/{nextBuildWatcher.mjs → serverArtifactWatcher.mjs} +18 -20
- package/package.json +62 -49
- package/package.original.json +65 -52
- package/postcss.config.cjs +1 -0
- package/src/app.js +175 -0
- package/src/html/renderDevPage.js +86 -0
- package/src/lib/getPathSegments.js +25 -0
- package/src/lib/safeScriptJson.js +38 -0
- package/src/middleware/apiContext.js +32 -0
- package/src/middleware/errorHandler.js +48 -0
- package/src/middleware/errorHandler.test.mjs +247 -0
- package/src/routes/agent.js +64 -0
- package/src/routes/apiPage.js +37 -0
- package/src/routes/auth.js +49 -0
- package/src/routes/auth.test.mjs +84 -0
- package/src/routes/clientError.js +57 -0
- package/src/routes/cron.js +47 -0
- package/src/routes/cronForward.js +47 -0
- package/src/routes/detached.js +47 -0
- package/src/routes/devInspect.js +111 -0
- package/src/routes/devInspect.test.mjs +119 -0
- package/{pages/[[...pageId]].js → src/routes/devTools.js} +4 -2
- package/{pages/404.js → src/routes/docs/appMap.js} +6 -2
- package/{pages/api/dev-tools.js → src/routes/docs/buildStatus.js} +5 -4
- package/src/routes/docs/checkpointsCreate.js +28 -0
- package/src/routes/docs/checkpointsList.js +23 -0
- package/{pages/api/icons/dynamic.js → src/routes/docs/checkpointsRevert.js} +11 -12
- package/src/routes/docs/content.js +31 -0
- package/src/routes/docs/evalOperator.js +56 -0
- package/src/routes/docs/examples.js +33 -0
- package/src/routes/docs/find.js +30 -0
- package/src/routes/docs/index.js +23 -0
- package/src/routes/docs/inspectState.js +44 -0
- package/src/routes/docs/loadState.js +49 -0
- package/src/routes/docs/mcp.js +30 -0
- package/src/routes/docs/pageConfig.js +36 -0
- package/src/routes/docs/parseUserParam.js +52 -0
- package/src/routes/docs/parseUserParam.test.mjs +45 -0
- package/src/routes/docs/pluginDoc.js +33 -0
- package/src/routes/docs/plugins.js +23 -0
- package/src/routes/docs/runRequest.js +36 -0
- package/{lib/server/auth/getServerSession.js → src/routes/docs/schema.js} +17 -15
- package/src/routes/docs/screenshot.js +72 -0
- package/src/routes/docs/search.js +27 -0
- package/src/routes/docs/snapshotState.js +36 -0
- package/src/routes/docs/stateCheckpointsList.js +26 -0
- package/src/routes/docs/types.js +33 -0
- package/src/routes/endpoints.js +71 -0
- package/src/routes/feedback.js +110 -0
- package/src/routes/feedback.test.mjs +204 -0
- package/src/routes/jitPage.js +86 -0
- package/src/routes/mcp.js +34 -0
- package/{pages/api → src/routes}/ping.js +4 -2
- package/src/routes/reload.js +79 -0
- package/src/routes/request.js +74 -0
- package/src/routes/request.test.mjs +124 -0
- package/{pages/api → src/routes}/root.js +4 -4
- package/src/routes/usage.js +46 -0
- package/src/routes/websocket.js +33 -0
- package/src/websocket/devWebSocket.js +78 -0
- package/vite.config.js +111 -0
- package/.eslintrc.yaml +0 -1
- package/lib/client/App.js +0 -80
- package/lib/client/Reload.js +0 -60
- package/lib/server/apiWrapper.js +0 -109
- package/lib/server/compileCss.js +0 -39
- package/manager/processes/compileCss.mjs +0 -54
- package/manager/processes/nextBuild.mjs +0 -50
- package/next.config.js +0 -25
- package/pages/_document.js +0 -109
- package/pages/api/agent/[...path].js +0 -86
- package/pages/api/auth/[...nextauth].js +0 -48
- package/pages/api/client-error.js +0 -45
- package/pages/api/js/[env].js +0 -42
- package/pages/api/page/[...pageId].js +0 -74
- package/pages/api/reload.js +0 -53
- package/pages/api/request/[...path].js +0 -41
- /package/lib/client/{BuildErrorPage.js → BuildErrorPage.jsx} +0 -0
- /package/lib/client/{BuildingPage.js → BuildingPage.jsx} +0 -0
- /package/lib/client/{ErrorBar.js → ErrorBar.jsx} +0 -0
- /package/lib/client/{InstallingPluginsPage.js → InstallingPluginsPage.jsx} +0 -0
- /package/lib/client/{RestartingPage.js → RestartingPage.jsx} +0 -0
|
@@ -14,14 +14,7 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
// CSS layer order — MUST be the first CSS import. Turbopack treats this as critical
|
|
18
|
-
// CSS that loads before hydration, locking the cascade priority (antd > base/preflight)
|
|
19
|
-
// before antd's StyleProvider injects @layer antd {} at runtime.
|
|
20
|
-
import '../build/layer-order.css';
|
|
21
|
-
|
|
22
17
|
import React, { Suspense, useCallback, useEffect, useRef, useState } from 'react';
|
|
23
|
-
import dynamic from 'next/dynamic';
|
|
24
|
-
import { useRouter } from 'next/router';
|
|
25
18
|
import useSWR from 'swr';
|
|
26
19
|
|
|
27
20
|
import { ErrorBoundary } from '@lowdefy/block-utils';
|
|
@@ -33,13 +26,10 @@ import { XProvider } from '@ant-design/x';
|
|
|
33
26
|
import antdLocaleLoaders from '../build/i18n/antdLocales.js';
|
|
34
27
|
import antdXLocaleLoaders from '../build/i18n/antdXLocales.js';
|
|
35
28
|
import dayjsLocaleMap from '../build/i18n/dayjsLocales.js';
|
|
36
|
-
import Auth from '../lib/client/auth/Auth.
|
|
37
|
-
import ErrorBar from '../lib/client/ErrorBar.
|
|
29
|
+
import Auth from '../lib/client/auth/Auth.jsx';
|
|
30
|
+
import ErrorBar from '../lib/client/ErrorBar.jsx';
|
|
38
31
|
import request from '../lib/client/utils/request.js';
|
|
39
|
-
|
|
40
|
-
// Full Tailwind CSS — also loaded via <link href="tailwind-jit.css"> in _document.js
|
|
41
|
-
// for hot-reloading. The Turbopack chunk provides layer ordering guarantee on initial load.
|
|
42
|
-
import '../build/globals.css';
|
|
32
|
+
import Routing from './Routing.jsx';
|
|
43
33
|
|
|
44
34
|
function ThemeTokenResolver({ lowdefyRef, children }) {
|
|
45
35
|
const { token } = antdTheme.useToken();
|
|
@@ -50,12 +40,11 @@ function ThemeTokenResolver({ lowdefyRef, children }) {
|
|
|
50
40
|
return children;
|
|
51
41
|
}
|
|
52
42
|
|
|
53
|
-
function App({
|
|
54
|
-
const router = useRouter();
|
|
43
|
+
function App({ router }) {
|
|
55
44
|
const lowdefyRef = useRef({});
|
|
56
45
|
const [runtimeErrors, setRuntimeErrors] = useState([]);
|
|
57
|
-
// Subscribe to rootConfig SWR cache — deduplicates with
|
|
58
|
-
// Without suspense so
|
|
46
|
+
// Subscribe to rootConfig SWR cache — deduplicates with Routing's fetch.
|
|
47
|
+
// Without suspense so App doesn't suspend — just re-renders when data arrives.
|
|
59
48
|
const { data: rootConfig } = useSWR(`${router.basePath}/api/root`, (url) => request({ url }));
|
|
60
49
|
if (rootConfig?.theme) {
|
|
61
50
|
lowdefyRef.current.theme = rootConfig.theme;
|
|
@@ -66,7 +55,11 @@ function App({ Component }) {
|
|
|
66
55
|
configDarkMode: lowdefyRef.current.theme?.darkMode,
|
|
67
56
|
});
|
|
68
57
|
|
|
69
|
-
const {
|
|
58
|
+
const {
|
|
59
|
+
active: activeLocale,
|
|
60
|
+
antdLocale,
|
|
61
|
+
antdXLocale,
|
|
62
|
+
} = useLocale({
|
|
70
63
|
i18n: rootConfig?.i18n,
|
|
71
64
|
antdLocaleLoaders,
|
|
72
65
|
antdXLocaleLoaders,
|
|
@@ -99,12 +92,10 @@ function App({ Component }) {
|
|
|
99
92
|
]);
|
|
100
93
|
}, []);
|
|
101
94
|
|
|
102
|
-
// Clear runtime errors on
|
|
95
|
+
// Clear runtime errors on navigation
|
|
103
96
|
useEffect(() => {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return () => router.events.off('routeChangeStart', clearErrors);
|
|
107
|
-
}, [router.events]);
|
|
97
|
+
return router.subscribe(() => setRuntimeErrors([]));
|
|
98
|
+
}, [router]);
|
|
108
99
|
|
|
109
100
|
const handleError = useCallback((error) => {
|
|
110
101
|
if (lowdefyRef.current?._internal?.handleError) {
|
|
@@ -115,10 +106,11 @@ function App({ Component }) {
|
|
|
115
106
|
}, []);
|
|
116
107
|
|
|
117
108
|
// XProvider extends antd's ConfigProvider; merging antd + antd-X locale packs
|
|
118
|
-
// gives X components their built-in strings
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
109
|
+
// gives X components (Bubble, Sender, Conversations, ...) their built-in strings
|
|
110
|
+
// alongside antd's. antd X ships only en_US + zh_CN; other locales fall back
|
|
111
|
+
// to en_US for X-native strings.
|
|
112
|
+
const mergedLocale =
|
|
113
|
+
antdLocale || antdXLocale ? { ...(antdLocale ?? {}), ...(antdXLocale ?? {}) } : undefined;
|
|
122
114
|
|
|
123
115
|
return (
|
|
124
116
|
<StyleProvider layer>
|
|
@@ -153,7 +145,7 @@ function App({ Component }) {
|
|
|
153
145
|
>
|
|
154
146
|
<Auth>
|
|
155
147
|
{(auth) => {
|
|
156
|
-
return <
|
|
148
|
+
return <Routing auth={auth} lowdefy={lowdefyRef.current} router={router} />;
|
|
157
149
|
}}
|
|
158
150
|
</Auth>
|
|
159
151
|
</Suspense>
|
|
@@ -166,8 +158,4 @@ function App({ Component }) {
|
|
|
166
158
|
);
|
|
167
159
|
}
|
|
168
160
|
|
|
169
|
-
|
|
170
|
-
ssr: false,
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
export default DynamicApp;
|
|
161
|
+
export default App;
|
|
@@ -0,0 +1,30 @@
|
|
|
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 { createContext } from 'react';
|
|
18
|
+
|
|
19
|
+
// The one /api/reload EventSource a dev tab holds, owned by Reload.jsx and
|
|
20
|
+
// shared with Inspector.jsx: { source, tabId }. Dev runs on HTTP/1.1, where
|
|
21
|
+
// browsers cap connections per host at six and an open event stream holds
|
|
22
|
+
// one for the life of the tab. Two streams per tab meant three tabs of the
|
|
23
|
+
// same app saturated the pool and the next fetch queued forever, so every
|
|
24
|
+
// dev-only channel rides this single stream instead of opening its own.
|
|
25
|
+
// - source: the EventSource, null until Reload's effect has opened it.
|
|
26
|
+
// - tabId: the id the server assigned this connection in its tab registry
|
|
27
|
+
// (lib/docs/tabChannel.js), null until the stream's `tab` event arrives.
|
|
28
|
+
const DevStreamContext = createContext({ source: null, tabId: null });
|
|
29
|
+
|
|
30
|
+
export default DevStreamContext;
|
|
@@ -0,0 +1,226 @@
|
|
|
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 { useContext, useEffect, useRef } from 'react';
|
|
18
|
+
|
|
19
|
+
import { type, serializer } from '@lowdefy/helpers';
|
|
20
|
+
|
|
21
|
+
import DevStreamContext from './DevStreamContext.js';
|
|
22
|
+
|
|
23
|
+
// Dev-only agent-state-xray channel. Listens for inspect-request/eval-request
|
|
24
|
+
// events on the tab's single /api/reload EventSource (owned by Reload.jsx,
|
|
25
|
+
// shared through DevStreamContext) rather than opening its own — a second
|
|
26
|
+
// stream per tab exhausted the browser's HTTP/1.1 connection pool once a few
|
|
27
|
+
// tabs of the same app were open, and every later fetch queued forever.
|
|
28
|
+
//
|
|
29
|
+
// pageId tracking: routes/reload.js registers every connection as an
|
|
30
|
+
// inspectable tab (lib/docs/tabChannel.js) and announces the tab id on the
|
|
31
|
+
// stream. This component then posts { tabId, pageId } to
|
|
32
|
+
// /api/dev-inspect/page whenever the developer navigates, so the registry's
|
|
33
|
+
// view of "what page is this tab on" stays current without reconnecting.
|
|
34
|
+
//
|
|
35
|
+
// Never let a bad payload or a plugin's operator error crash the app this is
|
|
36
|
+
// piggybacking on — every handler is wrapped, and the component itself
|
|
37
|
+
// always renders null.
|
|
38
|
+
function postJson({ basePath, path, body }) {
|
|
39
|
+
try {
|
|
40
|
+
fetch(`${basePath}/api/dev-inspect${path}`, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: { 'Content-Type': 'application/json' },
|
|
43
|
+
body: JSON.stringify(body),
|
|
44
|
+
}).catch(() => {
|
|
45
|
+
// Best-effort — a failed callback just leaves the agent's request to
|
|
46
|
+
// time out server-side, and a failed page update leaves the tab
|
|
47
|
+
// registered on its previous page.
|
|
48
|
+
});
|
|
49
|
+
} catch {
|
|
50
|
+
// JSON.stringify or fetch throwing synchronously — still best-effort.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function postResult({ basePath, requestId, result }) {
|
|
55
|
+
postJson({ basePath, path: '', body: { requestId, result } });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function postTabPage({ basePath, tabId, pageId }) {
|
|
59
|
+
postJson({ basePath, path: '/page', body: { tabId, pageId } });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildSnapshot({ lowdefy, pageId }) {
|
|
63
|
+
const context = lowdefy?.contexts?.[`page:${pageId}`];
|
|
64
|
+
if (type.isNone(context)) {
|
|
65
|
+
return { error: `No live context for page "${pageId}".` };
|
|
66
|
+
}
|
|
67
|
+
return serializer.serializeToString({
|
|
68
|
+
pageId,
|
|
69
|
+
state: context.state,
|
|
70
|
+
requests: context.requests,
|
|
71
|
+
eventLog: (context.eventLog ?? []).slice(-50),
|
|
72
|
+
global: lowdefy.lowdefyGlobal,
|
|
73
|
+
user: lowdefy.user,
|
|
74
|
+
input: lowdefy.inputs?.[`page:${pageId}`],
|
|
75
|
+
urlQuery: window.location.search,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Dev-only agent-state-xray: puts a human tester's real browser tab into a
|
|
80
|
+
// checkpoint's recorded state when it loads a page with `?_checkpoint=<name>`
|
|
81
|
+
// in the URL. Fetches the checkpoint's state part from
|
|
82
|
+
// GET <basePath>/api/dev-inspect/checkpoint/<name> (src/routes/devInspect.js
|
|
83
|
+
// — that fetch also loads the checkpoint's recorded requests into
|
|
84
|
+
// devMockRegistry server-side, so this tab's own requests replay recorded
|
|
85
|
+
// data), then injects state the same way e2e-utils' setState does:
|
|
86
|
+
// context._internal.State.set(...) + context._internal.update(). Polls
|
|
87
|
+
// briefly for the page context to exist since this can run before the page
|
|
88
|
+
// has finished mounting. Never lets a bad checkpoint or a missing context
|
|
89
|
+
// break the app — every step is try/catch, and failures just leave the app
|
|
90
|
+
// running unmodified.
|
|
91
|
+
async function bootstrapFromCheckpoint({ basePath, pageId, checkpointName, isCancelled }) {
|
|
92
|
+
try {
|
|
93
|
+
const response = await fetch(
|
|
94
|
+
`${basePath}/api/dev-inspect/checkpoint/${encodeURIComponent(checkpointName)}`
|
|
95
|
+
);
|
|
96
|
+
const body = await response.json();
|
|
97
|
+
if (!response.ok || body?.error) {
|
|
98
|
+
// eslint-disable-next-line no-console
|
|
99
|
+
console.error(
|
|
100
|
+
`Failed to load checkpoint "${checkpointName}": ${body?.error ?? response.statusText}`
|
|
101
|
+
);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const payload = serializer.deserialize(body);
|
|
105
|
+
|
|
106
|
+
const deadline = Date.now() + 5000;
|
|
107
|
+
let context;
|
|
108
|
+
while (!isCancelled() && Date.now() < deadline) {
|
|
109
|
+
context = window.lowdefy?.contexts?.[`page:${pageId}`];
|
|
110
|
+
if (context) {
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
// eslint-disable-next-line no-await-in-loop
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
115
|
+
}
|
|
116
|
+
if (isCancelled() || type.isNone(context)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
Object.entries(payload.state ?? {}).forEach(([key, value]) => {
|
|
121
|
+
context._internal.State.set(key, value);
|
|
122
|
+
});
|
|
123
|
+
context._internal.update();
|
|
124
|
+
} catch (error) {
|
|
125
|
+
// eslint-disable-next-line no-console
|
|
126
|
+
console.error('Failed to load checkpoint "%s":', checkpointName, error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function evalExpression({ lowdefy, pageId, expression }) {
|
|
131
|
+
const context = lowdefy?.contexts?.[`page:${pageId}`];
|
|
132
|
+
if (type.isNone(context)) {
|
|
133
|
+
return { error: `No live context for page "${pageId}".` };
|
|
134
|
+
}
|
|
135
|
+
// Callers may pass the operator expression as a JSON-serializable object,
|
|
136
|
+
// or (since it travels as a JSON string end-to-end from an MCP tool
|
|
137
|
+
// argument) as a JSON string that needs one more parse.
|
|
138
|
+
const input = type.isString(expression) ? JSON.parse(expression) : expression;
|
|
139
|
+
const { output, errors } = context._internal.parser.parse({ input, location: 'agent_eval' });
|
|
140
|
+
return {
|
|
141
|
+
value: serializer.serializeToString(output),
|
|
142
|
+
errors: errors.map((error) => error.message),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const Inspector = ({ basePath, lowdefy, pageId }) => {
|
|
147
|
+
const { source, tabId } = useContext(DevStreamContext);
|
|
148
|
+
const pageIdRef = useRef(pageId);
|
|
149
|
+
pageIdRef.current = pageId;
|
|
150
|
+
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
if (type.isNone(source)) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const onInspectRequest = (message) => {
|
|
157
|
+
let requestId;
|
|
158
|
+
try {
|
|
159
|
+
const data = JSON.parse(message.data);
|
|
160
|
+
requestId = data.requestId;
|
|
161
|
+
const targetPageId = data.pageId ?? pageIdRef.current;
|
|
162
|
+
const result = buildSnapshot({ lowdefy, pageId: targetPageId });
|
|
163
|
+
postResult({ basePath, requestId, result });
|
|
164
|
+
} catch (error) {
|
|
165
|
+
postResult({ basePath, requestId, result: { error: error.message } });
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const onEvalRequest = (message) => {
|
|
170
|
+
let requestId;
|
|
171
|
+
try {
|
|
172
|
+
const data = JSON.parse(message.data);
|
|
173
|
+
requestId = data.requestId;
|
|
174
|
+
const targetPageId = data.pageId ?? pageIdRef.current;
|
|
175
|
+
const result = evalExpression({
|
|
176
|
+
lowdefy,
|
|
177
|
+
pageId: targetPageId,
|
|
178
|
+
expression: data.expression,
|
|
179
|
+
});
|
|
180
|
+
postResult({ basePath, requestId, result });
|
|
181
|
+
} catch (error) {
|
|
182
|
+
postResult({ basePath, requestId, result: { error: error.message } });
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
source.addEventListener('inspect-request', onInspectRequest);
|
|
187
|
+
source.addEventListener('eval-request', onEvalRequest);
|
|
188
|
+
return () => {
|
|
189
|
+
source.removeEventListener('inspect-request', onInspectRequest);
|
|
190
|
+
source.removeEventListener('eval-request', onEvalRequest);
|
|
191
|
+
};
|
|
192
|
+
}, [basePath, lowdefy, source]);
|
|
193
|
+
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
if (type.isNone(tabId) || type.isNone(pageId)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
postTabPage({ basePath, tabId, pageId });
|
|
199
|
+
}, [basePath, tabId, pageId]);
|
|
200
|
+
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
if (type.isNone(pageId)) {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
const checkpointName = new URLSearchParams(window.location.search).get('_checkpoint');
|
|
206
|
+
if (type.isNone(checkpointName)) {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let cancelled = false;
|
|
211
|
+
bootstrapFromCheckpoint({
|
|
212
|
+
basePath,
|
|
213
|
+
pageId,
|
|
214
|
+
checkpointName,
|
|
215
|
+
isCancelled: () => cancelled,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
return () => {
|
|
219
|
+
cancelled = true;
|
|
220
|
+
};
|
|
221
|
+
}, [basePath, pageId]);
|
|
222
|
+
|
|
223
|
+
return null;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export default Inspector;
|
|
@@ -18,10 +18,9 @@ import React, { useEffect, useRef } from 'react';
|
|
|
18
18
|
import { GenIcon } from 'react-icons/lib';
|
|
19
19
|
import Client from '@lowdefy/client';
|
|
20
20
|
|
|
21
|
-
import BuildErrorPage from '
|
|
22
|
-
import InstallingPluginsPage from '
|
|
23
|
-
import
|
|
24
|
-
import usePageConfig from './utils/usePageConfig.js';
|
|
21
|
+
import BuildErrorPage from '../lib/client/BuildErrorPage.jsx';
|
|
22
|
+
import InstallingPluginsPage from '../lib/client/InstallingPluginsPage.jsx';
|
|
23
|
+
import usePageConfig from '../lib/client/utils/usePageConfig.js';
|
|
25
24
|
|
|
26
25
|
const Page = ({
|
|
27
26
|
auth,
|
|
@@ -48,7 +47,7 @@ const Page = ({
|
|
|
48
47
|
}, [pageConfig?._warnings, lowdefy]);
|
|
49
48
|
|
|
50
49
|
if (!pageConfig) {
|
|
51
|
-
router.replace(
|
|
50
|
+
router.replace({ pathname: '/404' });
|
|
52
51
|
return '';
|
|
53
52
|
}
|
|
54
53
|
if (pageConfig.buildError) {
|
|
@@ -63,9 +62,6 @@ const Page = ({
|
|
|
63
62
|
if (pageConfig.installing) {
|
|
64
63
|
return <InstallingPluginsPage packages={pageConfig.packages} />;
|
|
65
64
|
}
|
|
66
|
-
if (resetContext.restarting) {
|
|
67
|
-
return <RestartingPage />;
|
|
68
|
-
}
|
|
69
65
|
|
|
70
66
|
// Merge dynamic JS entries fetched after JIT build with the static jsMap
|
|
71
67
|
const mergedJsMap = pageConfig._jsEntries ? { ...jsMap, ...pageConfig._jsEntries } : jsMap;
|
|
@@ -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 React, { useEffect, useState } from 'react';
|
|
18
|
+
|
|
19
|
+
import DevStreamContext from './DevStreamContext.js';
|
|
20
|
+
import useMutateCache from '../lib/client/utils/useMutateCache.js';
|
|
21
|
+
import waitForRestartedServer from '../lib/client/utils/waitForRestartedServer.js';
|
|
22
|
+
|
|
23
|
+
// SSE listener — config rebuilds notify via /api/reload. Tailwind CSS updates
|
|
24
|
+
// arrive through Vite HMR now (globals.css is in the dev module graph), so
|
|
25
|
+
// the old tailwind-jit.css link cache-bust is gone.
|
|
26
|
+
//
|
|
27
|
+
// This is the tab's only EventSource. It is shared through DevStreamContext
|
|
28
|
+
// so Inspector.jsx can listen on the same connection instead of opening a
|
|
29
|
+
// second one — see the connection-limit note in DevStreamContext.js.
|
|
30
|
+
const Reload = ({ children, basePath, lowdefy }) => {
|
|
31
|
+
const [reset, setReset] = useState(false);
|
|
32
|
+
const [restarting, setRestarting] = useState(false);
|
|
33
|
+
const [devStream, setDevStream] = useState({ source: null, tabId: null });
|
|
34
|
+
// reset is a one-shot boolean the engine lowers only after a page mounts
|
|
35
|
+
// (getContext.js) — while a page is suspended it stays true and setReset(true)
|
|
36
|
+
// bails out of re-rendering. The tick always changes, so every reload event
|
|
37
|
+
// re-invokes Routing's render prop, which re-reads reloadVersion and mints a
|
|
38
|
+
// fresh Suspense/SWR key — a suspended tab recovers instead of stranding on
|
|
39
|
+
// the building screen.
|
|
40
|
+
const [, setReloadTick] = useState(0);
|
|
41
|
+
const mutateCache = useMutateCache(basePath);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const sse = new EventSource(`${basePath}/api/reload`);
|
|
44
|
+
|
|
45
|
+
// routes/reload.js registers every connection as an inspectable tab and
|
|
46
|
+
// announces the id it chose as the first event on the stream. The stream
|
|
47
|
+
// is published to consumers only then, in one state update, so the page
|
|
48
|
+
// subtree re-renders once at mount rather than once per field.
|
|
49
|
+
sse.addEventListener('tab', (message) => {
|
|
50
|
+
const { tabId } = JSON.parse(message.data);
|
|
51
|
+
setDevStream({ source: sse, tabId });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
sse.addEventListener('reload', () => {
|
|
55
|
+
// add a update delay to prevent rerender before server is shut down for rebuild, ideally we don't want to do this.
|
|
56
|
+
// TODO: We need to pass a flag when a rebuild will happen so that client does not trigger render.
|
|
57
|
+
setTimeout(async () => {
|
|
58
|
+
await mutateCache();
|
|
59
|
+
if (lowdefy._internal?.initialised) {
|
|
60
|
+
lowdefy._internal.initialised = false;
|
|
61
|
+
}
|
|
62
|
+
setReset(true);
|
|
63
|
+
setReloadTick((tick) => tick + 1);
|
|
64
|
+
console.log('Reloaded config.');
|
|
65
|
+
}, 600);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
sse.onerror = () => {
|
|
69
|
+
setRestarting(true);
|
|
70
|
+
console.log('Rebuilding Lowdefy App.');
|
|
71
|
+
sse.close();
|
|
72
|
+
waitForRestartedServer(basePath);
|
|
73
|
+
};
|
|
74
|
+
return () => {
|
|
75
|
+
sse.close();
|
|
76
|
+
};
|
|
77
|
+
}, []);
|
|
78
|
+
return (
|
|
79
|
+
<DevStreamContext.Provider value={devStream}>
|
|
80
|
+
{children({ reset, setReset, restarting })}
|
|
81
|
+
</DevStreamContext.Provider>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export default Reload;
|
|
@@ -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 React, { Suspense, useEffect, useState } from 'react';
|
|
18
|
+
|
|
19
|
+
import Head from '@lowdefy/client/adapters/Head.js';
|
|
20
|
+
import createLinkComponent from '@lowdefy/client/adapters/Link.js';
|
|
21
|
+
|
|
22
|
+
import BuildingPage from '../lib/client/BuildingPage.jsx';
|
|
23
|
+
import RestartingPage from '../lib/client/RestartingPage.jsx';
|
|
24
|
+
import FeedbackMount from './feedback/FeedbackMount.jsx';
|
|
25
|
+
import Inspector from './Inspector.jsx';
|
|
26
|
+
import OpenInEditorListener from './openInEditor/OpenInEditorListener.jsx';
|
|
27
|
+
import Reload from './Reload.jsx';
|
|
28
|
+
import Page from './Page.jsx';
|
|
29
|
+
import setPageId from '../lib/client/setPageId.js';
|
|
30
|
+
import { bumpNavVersion, getReloadVersion } from '../lib/client/utils/useMutateCache.js';
|
|
31
|
+
import useRootConfig from '../lib/client/utils/useRootConfig.js';
|
|
32
|
+
|
|
33
|
+
import actions from '../build/plugins/actions.js';
|
|
34
|
+
import blockMetas from '../build/plugins/blockMetas.json';
|
|
35
|
+
import blocks from '../build/plugins/blocks.js';
|
|
36
|
+
import icons from '../build/plugins/icons.js';
|
|
37
|
+
import operators from '../build/plugins/operators/client.js';
|
|
38
|
+
import staticJsMap from '../build/plugins/operators/clientJsMap.js';
|
|
39
|
+
|
|
40
|
+
// Replaces lib/client/App.js — page resolution driven by the custom router
|
|
41
|
+
// instead of next/router, everything else preserved.
|
|
42
|
+
function Routing({ auth, lowdefy, router }) {
|
|
43
|
+
const { data: rootConfig } = useRootConfig(router.basePath);
|
|
44
|
+
const [location, setLocation] = useState(() => router.getLocation());
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
return router.subscribe((location) => {
|
|
48
|
+
// Dynamic pages re-resolve per navigation — bump before setLocation so
|
|
49
|
+
// the page config SWR key changes with the navigation. The router
|
|
50
|
+
// notifies with a fresh location object per event, so same-URL
|
|
51
|
+
// navigations already produce a state change.
|
|
52
|
+
bumpNavVersion();
|
|
53
|
+
setLocation(location);
|
|
54
|
+
});
|
|
55
|
+
}, [router]);
|
|
56
|
+
|
|
57
|
+
const [Link] = useState(() => createLinkComponent({ router }));
|
|
58
|
+
|
|
59
|
+
if (rootConfig?.theme) {
|
|
60
|
+
lowdefy.theme = rootConfig.theme;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { redirect, pageId } = setPageId(location, rootConfig);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (redirect) {
|
|
66
|
+
router.replace({ pathname: `/${pageId}` });
|
|
67
|
+
}
|
|
68
|
+
}, [redirect, pageId, router]);
|
|
69
|
+
if (redirect) {
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<>
|
|
75
|
+
<FeedbackMount basePath={router.basePath} lowdefy={lowdefy} pageId={pageId} />
|
|
76
|
+
<OpenInEditorListener basePath={router.basePath} pageId={pageId} />
|
|
77
|
+
<Reload basePath={router.basePath} lowdefy={lowdefy}>
|
|
78
|
+
{(resetContext) => (
|
|
79
|
+
<>
|
|
80
|
+
{/* Inside Reload so it can share Reload's event stream (DevStreamContext). */}
|
|
81
|
+
<Inspector basePath={router.basePath} lowdefy={lowdefy} pageId={pageId} />
|
|
82
|
+
{/* Rendered here, not in Page — Page sits below the Suspense boundary
|
|
83
|
+
and cannot render anything while its config fetch is suspended, so
|
|
84
|
+
a restarting server would present as "Building page..." forever. */}
|
|
85
|
+
{resetContext.restarting ? (
|
|
86
|
+
<RestartingPage />
|
|
87
|
+
) : (
|
|
88
|
+
<Suspense key={`${pageId}_${getReloadVersion()}`} fallback={<BuildingPage />}>
|
|
89
|
+
<Page
|
|
90
|
+
auth={auth}
|
|
91
|
+
Components={{ Head, Link }}
|
|
92
|
+
config={{
|
|
93
|
+
rootConfig,
|
|
94
|
+
}}
|
|
95
|
+
jsMap={staticJsMap}
|
|
96
|
+
lowdefy={lowdefy}
|
|
97
|
+
pageId={pageId}
|
|
98
|
+
resetContext={resetContext}
|
|
99
|
+
router={router}
|
|
100
|
+
types={{
|
|
101
|
+
actions,
|
|
102
|
+
blockMetas,
|
|
103
|
+
blocks,
|
|
104
|
+
icons,
|
|
105
|
+
operators,
|
|
106
|
+
}}
|
|
107
|
+
/>
|
|
108
|
+
</Suspense>
|
|
109
|
+
)}
|
|
110
|
+
</>
|
|
111
|
+
)}
|
|
112
|
+
</Reload>
|
|
113
|
+
</>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export default Routing;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
|
|
21
|
+
// Everything the client imports from build/ ships in the browser bundle. build/config.json is
|
|
22
|
+
// server-only: it carries every deployment environment's settings (other environments' urls,
|
|
23
|
+
// secret names, email filters). The client learns only the current environment's name and
|
|
24
|
+
// switched-off features, through appMeta. This allowlist fails the build of any new client import
|
|
25
|
+
// of a build artifact until it has been checked for what it exposes.
|
|
26
|
+
const allowed = new Set([
|
|
27
|
+
'auth.json',
|
|
28
|
+
'appMeta.json',
|
|
29
|
+
'logger.json',
|
|
30
|
+
'i18n/antdLocales.js',
|
|
31
|
+
'i18n/antdXLocales.js',
|
|
32
|
+
'i18n/dayjsLocales.js',
|
|
33
|
+
'plugins/actions.js',
|
|
34
|
+
'plugins/blockMetas.json',
|
|
35
|
+
'plugins/blocks.js',
|
|
36
|
+
'plugins/icons.js',
|
|
37
|
+
'plugins/operators/client.js',
|
|
38
|
+
'plugins/operators/clientJsMap.js',
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
42
|
+
|
|
43
|
+
function listSourceFiles(dir) {
|
|
44
|
+
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
45
|
+
const full = path.join(dir, entry.name);
|
|
46
|
+
if (entry.isDirectory()) return listSourceFiles(full);
|
|
47
|
+
return /\.(js|jsx|mjs)$/.test(entry.name) && !entry.name.includes('.test.') ? [full] : [];
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
test('client code imports only client-safe build artifacts', () => {
|
|
52
|
+
const imported = ['client', 'lib/client'].flatMap((dir) =>
|
|
53
|
+
listSourceFiles(path.join(root, dir)).flatMap((file) =>
|
|
54
|
+
[...fs.readFileSync(file, 'utf8').matchAll(/from '[^']*\/build\/([^']+)'/g)].map(
|
|
55
|
+
(match) => ({ file: path.relative(root, file), artifact: match[1] })
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
);
|
|
59
|
+
expect(imported.length).toBeGreaterThan(0);
|
|
60
|
+
expect(imported.filter(({ artifact }) => !allowed.has(artifact))).toEqual([]);
|
|
61
|
+
});
|