@lowdefy/server-dev 4.7.3 → 5.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/lib/client/App.js +6 -0
- package/lib/client/BuildErrorPage.js +7 -3
- package/lib/client/BuildingPage.js +4 -2
- package/lib/client/ErrorBar.js +152 -0
- package/lib/client/InstallingPluginsPage.js +2 -0
- package/lib/client/Page.js +12 -1
- package/lib/client/Reload.js +5 -0
- package/lib/client/RestartingPage.js +2 -0
- package/lib/client/utils/usePageConfig.js +6 -0
- package/lib/server/compileCss.js +39 -0
- package/lib/server/jitPageBuilder.js +3 -1
- package/manager/getContext.mjs +2 -0
- package/manager/processes/compileCss.mjs +54 -0
- package/manager/processes/initialBuild.mjs +1 -0
- package/manager/processes/nextBuild.mjs +1 -1
- package/manager/run.mjs +1 -1
- package/manager/utils/createCustomPluginTypesMap.mjs +6 -6
- package/manager/utils/updatePageTailwindCss.mjs +73 -0
- package/manager/watchers/lowdefyBuildWatcher.mjs +3 -1
- package/manager/watchers/nextBuildWatcher.mjs +5 -3
- package/next.config.js +10 -24
- package/package.json +38 -36
- package/package.original.json +38 -36
- package/pages/_app.js +93 -12
- package/pages/_document.js +13 -1
- package/pages/api/page/[pageId].js +4 -0
package/lib/client/App.js
CHANGED
|
@@ -29,6 +29,7 @@ import { getReloadVersion } from './utils/useMutateCache.js';
|
|
|
29
29
|
import useRootConfig from './utils/useRootConfig.js';
|
|
30
30
|
|
|
31
31
|
import actions from '../../build/plugins/actions.js';
|
|
32
|
+
import blockMetas from '../../build/plugins/blockMetas.json';
|
|
32
33
|
import blocks from '../../build/plugins/blocks.js';
|
|
33
34
|
import icons from '../../build/plugins/icons.js';
|
|
34
35
|
import operators from '../../build/plugins/operators/client.js';
|
|
@@ -38,6 +39,10 @@ const App = ({ auth, lowdefy }) => {
|
|
|
38
39
|
const router = useRouter();
|
|
39
40
|
const { data: rootConfig } = useRootConfig(router.basePath);
|
|
40
41
|
|
|
42
|
+
if (rootConfig?.theme) {
|
|
43
|
+
lowdefy.theme = rootConfig.theme;
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
const { redirect, pageId } = setPageId(router, rootConfig);
|
|
42
47
|
if (redirect) {
|
|
43
48
|
router.push(`/${pageId}`);
|
|
@@ -60,6 +65,7 @@ const App = ({ auth, lowdefy }) => {
|
|
|
60
65
|
router={router}
|
|
61
66
|
types={{
|
|
62
67
|
actions,
|
|
68
|
+
blockMetas,
|
|
63
69
|
blocks,
|
|
64
70
|
icons,
|
|
65
71
|
operators,
|
|
@@ -25,7 +25,7 @@ const typeColors = {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
function getTypeColor(type) {
|
|
28
|
-
return typeColors[type] ?? '
|
|
28
|
+
return typeColors[type] ?? 'var(--ant-color-text-secondary)';
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const ErrorItem = ({ type, message, source }) => {
|
|
@@ -50,7 +50,9 @@ const ErrorItem = ({ type, message, source }) => {
|
|
|
50
50
|
{type}
|
|
51
51
|
</span>
|
|
52
52
|
<p style={{ fontSize: 14, margin: '4px 0', fontFamily: 'monospace' }}>{message}</p>
|
|
53
|
-
{source &&
|
|
53
|
+
{source && (
|
|
54
|
+
<p style={{ fontSize: 13, color: 'var(--ant-color-text-tertiary)', margin: 0 }}>{source}</p>
|
|
55
|
+
)}
|
|
54
56
|
</div>
|
|
55
57
|
);
|
|
56
58
|
};
|
|
@@ -67,6 +69,8 @@ const BuildErrorPage = ({ errors, message, source }) => {
|
|
|
67
69
|
flexDirection: 'column',
|
|
68
70
|
alignItems: 'center',
|
|
69
71
|
justifyContent: 'center',
|
|
72
|
+
backgroundColor: 'var(--ant-color-bg-layout)',
|
|
73
|
+
color: 'var(--ant-color-text)',
|
|
70
74
|
fontFamily:
|
|
71
75
|
"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
|
|
72
76
|
padding: '0 24px',
|
|
@@ -79,7 +83,7 @@ const BuildErrorPage = ({ errors, message, source }) => {
|
|
|
79
83
|
{errorList.map((err, i) => (
|
|
80
84
|
<ErrorItem key={i} type={err.type} message={err.message} source={err.source} />
|
|
81
85
|
))}
|
|
82
|
-
<p style={{ fontSize: 13, color: '
|
|
86
|
+
<p style={{ fontSize: 13, color: 'var(--ant-color-text-tertiary)', marginTop: 24 }}>
|
|
83
87
|
Fix the error{errorList.length > 1 ? 's' : ''} in your config and the page will rebuild
|
|
84
88
|
automatically.
|
|
85
89
|
</p>
|
|
@@ -26,6 +26,8 @@ const BuildingPage = () => {
|
|
|
26
26
|
flexDirection: 'column',
|
|
27
27
|
alignItems: 'center',
|
|
28
28
|
justifyContent: 'center',
|
|
29
|
+
backgroundColor: 'var(--ant-color-bg-layout)',
|
|
30
|
+
color: 'var(--ant-color-text)',
|
|
29
31
|
fontFamily:
|
|
30
32
|
"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
|
|
31
33
|
}}
|
|
@@ -45,7 +47,7 @@ const BuildingPage = () => {
|
|
|
45
47
|
left: 0,
|
|
46
48
|
width: '100%',
|
|
47
49
|
height: 3,
|
|
48
|
-
backgroundColor: '
|
|
50
|
+
backgroundColor: 'var(--ant-color-fill-quaternary)',
|
|
49
51
|
overflow: 'hidden',
|
|
50
52
|
}}
|
|
51
53
|
>
|
|
@@ -53,7 +55,7 @@ const BuildingPage = () => {
|
|
|
53
55
|
style={{
|
|
54
56
|
width: '40%',
|
|
55
57
|
height: '100%',
|
|
56
|
-
backgroundColor: '
|
|
58
|
+
backgroundColor: 'var(--ant-color-primary)',
|
|
57
59
|
animation: 'building-progress 1.5s ease-in-out infinite',
|
|
58
60
|
}}
|
|
59
61
|
/>
|
|
@@ -0,0 +1,152 @@
|
|
|
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, { useCallback, useState } from 'react';
|
|
18
|
+
|
|
19
|
+
function getBarStyle(errors) {
|
|
20
|
+
const hasError = errors.some((e) => e.type !== 'ConfigWarning');
|
|
21
|
+
return {
|
|
22
|
+
position: 'fixed',
|
|
23
|
+
bottom: 0,
|
|
24
|
+
left: 0,
|
|
25
|
+
right: 0,
|
|
26
|
+
height: 28,
|
|
27
|
+
backgroundColor: hasError ? '#cf1322' : '#d48806',
|
|
28
|
+
color: '#fff',
|
|
29
|
+
display: 'flex',
|
|
30
|
+
alignItems: 'center',
|
|
31
|
+
justifyContent: 'space-between',
|
|
32
|
+
padding: '0 12px',
|
|
33
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
|
34
|
+
fontSize: 12,
|
|
35
|
+
lineHeight: '14px',
|
|
36
|
+
zIndex: 99999,
|
|
37
|
+
boxShadow: '0 -1px 4px rgba(0,0,0,0.15)',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function formatErrorsForCopy(errors) {
|
|
42
|
+
return errors
|
|
43
|
+
.map((e) => {
|
|
44
|
+
let text = `[${e.type}] ${e.message}`;
|
|
45
|
+
if (e.source) text += `\n Source: ${e.source}`;
|
|
46
|
+
if (e.stack) text += `\n${e.stack}`;
|
|
47
|
+
return text;
|
|
48
|
+
})
|
|
49
|
+
.join('\n\n');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function CopyIcon() {
|
|
53
|
+
return (
|
|
54
|
+
<svg
|
|
55
|
+
width="14"
|
|
56
|
+
height="14"
|
|
57
|
+
viewBox="0 0 24 24"
|
|
58
|
+
fill="none"
|
|
59
|
+
stroke="currentColor"
|
|
60
|
+
strokeWidth="2"
|
|
61
|
+
strokeLinecap="round"
|
|
62
|
+
strokeLinejoin="round"
|
|
63
|
+
>
|
|
64
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
|
65
|
+
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
|
|
66
|
+
</svg>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function CheckIcon() {
|
|
71
|
+
return (
|
|
72
|
+
<svg
|
|
73
|
+
width="14"
|
|
74
|
+
height="14"
|
|
75
|
+
viewBox="0 0 24 24"
|
|
76
|
+
fill="none"
|
|
77
|
+
stroke="currentColor"
|
|
78
|
+
strokeWidth="2"
|
|
79
|
+
strokeLinecap="round"
|
|
80
|
+
strokeLinejoin="round"
|
|
81
|
+
>
|
|
82
|
+
<polyline points="20 6 9 17 4 12" />
|
|
83
|
+
</svg>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const ErrorBar = ({ errors }) => {
|
|
88
|
+
const [copied, setCopied] = useState(false);
|
|
89
|
+
|
|
90
|
+
const handleCopy = useCallback(() => {
|
|
91
|
+
const text = formatErrorsForCopy(errors);
|
|
92
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
93
|
+
setCopied(true);
|
|
94
|
+
setTimeout(() => setCopied(false), 1500);
|
|
95
|
+
});
|
|
96
|
+
}, [errors]);
|
|
97
|
+
|
|
98
|
+
if (!errors || errors.length === 0) return null;
|
|
99
|
+
|
|
100
|
+
const latest = errors[errors.length - 1];
|
|
101
|
+
const count = errors.length;
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div style={getBarStyle(errors)}>
|
|
105
|
+
<div
|
|
106
|
+
style={{
|
|
107
|
+
overflow: 'hidden',
|
|
108
|
+
textOverflow: 'ellipsis',
|
|
109
|
+
whiteSpace: 'nowrap',
|
|
110
|
+
flex: 1,
|
|
111
|
+
minWidth: 0,
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
<span style={{ opacity: 0.8 }}>{latest.type}: </span>
|
|
115
|
+
<span>{latest.message}</span>
|
|
116
|
+
{latest.source && <span style={{ opacity: 0.7, marginLeft: 8 }}>{latest.source}</span>}
|
|
117
|
+
</div>
|
|
118
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0, marginLeft: 12 }}>
|
|
119
|
+
{count > 1 && (
|
|
120
|
+
<span
|
|
121
|
+
style={{
|
|
122
|
+
backgroundColor: 'rgba(255,255,255,0.25)',
|
|
123
|
+
borderRadius: 8,
|
|
124
|
+
padding: '1px 7px',
|
|
125
|
+
fontSize: 11,
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
{count}
|
|
129
|
+
</span>
|
|
130
|
+
)}
|
|
131
|
+
<button
|
|
132
|
+
onClick={handleCopy}
|
|
133
|
+
title={copied ? 'Copied!' : 'Copy all errors'}
|
|
134
|
+
style={{
|
|
135
|
+
background: 'none',
|
|
136
|
+
border: 'none',
|
|
137
|
+
color: '#fff',
|
|
138
|
+
cursor: 'pointer',
|
|
139
|
+
padding: 2,
|
|
140
|
+
display: 'flex',
|
|
141
|
+
alignItems: 'center',
|
|
142
|
+
opacity: copied ? 1 : 0.7,
|
|
143
|
+
}}
|
|
144
|
+
>
|
|
145
|
+
{copied ? <CheckIcon /> : <CopyIcon />}
|
|
146
|
+
</button>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export default ErrorBar;
|
|
@@ -26,6 +26,8 @@ const InstallingPluginsPage = ({ packages }) => {
|
|
|
26
26
|
flexDirection: 'column',
|
|
27
27
|
alignItems: 'center',
|
|
28
28
|
justifyContent: 'center',
|
|
29
|
+
backgroundColor: 'var(--ant-color-bg-layout)',
|
|
30
|
+
color: 'var(--ant-color-text)',
|
|
29
31
|
fontFamily:
|
|
30
32
|
"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
|
|
31
33
|
}}
|
package/lib/client/Page.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import React from 'react';
|
|
17
|
+
import React, { useEffect, useRef } from 'react';
|
|
18
18
|
import { GenIcon } from 'react-icons/lib';
|
|
19
19
|
import Client from '@lowdefy/client';
|
|
20
20
|
|
|
@@ -36,6 +36,17 @@ const Page = ({
|
|
|
36
36
|
}) => {
|
|
37
37
|
const { data: pageConfig } = usePageConfig(pageId, router.basePath);
|
|
38
38
|
|
|
39
|
+
// Push build warnings to ErrorBar via runtime error callback
|
|
40
|
+
const pushedWarningsRef = useRef(null);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (pageConfig?._warnings && pageConfig._warnings !== pushedWarningsRef.current) {
|
|
43
|
+
pushedWarningsRef.current = pageConfig._warnings;
|
|
44
|
+
for (const warning of pageConfig._warnings) {
|
|
45
|
+
lowdefy._runtimeErrorCallback?.(warning);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}, [pageConfig?._warnings, lowdefy]);
|
|
49
|
+
|
|
39
50
|
if (!pageConfig) {
|
|
40
51
|
router.replace(`/404`);
|
|
41
52
|
return '';
|
package/lib/client/Reload.js
CHANGED
|
@@ -34,6 +34,11 @@ const Reload = ({ children, basePath, lowdefy }) => {
|
|
|
34
34
|
if (lowdefy._internal?.initialised) {
|
|
35
35
|
lowdefy._internal.initialised = false;
|
|
36
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
|
+
}
|
|
37
42
|
setReset(true);
|
|
38
43
|
console.log('Reloaded config.');
|
|
39
44
|
}, 600);
|
|
@@ -26,6 +26,8 @@ const RestartingPage = () => {
|
|
|
26
26
|
flexDirection: 'column',
|
|
27
27
|
alignItems: 'center',
|
|
28
28
|
justifyContent: 'center',
|
|
29
|
+
backgroundColor: 'var(--ant-color-bg-layout)',
|
|
30
|
+
color: 'var(--ant-color-text)',
|
|
29
31
|
fontFamily:
|
|
30
32
|
"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
|
|
31
33
|
}}
|
|
@@ -71,6 +71,12 @@ async function fetchPageConfig(url) {
|
|
|
71
71
|
data._jsEntries = jsEntries;
|
|
72
72
|
data._dynamicIcons = dynamicIcons;
|
|
73
73
|
|
|
74
|
+
// Bust CSS cache so the browser picks up newly compiled Tailwind classes
|
|
75
|
+
const cssLink = document.getElementById('tailwind-jit-css');
|
|
76
|
+
if (cssLink) {
|
|
77
|
+
cssLink.href = `${basePath}/tailwind-jit.css?v=${Date.now()}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
74
80
|
return data;
|
|
75
81
|
}
|
|
76
82
|
|
|
@@ -0,0 +1,39 @@
|
|
|
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;
|
|
@@ -19,6 +19,7 @@ import path from 'path';
|
|
|
19
19
|
import { serializer } from '@lowdefy/helpers';
|
|
20
20
|
import { buildPageJit, createContext, makeId } from '@lowdefy/build/dev';
|
|
21
21
|
|
|
22
|
+
import compileCss from './compileCss.js';
|
|
22
23
|
import createLogger from './log/createLogger.js';
|
|
23
24
|
import PageCache from './pageCache.mjs';
|
|
24
25
|
|
|
@@ -174,11 +175,12 @@ async function buildPageIfNeeded({ pageId, buildDirectory, configDirectory }) {
|
|
|
174
175
|
return result;
|
|
175
176
|
}
|
|
176
177
|
pageCache.markCompiled(pageId);
|
|
178
|
+
await compileCss(buildDirectory);
|
|
177
179
|
jitLogger.info(
|
|
178
180
|
{ spin: 'succeed', color: 'white' },
|
|
179
181
|
`Built page "${pageId}" in ${formatDuration(Date.now() - startTime)}.`
|
|
180
182
|
);
|
|
181
|
-
return true;
|
|
183
|
+
return { built: true, warnings: result?._warnings };
|
|
182
184
|
} finally {
|
|
183
185
|
pageCache.releaseBuildLock(pageId);
|
|
184
186
|
}
|
package/manager/getContext.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import { hideBin } from 'yargs/helpers';
|
|
|
22
22
|
import pino from 'pino';
|
|
23
23
|
import { createNodeLogger } from '@lowdefy/logger/node';
|
|
24
24
|
import checkMockUserWarning from './processes/checkMockUserWarning.mjs';
|
|
25
|
+
import compileCss from './processes/compileCss.mjs';
|
|
25
26
|
import initialBuild from './processes/initialBuild.mjs';
|
|
26
27
|
import installPlugins from './processes/installPlugins.mjs';
|
|
27
28
|
import lowdefyBuild from './processes/lowdefyBuild.mjs';
|
|
@@ -86,6 +87,7 @@ async function getContext() {
|
|
|
86
87
|
}
|
|
87
88
|
};
|
|
88
89
|
|
|
90
|
+
context.compileCss = compileCss(context);
|
|
89
91
|
context.nextBuild = nextBuild(context);
|
|
90
92
|
context.readDotEnv = readDotEnv(context);
|
|
91
93
|
context.reloadClients = reloadClients(context);
|
|
@@ -0,0 +1,54 @@
|
|
|
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;
|
|
@@ -30,7 +30,7 @@ function nextBuild({ bin, logger }) {
|
|
|
30
30
|
try {
|
|
31
31
|
await spawnProcess({
|
|
32
32
|
command: 'node',
|
|
33
|
-
args: [bin.next, 'build'],
|
|
33
|
+
args: ['--max-old-space-size=4096', bin.next, 'build'],
|
|
34
34
|
stdOutLineHandler: (line) => logger.debug(line),
|
|
35
35
|
stdErrLineHandler: (line) => {
|
|
36
36
|
logger.debug(line);
|
package/manager/run.mjs
CHANGED
|
@@ -57,7 +57,7 @@ The run script does the following:
|
|
|
57
57
|
- <build-dir>/config.json changes, rebuild and restart server.
|
|
58
58
|
|
|
59
59
|
If user styles change:
|
|
60
|
-
- <public-dir>/styles.
|
|
60
|
+
- <public-dir>/styles.css changes, rebuild and restart server.
|
|
61
61
|
|
|
62
62
|
If new plugin type in an existing plugin package is used:
|
|
63
63
|
- <build-dir>/plugins/** changes, rebuild next and restart server.
|
|
@@ -15,12 +15,15 @@
|
|
|
15
15
|
limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
18
19
|
import path from 'path';
|
|
19
20
|
import { get } from '@lowdefy/helpers';
|
|
20
21
|
import { readFile } from '@lowdefy/node-utils';
|
|
21
22
|
import { createPluginTypesMap } from '@lowdefy/build';
|
|
22
23
|
import YAML from 'yaml';
|
|
23
24
|
|
|
25
|
+
const require = createRequire(import.meta.url);
|
|
26
|
+
|
|
24
27
|
async function getPluginDefinitions({ directories }) {
|
|
25
28
|
let lowdefyYaml = await readFile(path.join(directories.config, 'lowdefy.yaml'));
|
|
26
29
|
if (!lowdefyYaml) {
|
|
@@ -42,6 +45,7 @@ async function createCustomPluginTypesMap({ directories, logger }) {
|
|
|
42
45
|
events: {},
|
|
43
46
|
providers: {},
|
|
44
47
|
},
|
|
48
|
+
blockMetas: {},
|
|
45
49
|
blocks: {},
|
|
46
50
|
connections: {},
|
|
47
51
|
icons: {},
|
|
@@ -50,10 +54,6 @@ async function createCustomPluginTypesMap({ directories, logger }) {
|
|
|
50
54
|
server: {},
|
|
51
55
|
},
|
|
52
56
|
requests: {},
|
|
53
|
-
styles: {
|
|
54
|
-
packages: {},
|
|
55
|
-
blocks: {},
|
|
56
|
-
},
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
const pluginDefinitions = await getPluginDefinitions({ directories });
|
|
@@ -61,7 +61,7 @@ async function createCustomPluginTypesMap({ directories, logger }) {
|
|
|
61
61
|
for (const plugin of pluginDefinitions) {
|
|
62
62
|
let types;
|
|
63
63
|
try {
|
|
64
|
-
types = (
|
|
64
|
+
types = require(`${plugin.name}/types`);
|
|
65
65
|
} catch (e) {
|
|
66
66
|
logger.error(`Failed to import plugin "${plugin.name}".`);
|
|
67
67
|
logger.debug(e);
|
|
@@ -69,7 +69,7 @@ async function createCustomPluginTypesMap({ directories, logger }) {
|
|
|
69
69
|
throw new Error(`Failed to import plugin "${plugin.name}".`);
|
|
70
70
|
}
|
|
71
71
|
createPluginTypesMap({
|
|
72
|
-
packageTypes: types,
|
|
72
|
+
packageTypes: types.default ?? types,
|
|
73
73
|
typesMap: customTypesMap,
|
|
74
74
|
packageName: plugin.name,
|
|
75
75
|
version: plugin.version,
|
|
@@ -0,0 +1,73 @@
|
|
|
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 YAML from 'yaml';
|
|
20
|
+
|
|
21
|
+
function collectStrings(obj, strings) {
|
|
22
|
+
if (!obj || typeof obj !== 'object') {
|
|
23
|
+
if (typeof obj === 'string') strings.push(obj);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (Array.isArray(obj)) {
|
|
27
|
+
for (const item of obj) collectStrings(item, strings);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
for (const value of Object.values(obj)) {
|
|
31
|
+
collectStrings(value, strings);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function updatePageTailwindCss({ changedFiles, context }) {
|
|
36
|
+
let contentWritten = false;
|
|
37
|
+
|
|
38
|
+
changedFiles
|
|
39
|
+
.filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'))
|
|
40
|
+
.forEach((f) => {
|
|
41
|
+
try {
|
|
42
|
+
const content = fs.readFileSync(
|
|
43
|
+
path.join(context.directories.config, f),
|
|
44
|
+
'utf8'
|
|
45
|
+
);
|
|
46
|
+
const doc = YAML.parse(content);
|
|
47
|
+
|
|
48
|
+
const pageId = path.basename(f, path.extname(f));
|
|
49
|
+
const allStrings = [];
|
|
50
|
+
collectStrings(doc, allStrings);
|
|
51
|
+
if (allStrings.length > 0) {
|
|
52
|
+
const contentPath = path.join(
|
|
53
|
+
context.directories.server,
|
|
54
|
+
'lowdefy-build',
|
|
55
|
+
'tailwind',
|
|
56
|
+
`${pageId}.html`
|
|
57
|
+
);
|
|
58
|
+
fs.mkdirSync(path.dirname(contentPath), { recursive: true });
|
|
59
|
+
fs.writeFileSync(contentPath, allStrings.join('\n'));
|
|
60
|
+
contentWritten = true;
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
// YAML parse error — skip
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (contentWritten) {
|
|
68
|
+
await context.compileCss();
|
|
69
|
+
}
|
|
70
|
+
return contentWritten;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default updatePageTailwindCss;
|
|
@@ -19,6 +19,7 @@ import path from 'path';
|
|
|
19
19
|
import getLowdefyVersion from '../utils/getLowdefyVersion.mjs';
|
|
20
20
|
import loadSkeletonSourceFiles from '../utils/loadSkeletonSourceFiles.mjs';
|
|
21
21
|
import setupWatcher from '../utils/setupWatcher.mjs';
|
|
22
|
+
import updatePageTailwindCss from '../utils/updatePageTailwindCss.mjs';
|
|
22
23
|
|
|
23
24
|
function lowdefyBuildWatcher(context) {
|
|
24
25
|
const fixRelativePathConfigDir = (item) =>
|
|
@@ -50,10 +51,11 @@ function lowdefyBuildWatcher(context) {
|
|
|
50
51
|
|
|
51
52
|
if (isSkeletonChange) {
|
|
52
53
|
await context.lowdefyBuild();
|
|
54
|
+
await context.compileCss();
|
|
53
55
|
} else {
|
|
54
|
-
// Page-only changes: write signal file so the server invalidates its page cache
|
|
55
56
|
const invalidatePath = path.join(context.directories.build, 'invalidatePages');
|
|
56
57
|
fs.writeFileSync(invalidatePath, String(Date.now()));
|
|
58
|
+
await updatePageTailwindCss({ changedFiles, context });
|
|
57
59
|
context.logger.info('Page files changed, invalidated all pages.');
|
|
58
60
|
}
|
|
59
61
|
} catch (error) {
|
|
@@ -38,8 +38,9 @@ const trackedFiles = [
|
|
|
38
38
|
'build/plugins/operators/clientJsMap.js',
|
|
39
39
|
'build/plugins/operators/server.js',
|
|
40
40
|
'build/plugins/operators/serverJsMap.js',
|
|
41
|
-
'build/plugins/styles.
|
|
42
|
-
'
|
|
41
|
+
'build/plugins/styles.css',
|
|
42
|
+
'build/globals.css',
|
|
43
|
+
'public/styles.css',
|
|
43
44
|
'package.json',
|
|
44
45
|
];
|
|
45
46
|
|
|
@@ -94,7 +95,7 @@ async function nextBuildWatcher(context) {
|
|
|
94
95
|
if (install) {
|
|
95
96
|
context.logger.warn('Plugin dependencies have changed and will be reinstalled.');
|
|
96
97
|
await context.installPlugins();
|
|
97
|
-
// Rebuild Lowdefy artifacts (blocks.js, icons.js, styles.
|
|
98
|
+
// Rebuild Lowdefy artifacts (blocks.js, icons.js, styles.css, etc.)
|
|
98
99
|
// so newly installed packages are included in the Next.js bundle.
|
|
99
100
|
await context.lowdefyBuild();
|
|
100
101
|
// Re-hash all tracked files to avoid detecting our own build output
|
|
@@ -106,6 +107,7 @@ async function nextBuildWatcher(context) {
|
|
|
106
107
|
);
|
|
107
108
|
}
|
|
108
109
|
await context.nextBuild();
|
|
110
|
+
await context.compileCss();
|
|
109
111
|
context.restartServer();
|
|
110
112
|
};
|
|
111
113
|
|
package/next.config.js
CHANGED
|
@@ -1,32 +1,18 @@
|
|
|
1
|
-
const withLess = require('next-with-less');
|
|
2
1
|
const lowdefyConfig = require('./build/config.json');
|
|
2
|
+
const blockPackages = require('./build/blockPackages.json');
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// Transpile @lowdefy/client plus all block plugin packages that may
|
|
5
|
+
// contain CSS imports (e.g., AG Grid themes, loaders, markdown).
|
|
6
|
+
// Built dynamically so custom user plugins are included automatically.
|
|
7
|
+
const transpilePackages = ['@lowdefy/client', ...blockPackages];
|
|
8
|
+
|
|
9
|
+
const nextConfig = {
|
|
5
10
|
basePath: lowdefyConfig.basePath,
|
|
6
11
|
// reactStrictMode: true,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
config.resolve.fallback = {
|
|
10
|
-
assert: false,
|
|
11
|
-
buffer: false,
|
|
12
|
-
crypto: false,
|
|
13
|
-
events: false,
|
|
14
|
-
fs: false,
|
|
15
|
-
path: false,
|
|
16
|
-
process: require.resolve('process/browser'),
|
|
17
|
-
util: false,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
return config;
|
|
21
|
-
},
|
|
12
|
+
turbopack: {},
|
|
13
|
+
transpilePackages,
|
|
22
14
|
compress: false,
|
|
23
|
-
outputFileTracing: false,
|
|
24
15
|
poweredByHeader: false,
|
|
25
|
-
|
|
26
|
-
optimizeFonts: false,
|
|
27
|
-
eslint: {
|
|
28
|
-
ignoreDuringBuilds: true,
|
|
29
|
-
},
|
|
30
|
-
});
|
|
16
|
+
};
|
|
31
17
|
|
|
32
18
|
module.exports = nextConfig;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/server-dev",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -36,55 +36,57 @@
|
|
|
36
36
|
".npmrc"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@lowdefy/actions-core": "
|
|
40
|
-
"@lowdefy/api": "
|
|
41
|
-
"@lowdefy/block-utils": "
|
|
42
|
-
"@lowdefy/blocks-aggrid": "
|
|
43
|
-
"@lowdefy/blocks-antd": "
|
|
44
|
-
"@lowdefy/blocks-basic": "
|
|
45
|
-
"@lowdefy/blocks-
|
|
46
|
-
"@lowdefy/blocks-
|
|
47
|
-
"@lowdefy/blocks-
|
|
48
|
-
"@lowdefy/
|
|
49
|
-
"@lowdefy/
|
|
50
|
-
"@lowdefy/
|
|
51
|
-
"@lowdefy/
|
|
52
|
-
"@lowdefy/
|
|
53
|
-
"@lowdefy/
|
|
54
|
-
"@lowdefy/
|
|
55
|
-
"@lowdefy/
|
|
56
|
-
"@lowdefy/
|
|
57
|
-
"@lowdefy/
|
|
58
|
-
"@lowdefy/operators-
|
|
59
|
-
"@lowdefy/operators-diff": "
|
|
60
|
-
"@lowdefy/operators-js": "
|
|
61
|
-
"@lowdefy/operators-
|
|
62
|
-
"@lowdefy/operators-
|
|
63
|
-
"@lowdefy/operators-
|
|
64
|
-
"@lowdefy/operators-
|
|
65
|
-
"@lowdefy/
|
|
66
|
-
"@
|
|
39
|
+
"@lowdefy/actions-core": "5.0.0",
|
|
40
|
+
"@lowdefy/api": "5.0.0",
|
|
41
|
+
"@lowdefy/block-utils": "5.0.0",
|
|
42
|
+
"@lowdefy/blocks-aggrid": "5.0.0",
|
|
43
|
+
"@lowdefy/blocks-antd": "5.0.0",
|
|
44
|
+
"@lowdefy/blocks-basic": "5.0.0",
|
|
45
|
+
"@lowdefy/blocks-echarts": "5.0.0",
|
|
46
|
+
"@lowdefy/blocks-loaders": "5.0.0",
|
|
47
|
+
"@lowdefy/blocks-markdown": "5.0.0",
|
|
48
|
+
"@lowdefy/build": "5.0.0",
|
|
49
|
+
"@lowdefy/client": "5.0.0",
|
|
50
|
+
"@lowdefy/connection-axios-http": "5.0.0",
|
|
51
|
+
"@lowdefy/engine": "5.0.0",
|
|
52
|
+
"@lowdefy/errors": "5.0.0",
|
|
53
|
+
"@lowdefy/helpers": "5.0.0",
|
|
54
|
+
"@lowdefy/layout": "5.0.0",
|
|
55
|
+
"@lowdefy/logger": "5.0.0",
|
|
56
|
+
"@lowdefy/node-utils": "5.0.0",
|
|
57
|
+
"@lowdefy/operators-change-case": "5.0.0",
|
|
58
|
+
"@lowdefy/operators-dayjs": "5.0.0",
|
|
59
|
+
"@lowdefy/operators-diff": "5.0.0",
|
|
60
|
+
"@lowdefy/operators-js": "5.0.0",
|
|
61
|
+
"@lowdefy/operators-mql": "5.0.0",
|
|
62
|
+
"@lowdefy/operators-nunjucks": "5.0.0",
|
|
63
|
+
"@lowdefy/operators-uuid": "5.0.0",
|
|
64
|
+
"@lowdefy/operators-yaml": "5.0.0",
|
|
65
|
+
"@lowdefy/plugin-next-auth": "5.0.0",
|
|
66
|
+
"@ant-design/cssinjs": "2.1.2",
|
|
67
|
+
"antd": "6.3.1",
|
|
68
|
+
"dayjs": "1.11.19",
|
|
67
69
|
"chokidar": "3.5.3",
|
|
68
70
|
"dotenv": "16.3.1",
|
|
69
|
-
"
|
|
70
|
-
"next
|
|
71
|
+
"@tailwindcss/postcss": "4.2.1",
|
|
72
|
+
"next": "16.1.6",
|
|
73
|
+
"postcss": "8.5.6",
|
|
74
|
+
"tailwindcss": "4.2.1",
|
|
75
|
+
"next-auth": "4.24.10",
|
|
71
76
|
"opener": "1.5.2",
|
|
72
77
|
"pino": "8.16.2",
|
|
73
78
|
"process": "0.11.10",
|
|
74
79
|
"react": "18.2.0",
|
|
75
80
|
"react-dom": "18.2.0",
|
|
76
|
-
"react-icons": "
|
|
81
|
+
"react-icons": "5.6.0",
|
|
77
82
|
"swr": "2.2.4",
|
|
78
83
|
"uuid": "13.0.0",
|
|
79
84
|
"yaml": "2.3.4",
|
|
80
85
|
"yargs": "17.7.2"
|
|
81
86
|
},
|
|
82
87
|
"devDependencies": {
|
|
83
|
-
"@next/eslint-plugin-next": "
|
|
88
|
+
"@next/eslint-plugin-next": "16.1.6",
|
|
84
89
|
"jest": "28.1.3",
|
|
85
|
-
"less": "4.1.3",
|
|
86
|
-
"less-loader": "11.1.3",
|
|
87
|
-
"next-with-less": "3.0.1",
|
|
88
90
|
"webpack": "5.94.0"
|
|
89
91
|
},
|
|
90
92
|
"engines": {
|
package/package.original.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/server-dev",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -44,55 +44,57 @@
|
|
|
44
44
|
"prepublishOnly": "pnpm build"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@lowdefy/actions-core": "
|
|
48
|
-
"@lowdefy/api": "
|
|
49
|
-
"@lowdefy/block-utils": "
|
|
50
|
-
"@lowdefy/blocks-aggrid": "
|
|
51
|
-
"@lowdefy/blocks-antd": "
|
|
52
|
-
"@lowdefy/blocks-basic": "
|
|
53
|
-
"@lowdefy/blocks-
|
|
54
|
-
"@lowdefy/blocks-
|
|
55
|
-
"@lowdefy/blocks-
|
|
56
|
-
"@lowdefy/
|
|
57
|
-
"@lowdefy/
|
|
58
|
-
"@lowdefy/
|
|
59
|
-
"@lowdefy/
|
|
60
|
-
"@lowdefy/
|
|
61
|
-
"@lowdefy/
|
|
62
|
-
"@lowdefy/
|
|
63
|
-
"@lowdefy/
|
|
64
|
-
"@lowdefy/
|
|
65
|
-
"@lowdefy/
|
|
66
|
-
"@lowdefy/operators-
|
|
67
|
-
"@lowdefy/operators-diff": "
|
|
68
|
-
"@lowdefy/operators-js": "
|
|
69
|
-
"@lowdefy/operators-
|
|
70
|
-
"@lowdefy/operators-
|
|
71
|
-
"@lowdefy/operators-
|
|
72
|
-
"@lowdefy/operators-
|
|
73
|
-
"@lowdefy/
|
|
74
|
-
"@
|
|
47
|
+
"@lowdefy/actions-core": "5.0.0",
|
|
48
|
+
"@lowdefy/api": "5.0.0",
|
|
49
|
+
"@lowdefy/block-utils": "5.0.0",
|
|
50
|
+
"@lowdefy/blocks-aggrid": "5.0.0",
|
|
51
|
+
"@lowdefy/blocks-antd": "5.0.0",
|
|
52
|
+
"@lowdefy/blocks-basic": "5.0.0",
|
|
53
|
+
"@lowdefy/blocks-echarts": "5.0.0",
|
|
54
|
+
"@lowdefy/blocks-loaders": "5.0.0",
|
|
55
|
+
"@lowdefy/blocks-markdown": "5.0.0",
|
|
56
|
+
"@lowdefy/build": "5.0.0",
|
|
57
|
+
"@lowdefy/client": "5.0.0",
|
|
58
|
+
"@lowdefy/connection-axios-http": "5.0.0",
|
|
59
|
+
"@lowdefy/engine": "5.0.0",
|
|
60
|
+
"@lowdefy/errors": "5.0.0",
|
|
61
|
+
"@lowdefy/helpers": "5.0.0",
|
|
62
|
+
"@lowdefy/layout": "5.0.0",
|
|
63
|
+
"@lowdefy/logger": "5.0.0",
|
|
64
|
+
"@lowdefy/node-utils": "5.0.0",
|
|
65
|
+
"@lowdefy/operators-change-case": "5.0.0",
|
|
66
|
+
"@lowdefy/operators-dayjs": "5.0.0",
|
|
67
|
+
"@lowdefy/operators-diff": "5.0.0",
|
|
68
|
+
"@lowdefy/operators-js": "5.0.0",
|
|
69
|
+
"@lowdefy/operators-mql": "5.0.0",
|
|
70
|
+
"@lowdefy/operators-nunjucks": "5.0.0",
|
|
71
|
+
"@lowdefy/operators-uuid": "5.0.0",
|
|
72
|
+
"@lowdefy/operators-yaml": "5.0.0",
|
|
73
|
+
"@lowdefy/plugin-next-auth": "5.0.0",
|
|
74
|
+
"@ant-design/cssinjs": "2.1.2",
|
|
75
|
+
"antd": "6.3.1",
|
|
76
|
+
"dayjs": "1.11.19",
|
|
75
77
|
"chokidar": "3.5.3",
|
|
76
78
|
"dotenv": "16.3.1",
|
|
77
|
-
"
|
|
78
|
-
"next
|
|
79
|
+
"@tailwindcss/postcss": "4.2.1",
|
|
80
|
+
"next": "16.1.6",
|
|
81
|
+
"postcss": "8.5.6",
|
|
82
|
+
"tailwindcss": "4.2.1",
|
|
83
|
+
"next-auth": "4.24.10",
|
|
79
84
|
"opener": "1.5.2",
|
|
80
85
|
"pino": "8.16.2",
|
|
81
86
|
"process": "0.11.10",
|
|
82
87
|
"react": "18.2.0",
|
|
83
88
|
"react-dom": "18.2.0",
|
|
84
|
-
"react-icons": "
|
|
89
|
+
"react-icons": "5.6.0",
|
|
85
90
|
"swr": "2.2.4",
|
|
86
91
|
"uuid": "13.0.0",
|
|
87
92
|
"yaml": "2.3.4",
|
|
88
93
|
"yargs": "17.7.2"
|
|
89
94
|
},
|
|
90
95
|
"devDependencies": {
|
|
91
|
-
"@next/eslint-plugin-next": "
|
|
96
|
+
"@next/eslint-plugin-next": "16.1.6",
|
|
92
97
|
"jest": "28.1.3",
|
|
93
|
-
"less": "4.1.3",
|
|
94
|
-
"less-loader": "11.1.3",
|
|
95
|
-
"next-with-less": "3.0.1",
|
|
96
98
|
"webpack": "5.94.0"
|
|
97
99
|
},
|
|
98
100
|
"engines": {
|
package/pages/_app.js
CHANGED
|
@@ -14,18 +14,74 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
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
|
+
import React, { Suspense, useCallback, useEffect, useRef, useState } from 'react';
|
|
18
23
|
import dynamic from 'next/dynamic';
|
|
24
|
+
import { useRouter } from 'next/router';
|
|
25
|
+
import useSWR from 'swr';
|
|
19
26
|
|
|
20
27
|
import { ErrorBoundary } from '@lowdefy/block-utils';
|
|
28
|
+
import { useDarkMode } from '@lowdefy/client';
|
|
29
|
+
import { StyleProvider } from '@ant-design/cssinjs';
|
|
30
|
+
import { App as AntdApp, ConfigProvider, theme as antdTheme } from 'antd';
|
|
21
31
|
|
|
22
32
|
import Auth from '../lib/client/auth/Auth.js';
|
|
33
|
+
import ErrorBar from '../lib/client/ErrorBar.js';
|
|
34
|
+
import request from '../lib/client/utils/request.js';
|
|
35
|
+
|
|
36
|
+
// Full Tailwind CSS — also loaded via <link href="tailwind-jit.css"> in _document.js
|
|
37
|
+
// for hot-reloading. The Turbopack chunk provides layer ordering guarantee on initial load.
|
|
38
|
+
import '../build/globals.css';
|
|
23
39
|
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
function ThemeTokenResolver({ lowdefyRef, children }) {
|
|
41
|
+
const { token } = antdTheme.useToken();
|
|
42
|
+
if (!lowdefyRef.current.theme) {
|
|
43
|
+
lowdefyRef.current.theme = {};
|
|
44
|
+
}
|
|
45
|
+
lowdefyRef.current.theme._resolvedAntdToken = token;
|
|
46
|
+
return children;
|
|
47
|
+
}
|
|
26
48
|
|
|
27
49
|
function App({ Component }) {
|
|
50
|
+
const router = useRouter();
|
|
28
51
|
const lowdefyRef = useRef({});
|
|
52
|
+
const [runtimeErrors, setRuntimeErrors] = useState([]);
|
|
53
|
+
// Subscribe to rootConfig SWR cache — deduplicates with inner App.js fetch.
|
|
54
|
+
// Without suspense so _app.js doesn't suspend — just re-renders when data arrives.
|
|
55
|
+
const { data: rootConfig } = useSWR(`${router.basePath}/api/root`, (url) => request({ url }));
|
|
56
|
+
if (rootConfig?.theme) {
|
|
57
|
+
lowdefyRef.current.theme = rootConfig.theme;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const algorithm = useDarkMode({
|
|
61
|
+
baseAlgorithm: lowdefyRef.current.theme?.antd?.algorithm,
|
|
62
|
+
configDarkMode: lowdefyRef.current.theme?.darkMode,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Runtime error callback — pushes errors to state for ErrorBar display.
|
|
66
|
+
// Accepts Error objects (with .name) or plain objects (with .type) from build warnings.
|
|
67
|
+
lowdefyRef.current._runtimeErrorCallback = useCallback((error) => {
|
|
68
|
+
setRuntimeErrors((prev) => [
|
|
69
|
+
...prev,
|
|
70
|
+
{
|
|
71
|
+
type: error.type ?? error.name,
|
|
72
|
+
message: error.message,
|
|
73
|
+
source: error.source,
|
|
74
|
+
stack: error.stack,
|
|
75
|
+
},
|
|
76
|
+
]);
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
// Clear runtime errors on route change
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const clearErrors = () => setRuntimeErrors([]);
|
|
82
|
+
router.events.on('routeChangeStart', clearErrors);
|
|
83
|
+
return () => router.events.off('routeChangeStart', clearErrors);
|
|
84
|
+
}, [router.events]);
|
|
29
85
|
|
|
30
86
|
const handleError = useCallback((error) => {
|
|
31
87
|
if (lowdefyRef.current?._internal?.handleError) {
|
|
@@ -36,15 +92,40 @@ function App({ Component }) {
|
|
|
36
92
|
}, []);
|
|
37
93
|
|
|
38
94
|
return (
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
95
|
+
<StyleProvider layer>
|
|
96
|
+
<ConfigProvider
|
|
97
|
+
theme={{
|
|
98
|
+
...lowdefyRef.current.theme?.antd,
|
|
99
|
+
cssVar: { key: 'lowdefy' },
|
|
100
|
+
hashed: false,
|
|
101
|
+
algorithm,
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
<AntdApp>
|
|
105
|
+
<ThemeTokenResolver lowdefyRef={lowdefyRef}>
|
|
106
|
+
<ErrorBoundary fullPage onError={handleError}>
|
|
107
|
+
<Suspense
|
|
108
|
+
fallback={
|
|
109
|
+
<div
|
|
110
|
+
style={{
|
|
111
|
+
minHeight: '100vh',
|
|
112
|
+
background: 'var(--ant-color-bg-layout)',
|
|
113
|
+
}}
|
|
114
|
+
/>
|
|
115
|
+
}
|
|
116
|
+
>
|
|
117
|
+
<Auth>
|
|
118
|
+
{(auth) => {
|
|
119
|
+
return <Component auth={auth} lowdefy={lowdefyRef.current} />;
|
|
120
|
+
}}
|
|
121
|
+
</Auth>
|
|
122
|
+
</Suspense>
|
|
123
|
+
</ErrorBoundary>
|
|
124
|
+
<ErrorBar errors={runtimeErrors} />
|
|
125
|
+
</ThemeTokenResolver>
|
|
126
|
+
</AntdApp>
|
|
127
|
+
</ConfigProvider>
|
|
128
|
+
</StyleProvider>
|
|
48
129
|
);
|
|
49
130
|
}
|
|
50
131
|
|
package/pages/_document.js
CHANGED
|
@@ -25,11 +25,23 @@ const basePath = lowdefyConfig.basePath ?? '';
|
|
|
25
25
|
class LowdefyDocument extends Document {
|
|
26
26
|
render() {
|
|
27
27
|
return (
|
|
28
|
-
<Html>
|
|
28
|
+
<Html className="lowdefy">
|
|
29
29
|
<Head>
|
|
30
|
+
{/* Synchronous script that creates the @layer order declaration and keeps
|
|
31
|
+
it as the first child of <head> via MutationObserver. antd's CSS-in-JS
|
|
32
|
+
uses prependQueue to inject <style> tags at the top of <head>, which
|
|
33
|
+
would otherwise make @layer antd the first (lowest priority) layer.
|
|
34
|
+
MutationObserver fires before paint, so the browser never sees the
|
|
35
|
+
wrong cascade order. */}
|
|
36
|
+
<script
|
|
37
|
+
dangerouslySetInnerHTML={{
|
|
38
|
+
__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})})();`,
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
30
41
|
<link rel="manifest" href={`${basePath}/manifest.webmanifest`} />
|
|
31
42
|
<link rel="icon" type="image/svg+xml" href={`${basePath}/icon.svg`} />
|
|
32
43
|
<link rel="apple-touch-icon" href={`${basePath}/apple-touch-icon.png`} />
|
|
44
|
+
<link id="tailwind-jit-css" rel="stylesheet" href={`${basePath}/tailwind-jit.css`} />
|
|
33
45
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
34
46
|
<script
|
|
35
47
|
dangerouslySetInnerHTML={{
|
|
@@ -39,6 +39,7 @@ async function handler({ context, req, res }) {
|
|
|
39
39
|
type: err.name ?? 'Error',
|
|
40
40
|
message: err.message,
|
|
41
41
|
source: err.source ?? null,
|
|
42
|
+
stack: err.stack ?? null,
|
|
42
43
|
});
|
|
43
44
|
}
|
|
44
45
|
res.status(500).json({
|
|
@@ -63,6 +64,9 @@ async function handler({ context, req, res }) {
|
|
|
63
64
|
if (pageConfig === null) {
|
|
64
65
|
res.status(404).send('Page not found.');
|
|
65
66
|
} else {
|
|
67
|
+
if (buildResult?.warnings?.length > 0) {
|
|
68
|
+
pageConfig._warnings = buildResult.warnings;
|
|
69
|
+
}
|
|
66
70
|
res.status(200).json(pageConfig);
|
|
67
71
|
}
|
|
68
72
|
}
|