@lowdefy/server-dev 5.5.1 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{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/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} +23 -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} +7 -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/{apiWrapper.js → createLowdefyContext.js} +63 -57
- package/lib/server/getPageJitEnrichment.test.mjs +105 -0
- package/lib/server/jitPageBuilder.js +125 -12
- package/lib/server/log/createHandleError.js +9 -0
- package/lib/server/log/createHandleError.test.mjs +126 -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 +61 -47
- package/package.original.json +64 -55
- 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 +235 -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/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 -107
- 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
|
@@ -0,0 +1,33 @@
|
|
|
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 { setPendingWebSocketContext } from '../websocket/devWebSocket.js';
|
|
18
|
+
|
|
19
|
+
// In dev, Vite owns the HTTP server, so websocket upgrades can't flow through
|
|
20
|
+
// @hono/node-server. The Vite plugin (see vite.config.js) fetches this route
|
|
21
|
+
// with the upgrade request's headers to run the full middleware chain (auth
|
|
22
|
+
// session, apiContext), and this handler hands the built context back to the
|
|
23
|
+
// upgrade handler through a pending-context exchange keyed by request id.
|
|
24
|
+
function websocketHandler(c) {
|
|
25
|
+
const rid = c.req.header('x-lowdefy-websocket-rid');
|
|
26
|
+
if (!rid) {
|
|
27
|
+
return c.json({ message: 'WebSocket upgrade required.' }, 400);
|
|
28
|
+
}
|
|
29
|
+
setPendingWebSocketContext(rid, c.get('lowdefyContext'));
|
|
30
|
+
return c.json({ ok: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default websocketHandler;
|
|
@@ -0,0 +1,78 @@
|
|
|
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 { randomUUID } from 'node:crypto';
|
|
18
|
+
import { createChannelRegistry, createWebSocketConnection } from '@lowdefy/api';
|
|
19
|
+
|
|
20
|
+
import app from '../app.js';
|
|
21
|
+
|
|
22
|
+
// One registry per dev server process, shared by all websocket connections.
|
|
23
|
+
const registry = createChannelRegistry();
|
|
24
|
+
|
|
25
|
+
// Contexts built by the /api/websocket route (full middleware chain) are
|
|
26
|
+
// handed to the upgrade handler through this exchange, keyed by request id.
|
|
27
|
+
const pendingContexts = new Map();
|
|
28
|
+
|
|
29
|
+
function setPendingWebSocketContext(rid, context) {
|
|
30
|
+
pendingContexts.set(rid, context);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function takePendingWebSocketContext(rid) {
|
|
34
|
+
const context = pendingContexts.get(rid);
|
|
35
|
+
pendingContexts.delete(rid);
|
|
36
|
+
return context;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Called by the Vite plugin on an HTTP upgrade for /api/websocket. Runs the
|
|
40
|
+
// dev Hono app for the upgrade request (auth session + apiContext), then
|
|
41
|
+
// completes the websocket handshake and wires the connection to the registry.
|
|
42
|
+
async function handleWebSocketUpgrade({ request, socket, head, wss }) {
|
|
43
|
+
const rid = randomUUID();
|
|
44
|
+
const headers = new Headers();
|
|
45
|
+
Object.entries(request.headers).forEach(([key, value]) => {
|
|
46
|
+
if (value) {
|
|
47
|
+
headers.set(key, Array.isArray(value) ? value[0] : value);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
headers.set('x-lowdefy-websocket-rid', rid);
|
|
51
|
+
|
|
52
|
+
const url = `http://${request.headers.host ?? 'localhost'}${request.url}`;
|
|
53
|
+
const response = await app.fetch(new Request(url, { headers }));
|
|
54
|
+
const context = takePendingWebSocketContext(rid);
|
|
55
|
+
|
|
56
|
+
if (!context || response.status !== 200) {
|
|
57
|
+
socket.end('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
62
|
+
const connection = createWebSocketConnection(context, {
|
|
63
|
+
registry,
|
|
64
|
+
send: (message) => ws.send(message),
|
|
65
|
+
});
|
|
66
|
+
ws.on('message', (data) => {
|
|
67
|
+
connection.handleMessage(data.toString());
|
|
68
|
+
});
|
|
69
|
+
ws.on('close', () => {
|
|
70
|
+
connection.close();
|
|
71
|
+
});
|
|
72
|
+
ws.on('error', (error) => {
|
|
73
|
+
context.logger.debug({ event: 'ws_error' }, String(error?.message ?? error));
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { handleWebSocketUpgrade, setPendingWebSocketContext };
|
package/vite.config.js
ADDED
|
@@ -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 'node:fs';
|
|
18
|
+
import { defineConfig } from 'vite';
|
|
19
|
+
import react from '@vitejs/plugin-react';
|
|
20
|
+
import devServer from '@hono/vite-dev-server';
|
|
21
|
+
import { WebSocketServer } from 'ws';
|
|
22
|
+
|
|
23
|
+
// basePath from the Lowdefy build — assets and routes are served under it.
|
|
24
|
+
let basePath = '';
|
|
25
|
+
try {
|
|
26
|
+
const config = JSON.parse(fs.readFileSync('./build/config.json', 'utf8'));
|
|
27
|
+
basePath = config.basePath ?? '';
|
|
28
|
+
} catch (e) {
|
|
29
|
+
// No build yet — default base.
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Vite owns the HTTP server in dev, so /api/websocket upgrades are handled
|
|
33
|
+
// here instead of @hono/node-server. The upgrade handler module is loaded
|
|
34
|
+
// through Vite's SSR graph so it shares the Hono app and @lowdefy/api module
|
|
35
|
+
// instances with the dev server routes. Vite's own HMR websocket uses a
|
|
36
|
+
// different path and is untouched.
|
|
37
|
+
function lowdefyWebSocket() {
|
|
38
|
+
return {
|
|
39
|
+
name: 'lowdefy-websocket',
|
|
40
|
+
configureServer(server) {
|
|
41
|
+
const wss = new WebSocketServer({ noServer: true, maxPayload: 256 * 1024 });
|
|
42
|
+
server.httpServer?.on('upgrade', async (request, socket, head) => {
|
|
43
|
+
const { pathname } = new URL(request.url ?? '/', 'http://localhost');
|
|
44
|
+
if (pathname !== `${basePath}/api/websocket`) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const { handleWebSocketUpgrade } = await server.ssrLoadModule(
|
|
49
|
+
'./src/websocket/devWebSocket.js'
|
|
50
|
+
);
|
|
51
|
+
await handleWebSocketUpgrade({ request, socket, head, wss });
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(error);
|
|
54
|
+
socket.destroy();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default defineConfig(({ mode }) => ({
|
|
62
|
+
base: `${basePath}/`,
|
|
63
|
+
plugins: [
|
|
64
|
+
react(),
|
|
65
|
+
lowdefyWebSocket(),
|
|
66
|
+
devServer({
|
|
67
|
+
entry: './src/app.js',
|
|
68
|
+
// Vite serves these itself; everything else routes to the Hono app.
|
|
69
|
+
exclude: [
|
|
70
|
+
/^\/client\/.+/,
|
|
71
|
+
/^\/lib\/.+/,
|
|
72
|
+
/^\/build\/.+/,
|
|
73
|
+
/^\/@.+$/,
|
|
74
|
+
/^\/node_modules\/.*/,
|
|
75
|
+
/\?t=\d+$/,
|
|
76
|
+
/^\/favicon\.ico$/,
|
|
77
|
+
],
|
|
78
|
+
}),
|
|
79
|
+
],
|
|
80
|
+
define: {
|
|
81
|
+
// Vite does not replace process.env.NODE_ENV inside dependencies —
|
|
82
|
+
// plugin and client code branch on it.
|
|
83
|
+
'process.env.NODE_ENV': JSON.stringify(mode === 'production' ? 'production' : 'development'),
|
|
84
|
+
},
|
|
85
|
+
optimizeDeps: {
|
|
86
|
+
// There is no index.html in the root (the Hono app renders HTML), so the
|
|
87
|
+
// dependency scanner has nothing to crawl and would discover client deps
|
|
88
|
+
// lazily at first request — ending in a mid-session "optimized
|
|
89
|
+
// dependencies changed" full reload. The client entry reaches every
|
|
90
|
+
// plugin import statically (main.jsx → App → Routing → build/plugins/*),
|
|
91
|
+
// so a startup crawl discovers the full set. Linked workspace plugins are
|
|
92
|
+
// intentionally not pre-bundled (Vite default) so local plugin edits stay
|
|
93
|
+
// live.
|
|
94
|
+
entries: ['client/main.jsx'],
|
|
95
|
+
},
|
|
96
|
+
resolve: {
|
|
97
|
+
// linked plugin packages (pnpm link: / workspace) must share one React.
|
|
98
|
+
dedupe: ['react', 'react-dom'],
|
|
99
|
+
},
|
|
100
|
+
server: {
|
|
101
|
+
watch: {
|
|
102
|
+
// Tailwind scan inputs are rewritten by JIT page builds, and Vite
|
|
103
|
+
// full-reloads the browser on any watched .html change — killing
|
|
104
|
+
// in-flight requests on every first page visit. CSS recompilation does
|
|
105
|
+
// not need these events: globals.css imports build/tailwind-candidates.css,
|
|
106
|
+
// which the JIT builder touches whenever tailwind content changes (the
|
|
107
|
+
// sole recompile trigger — see writeGlobalsCss in @lowdefy/build).
|
|
108
|
+
ignored: ['**/lowdefy-build/tailwind/**'],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
}));
|
package/.eslintrc.yaml
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
extends: 'plugin:@next/next/core-web-vitals'
|
package/lib/client/App.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
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 } from 'react';
|
|
18
|
-
|
|
19
|
-
import { useRouter } from 'next/router';
|
|
20
|
-
|
|
21
|
-
import Head from 'next/head';
|
|
22
|
-
import Link from 'next/link';
|
|
23
|
-
|
|
24
|
-
import BuildingPage from './BuildingPage.js';
|
|
25
|
-
import Reload from './Reload.js';
|
|
26
|
-
import Page from './Page.js';
|
|
27
|
-
import setPageId from './setPageId.js';
|
|
28
|
-
import { getReloadVersion } from './utils/useMutateCache.js';
|
|
29
|
-
import useRootConfig from './utils/useRootConfig.js';
|
|
30
|
-
|
|
31
|
-
import actions from '../../build/plugins/actions.js';
|
|
32
|
-
import blockMetas from '../../build/plugins/blockMetas.json';
|
|
33
|
-
import blocks from '../../build/plugins/blocks.js';
|
|
34
|
-
import icons from '../../build/plugins/icons.js';
|
|
35
|
-
import operators from '../../build/plugins/operators/client.js';
|
|
36
|
-
import staticJsMap from '../../build/plugins/operators/clientJsMap.js';
|
|
37
|
-
|
|
38
|
-
const App = ({ auth, lowdefy }) => {
|
|
39
|
-
const router = useRouter();
|
|
40
|
-
const { data: rootConfig } = useRootConfig(router.basePath);
|
|
41
|
-
|
|
42
|
-
if (rootConfig?.theme) {
|
|
43
|
-
lowdefy.theme = rootConfig.theme;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const { redirect, pageId } = setPageId(router, rootConfig);
|
|
47
|
-
if (redirect) {
|
|
48
|
-
router.push(`/${pageId}`);
|
|
49
|
-
return '';
|
|
50
|
-
}
|
|
51
|
-
return (
|
|
52
|
-
<Reload basePath={router.basePath} lowdefy={lowdefy}>
|
|
53
|
-
{(resetContext) => (
|
|
54
|
-
<Suspense key={`${pageId}_${getReloadVersion()}`} fallback={<BuildingPage />}>
|
|
55
|
-
<Page
|
|
56
|
-
auth={auth}
|
|
57
|
-
Components={{ Head, Link }}
|
|
58
|
-
config={{
|
|
59
|
-
rootConfig,
|
|
60
|
-
}}
|
|
61
|
-
jsMap={staticJsMap}
|
|
62
|
-
lowdefy={lowdefy}
|
|
63
|
-
pageId={pageId}
|
|
64
|
-
resetContext={resetContext}
|
|
65
|
-
router={router}
|
|
66
|
-
types={{
|
|
67
|
-
actions,
|
|
68
|
-
blockMetas,
|
|
69
|
-
blocks,
|
|
70
|
-
icons,
|
|
71
|
-
operators,
|
|
72
|
-
}}
|
|
73
|
-
/>
|
|
74
|
-
</Suspense>
|
|
75
|
-
)}
|
|
76
|
-
</Reload>
|
|
77
|
-
);
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export default App;
|
package/lib/client/Reload.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
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 useMutateCache from './utils/useMutateCache.js';
|
|
20
|
-
import waitForRestartedServer from './utils/waitForRestartedServer.js';
|
|
21
|
-
|
|
22
|
-
const Reload = ({ children, basePath, lowdefy }) => {
|
|
23
|
-
const [reset, setReset] = useState(false);
|
|
24
|
-
const [restarting, setRestarting] = useState(false);
|
|
25
|
-
const mutateCache = useMutateCache(basePath);
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
const sse = new EventSource(`${basePath}/api/reload`);
|
|
28
|
-
|
|
29
|
-
sse.addEventListener('reload', () => {
|
|
30
|
-
// add a update delay to prevent rerender before server is shut down for rebuild, ideally we don't want to do this.
|
|
31
|
-
// TODO: We need to pass a flag when a rebuild will happen so that client does not trigger render.
|
|
32
|
-
setTimeout(async () => {
|
|
33
|
-
await mutateCache();
|
|
34
|
-
if (lowdefy._internal?.initialised) {
|
|
35
|
-
lowdefy._internal.initialised = false;
|
|
36
|
-
}
|
|
37
|
-
// Refresh JIT CSS link to pick up newly compiled Tailwind classes
|
|
38
|
-
const cssLink = document.getElementById('tailwind-jit-css');
|
|
39
|
-
if (cssLink) {
|
|
40
|
-
cssLink.href = `${basePath}/tailwind-jit.css?v=${Date.now()}`;
|
|
41
|
-
}
|
|
42
|
-
setReset(true);
|
|
43
|
-
console.log('Reloaded config.');
|
|
44
|
-
}, 600);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
sse.onerror = () => {
|
|
48
|
-
setRestarting(true);
|
|
49
|
-
console.log('Rebuilding Lowdefy App.');
|
|
50
|
-
sse.close();
|
|
51
|
-
waitForRestartedServer(basePath);
|
|
52
|
-
};
|
|
53
|
-
return () => {
|
|
54
|
-
sse.close();
|
|
55
|
-
};
|
|
56
|
-
}, []);
|
|
57
|
-
return <>{children({ reset, setReset, restarting })}</>;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export default Reload;
|
package/lib/server/compileCss.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
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 postcss from 'postcss';
|
|
20
|
-
import tailwindcssPlugin from '@tailwindcss/postcss';
|
|
21
|
-
|
|
22
|
-
async function compileCss(buildDirectory) {
|
|
23
|
-
const serverDirectory = path.resolve(buildDirectory, '..');
|
|
24
|
-
const inputPath = path.join(buildDirectory, 'globals.css');
|
|
25
|
-
const outputPath = path.join(serverDirectory, 'public/tailwind-jit.css');
|
|
26
|
-
if (!fs.existsSync(inputPath)) return;
|
|
27
|
-
const css = fs.readFileSync(inputPath, 'utf8');
|
|
28
|
-
const result = await postcss([tailwindcssPlugin()]).process(css, {
|
|
29
|
-
from: inputPath,
|
|
30
|
-
to: outputPath,
|
|
31
|
-
});
|
|
32
|
-
// The Tailwind PostCSS plugin consumes the @layer order declaration from
|
|
33
|
-
// globals.css during compilation. Re-inject it as the first line so the
|
|
34
|
-
// browser establishes the correct cascade priority (antd > base/preflight).
|
|
35
|
-
const layerOrder = '@layer theme, base, antd, components, utilities;\n';
|
|
36
|
-
fs.writeFileSync(outputPath, layerOrder + result.css);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export default compileCss;
|
|
@@ -1,54 +0,0 @@
|
|
|
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 postcss from 'postcss';
|
|
20
|
-
import tailwindcssPlugin from '@tailwindcss/postcss';
|
|
21
|
-
|
|
22
|
-
function formatDuration(ms) {
|
|
23
|
-
if (ms < 1000) return `${ms}ms`;
|
|
24
|
-
return `${(ms / 1000).toFixed(2)}s`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function compileCss({ directories, logger }) {
|
|
28
|
-
return async () => {
|
|
29
|
-
const inputPath = path.join(directories.build, 'globals.css');
|
|
30
|
-
const outputPath = path.join(directories.server, 'public/tailwind-jit.css');
|
|
31
|
-
if (!fs.existsSync(inputPath)) return;
|
|
32
|
-
|
|
33
|
-
logger.info({ spin: 'start' }, 'Compiling CSS...');
|
|
34
|
-
const startTime = Date.now();
|
|
35
|
-
|
|
36
|
-
const css = fs.readFileSync(inputPath, 'utf8');
|
|
37
|
-
const result = await postcss([tailwindcssPlugin()]).process(css, {
|
|
38
|
-
from: inputPath,
|
|
39
|
-
to: outputPath,
|
|
40
|
-
});
|
|
41
|
-
// The Tailwind PostCSS plugin consumes the @layer order declaration from
|
|
42
|
-
// globals.css during compilation. Re-inject it as the first line so the
|
|
43
|
-
// browser establishes the correct cascade priority (antd > base/preflight).
|
|
44
|
-
const layerOrder = '@layer theme, base, antd, components, utilities;\n';
|
|
45
|
-
fs.writeFileSync(outputPath, layerOrder + result.css);
|
|
46
|
-
|
|
47
|
-
logger.info(
|
|
48
|
-
{ spin: 'succeed' },
|
|
49
|
-
`Compiled CSS in ${formatDuration(Date.now() - startTime)}.`
|
|
50
|
-
);
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export default compileCss;
|
|
@@ -1,50 +0,0 @@
|
|
|
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 { BuildError } from '@lowdefy/errors';
|
|
18
|
-
import { spawnProcess } from '@lowdefy/node-utils';
|
|
19
|
-
|
|
20
|
-
function formatDuration(ms) {
|
|
21
|
-
if (ms < 1000) return `${ms}ms`;
|
|
22
|
-
return `${(ms / 1000).toFixed(2)}s`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function nextBuild({ bin, logger }) {
|
|
26
|
-
return async () => {
|
|
27
|
-
logger.info({ spin: 'start' }, 'Building app...');
|
|
28
|
-
const startTime = Date.now();
|
|
29
|
-
const errorLines = [];
|
|
30
|
-
try {
|
|
31
|
-
await spawnProcess({
|
|
32
|
-
command: 'node',
|
|
33
|
-
args: ['--max-old-space-size=4096', bin.next, 'build'],
|
|
34
|
-
stdOutLineHandler: (line) => logger.debug(line),
|
|
35
|
-
stdErrLineHandler: (line) => {
|
|
36
|
-
logger.debug(line);
|
|
37
|
-
errorLines.push(line);
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
} catch (err) {
|
|
41
|
-
if (errorLines.length > 0) {
|
|
42
|
-
errorLines.forEach((line) => logger.error(line));
|
|
43
|
-
}
|
|
44
|
-
throw new BuildError('Next.js build failed. See above for details.');
|
|
45
|
-
}
|
|
46
|
-
logger.info({ spin: 'succeed' }, `Built app in ${formatDuration(Date.now() - startTime)}.`);
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export default nextBuild;
|
package/next.config.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
const lowdefyConfig = require('./build/config.json');
|
|
2
|
-
const blockPackages = require('./build/blockPackages.json');
|
|
3
|
-
const serverExternalPackages = require('./build/serverExternalPackages.json');
|
|
4
|
-
|
|
5
|
-
// Transpile @lowdefy/client plus all block plugin packages that may
|
|
6
|
-
// contain CSS imports (e.g., AG Grid themes, loaders, markdown).
|
|
7
|
-
// Built dynamically so custom user plugins are included automatically.
|
|
8
|
-
const transpilePackages = [
|
|
9
|
-
'@lowdefy/client',
|
|
10
|
-
'@ant-design/x',
|
|
11
|
-
'@ant-design/x-markdown',
|
|
12
|
-
...blockPackages,
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
const nextConfig = {
|
|
16
|
-
basePath: lowdefyConfig.basePath,
|
|
17
|
-
// reactStrictMode: true,
|
|
18
|
-
turbopack: {},
|
|
19
|
-
transpilePackages,
|
|
20
|
-
serverExternalPackages,
|
|
21
|
-
compress: false,
|
|
22
|
-
poweredByHeader: false,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
module.exports = nextConfig;
|
package/pages/_document.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
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 from 'react';
|
|
18
|
-
import Document, { Html, Head, Main, NextScript } from 'next/document';
|
|
19
|
-
|
|
20
|
-
import appJson from '../lib/build/app.js';
|
|
21
|
-
import lowdefyConfig from '../lib/build/config.js';
|
|
22
|
-
import themeConfig from '../lib/build/theme.js';
|
|
23
|
-
|
|
24
|
-
const basePath = lowdefyConfig.basePath ?? '';
|
|
25
|
-
const VALID_COLOR_MODES = ['system', 'light', 'dark'];
|
|
26
|
-
const configColorMode = VALID_COLOR_MODES.includes(themeConfig.darkMode)
|
|
27
|
-
? themeConfig.darkMode
|
|
28
|
-
: 'system';
|
|
29
|
-
const darkBg = themeConfig?.antd?.darkToken?.colorBgLayout ?? '#000';
|
|
30
|
-
const lightBg = themeConfig?.antd?.lightToken?.colorBgLayout ?? '';
|
|
31
|
-
|
|
32
|
-
// Escape characters that could break out of the enclosing <script> tag or
|
|
33
|
-
// terminate a JS string literal. Used to defuse the js/bad-code-sanitization
|
|
34
|
-
// class of injection for values embedded into the pre-hydration inline script.
|
|
35
|
-
const SCRIPT_ESCAPES = {
|
|
36
|
-
'<': '\\u003C',
|
|
37
|
-
'>': '\\u003E',
|
|
38
|
-
'\b': '\\b',
|
|
39
|
-
'\f': '\\f',
|
|
40
|
-
'\n': '\\n',
|
|
41
|
-
'\r': '\\r',
|
|
42
|
-
'\t': '\\t',
|
|
43
|
-
'\0': '\\0',
|
|
44
|
-
'\u2028': '\\u2028',
|
|
45
|
-
'\u2029': '\\u2029',
|
|
46
|
-
};
|
|
47
|
-
function safeScriptJson(value) {
|
|
48
|
-
return JSON.stringify(value).replace(/[<>\b\f\n\r\t\0\u2028\u2029]/g, (c) => SCRIPT_ESCAPES[c]);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
class LowdefyDocument extends Document {
|
|
52
|
-
render() {
|
|
53
|
-
return (
|
|
54
|
-
<Html className="lowdefy">
|
|
55
|
-
<Head>
|
|
56
|
-
{/* Synchronous script that creates the @layer order declaration and keeps
|
|
57
|
-
it as the first child of <head> via MutationObserver. antd's CSS-in-JS
|
|
58
|
-
uses prependQueue to inject <style> tags at the top of <head>, which
|
|
59
|
-
would otherwise make @layer antd the first (lowest priority) layer.
|
|
60
|
-
MutationObserver fires before paint, so the browser never sees the
|
|
61
|
-
wrong cascade order. */}
|
|
62
|
-
<script
|
|
63
|
-
dangerouslySetInnerHTML={{
|
|
64
|
-
__html: `(function(){var s=document.createElement("style");s.id="__lf-layer-order";s.textContent="@layer theme, base, antd, components, utilities;";document.head.prepend(s);new MutationObserver(function(){if(document.head.firstChild!==s)document.head.prepend(s)}).observe(document.head,{childList:true})})();`,
|
|
65
|
-
}}
|
|
66
|
-
/>
|
|
67
|
-
{/* Synchronous pre-hydration background script — prevents mode-mismatch
|
|
68
|
-
flash on page navigation. Mirrors useDarkMode.js resolution order:
|
|
69
|
-
configDarkMode → localStorage → prefers-color-scheme. Uses the user's
|
|
70
|
-
configured colorBgLayout tokens when present (theme.antd.darkToken and
|
|
71
|
-
theme.antd.lightToken), falling back to #000 in dark and no inline style
|
|
72
|
-
in light so default behavior is unchanged. */}
|
|
73
|
-
<script
|
|
74
|
-
dangerouslySetInnerHTML={{
|
|
75
|
-
__html: `(function(){var c=${safeScriptJson(configColorMode)};var db=${safeScriptJson(
|
|
76
|
-
darkBg
|
|
77
|
-
)};var lb=${safeScriptJson(
|
|
78
|
-
lightBg
|
|
79
|
-
)};var d;if(c==="dark")d=true;else if(c==="light")d=false;else{try{var p=localStorage.getItem("lowdefy_darkMode");if(p==="dark")d=true;else if(p==="light")d=false;else d=window.matchMedia("(prefers-color-scheme:dark)").matches}catch(e){d=window.matchMedia("(prefers-color-scheme:dark)").matches}}var bg=d?db:lb;if(bg)document.documentElement.style.backgroundColor=bg})();`,
|
|
80
|
-
}}
|
|
81
|
-
/>
|
|
82
|
-
<link rel="manifest" href={`${basePath}/manifest.webmanifest`} />
|
|
83
|
-
<link rel="icon" type="image/svg+xml" href={`${basePath}/icon.svg`} />
|
|
84
|
-
<link rel="apple-touch-icon" href={`${basePath}/apple-touch-icon.png`} />
|
|
85
|
-
<link id="tailwind-jit-css" rel="stylesheet" href={`${basePath}/tailwind-jit.css`} />
|
|
86
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
87
|
-
<script
|
|
88
|
-
dangerouslySetInnerHTML={{
|
|
89
|
-
__html: `/* start of Lowdefy append head */</script>${appJson.html.appendHead}<script>/* end of Lowdefy append head */`,
|
|
90
|
-
}}
|
|
91
|
-
/>
|
|
92
|
-
</Head>
|
|
93
|
-
<body>
|
|
94
|
-
<Main />
|
|
95
|
-
<NextScript />
|
|
96
|
-
<script
|
|
97
|
-
dangerouslySetInnerHTML={{
|
|
98
|
-
__html: `/* start of Lowdefy append body */</script>${appJson.html.appendBody}<script>/* end of Lowdefy append body */`,
|
|
99
|
-
}}
|
|
100
|
-
/>
|
|
101
|
-
</body>
|
|
102
|
-
</Html>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export default LowdefyDocument;
|