@lowdefy/server-dev 0.0.0-experimental-20260219135421 → 0.0.0-experimental-20260220095000
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/BuildErrorPage.js +4 -3
- package/lib/client/InstallingPluginsPage.js +50 -0
- package/lib/client/Page.js +4 -0
- package/lib/client/RestartingPage.js +2 -0
- package/lib/client/utils/request.js +1 -3
- package/lib/client/utils/usePageConfig.js +3 -0
- package/lib/server/apiWrapper.js +14 -10
- package/lib/server/auth/getMockSession.js +1 -1
- package/lib/server/jitPageBuilder.js +17 -1
- package/lib/server/log/createHandleError.js +47 -0
- package/lib/server/log/createLogger.js +2 -2
- package/manager/getContext.mjs +8 -2
- package/manager/processes/installPlugins.mjs +2 -2
- package/manager/processes/lowdefyBuild.mjs +2 -2
- package/manager/processes/nextBuild.mjs +4 -6
- package/manager/processes/restartServer.mjs +2 -2
- package/manager/processes/shutdownServer.mjs +1 -1
- package/manager/utils/checkPortAvailable.mjs +5 -4
- package/manager/utils/getLowdefyVersion.mjs +6 -12
- package/manager/watchers/lowdefyBuildWatcher.mjs +1 -1
- package/manager/watchers/nextBuildWatcher.mjs +12 -2
- package/next.config.js +1 -10
- package/package.json +30 -30
- package/package.original.json +30 -30
- package/pages/_app.js +3 -17
- package/pages/api/client-error.js +15 -10
- package/pages/api/page/[pageId].js +11 -3
- package/pages/api/request/[pageId]/[requestId].js +4 -1
- package/lib/client/sentry/captureSentryError.js +0 -43
- package/lib/client/sentry/initSentryClient.js +0 -55
- package/lib/client/sentry/setSentryUser.js +0 -41
- package/lib/server/log/logError.js +0 -56
- package/lib/server/sentry/captureSentryError.js +0 -57
- package/lib/server/sentry/initSentry.js +0 -44
- package/lib/server/sentry/setSentryUser.js +0 -46
|
@@ -21,7 +21,7 @@ const typeColors = {
|
|
|
21
21
|
ConfigWarning: '#d48806',
|
|
22
22
|
PluginError: '#531dab',
|
|
23
23
|
ServiceError: '#096dd9',
|
|
24
|
-
|
|
24
|
+
LowdefyInternalError: '#cf1322',
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
function getTypeColor(type) {
|
|
@@ -49,7 +49,7 @@ const ErrorItem = ({ type, message, source }) => {
|
|
|
49
49
|
>
|
|
50
50
|
{type}
|
|
51
51
|
</span>
|
|
52
|
-
<p style={{ fontSize: 14, margin: '4px 0' }}>{message}</p>
|
|
52
|
+
<p style={{ fontSize: 14, margin: '4px 0', fontFamily: 'monospace' }}>{message}</p>
|
|
53
53
|
{source && <p style={{ fontSize: 13, color: '#8c8c8c', margin: 0 }}>{source}</p>}
|
|
54
54
|
</div>
|
|
55
55
|
);
|
|
@@ -67,7 +67,8 @@ const BuildErrorPage = ({ errors, message, source }) => {
|
|
|
67
67
|
flexDirection: 'column',
|
|
68
68
|
alignItems: 'center',
|
|
69
69
|
justifyContent: 'center',
|
|
70
|
-
fontFamily:
|
|
70
|
+
fontFamily:
|
|
71
|
+
"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
|
|
71
72
|
padding: '0 24px',
|
|
72
73
|
}}
|
|
73
74
|
>
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
|
|
19
|
+
const InstallingPluginsPage = ({ packages }) => {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
style={{
|
|
23
|
+
height: '100vh',
|
|
24
|
+
textAlign: 'center',
|
|
25
|
+
display: 'flex',
|
|
26
|
+
flexDirection: 'column',
|
|
27
|
+
alignItems: 'center',
|
|
28
|
+
justifyContent: 'center',
|
|
29
|
+
fontFamily:
|
|
30
|
+
"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
<div
|
|
34
|
+
style={{
|
|
35
|
+
verticalAlign: 'middle',
|
|
36
|
+
display: 'inline-block',
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
<h3>Installing Plugin Packages</h3>
|
|
40
|
+
<p>
|
|
41
|
+
This page requires packages that are not yet installed:{' '}
|
|
42
|
+
<strong>{(packages ?? []).join(', ')}</strong>.
|
|
43
|
+
</p>
|
|
44
|
+
<p>The server will restart automatically. The page will reload when ready.</p>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default InstallingPluginsPage;
|
package/lib/client/Page.js
CHANGED
|
@@ -18,6 +18,7 @@ import React from 'react';
|
|
|
18
18
|
import Client from '@lowdefy/client';
|
|
19
19
|
|
|
20
20
|
import BuildErrorPage from './BuildErrorPage.js';
|
|
21
|
+
import InstallingPluginsPage from './InstallingPluginsPage.js';
|
|
21
22
|
import RestartingPage from './RestartingPage.js';
|
|
22
23
|
import usePageConfig from './utils/usePageConfig.js';
|
|
23
24
|
|
|
@@ -47,6 +48,9 @@ const Page = ({
|
|
|
47
48
|
/>
|
|
48
49
|
);
|
|
49
50
|
}
|
|
51
|
+
if (pageConfig.installing) {
|
|
52
|
+
return <InstallingPluginsPage packages={pageConfig.packages} />;
|
|
53
|
+
}
|
|
50
54
|
if (resetContext.restarting) {
|
|
51
55
|
return <RestartingPage />;
|
|
52
56
|
}
|
|
@@ -27,9 +27,7 @@ async function request({ url, method = 'GET', body }) {
|
|
|
27
27
|
}
|
|
28
28
|
if (!res.ok) {
|
|
29
29
|
const body = await res.json();
|
|
30
|
-
|
|
31
|
-
console.log(body);
|
|
32
|
-
throw new Error(body.message || 'Request error');
|
|
30
|
+
throw new Error(body?.['~e']?.message ?? body?.message ?? 'Request error');
|
|
33
31
|
}
|
|
34
32
|
return res.json();
|
|
35
33
|
}
|
package/lib/server/apiWrapper.js
CHANGED
|
@@ -17,6 +17,7 @@ import fs from 'fs';
|
|
|
17
17
|
import path from 'path';
|
|
18
18
|
import { createApiContext } from '@lowdefy/api';
|
|
19
19
|
import { getSecretsFromEnv } from '@lowdefy/node-utils';
|
|
20
|
+
import { serializer } from '@lowdefy/helpers';
|
|
20
21
|
import { v4 as uuid } from 'uuid';
|
|
21
22
|
|
|
22
23
|
import config from '../build/config.js';
|
|
@@ -24,13 +25,11 @@ import connections from '../../build/plugins/connections.js';
|
|
|
24
25
|
import createLogger from './log/createLogger.js';
|
|
25
26
|
import fileCache from './fileCache.js';
|
|
26
27
|
import getServerSession from './auth/getServerSession.js';
|
|
27
|
-
import
|
|
28
|
+
import createHandleError from './log/createHandleError.js';
|
|
28
29
|
import logRequest from './log/logRequest.js';
|
|
29
30
|
import operators from '../../build/plugins/operators/server.js';
|
|
30
31
|
import staticJsMap from '../../build/plugins/operators/serverJsMap.js';
|
|
31
32
|
import getAuthOptions from './auth/getAuthOptions.js';
|
|
32
|
-
import loggerConfig from '../build/logger.js';
|
|
33
|
-
import setSentryUser from './sentry/setSentryUser.js';
|
|
34
33
|
|
|
35
34
|
const secrets = getSecretsFromEnv();
|
|
36
35
|
|
|
@@ -73,6 +72,9 @@ function apiWrapper(handler) {
|
|
|
73
72
|
fileCache,
|
|
74
73
|
headers: req?.headers,
|
|
75
74
|
jsMap,
|
|
75
|
+
handleError: async (err) => {
|
|
76
|
+
console.error(err);
|
|
77
|
+
},
|
|
76
78
|
logger: console,
|
|
77
79
|
operators,
|
|
78
80
|
req,
|
|
@@ -81,14 +83,10 @@ function apiWrapper(handler) {
|
|
|
81
83
|
};
|
|
82
84
|
try {
|
|
83
85
|
context.logger = createLogger({ rid: context.rid });
|
|
86
|
+
context.handleError = createHandleError({ context });
|
|
84
87
|
context.authOptions = getAuthOptions(context);
|
|
85
88
|
if (!req.url.startsWith('/api/auth')) {
|
|
86
89
|
context.session = await getServerSession(context);
|
|
87
|
-
// Set Sentry user context for authenticated requests
|
|
88
|
-
setSentryUser({
|
|
89
|
-
user: context.session?.user,
|
|
90
|
-
sentryConfig: loggerConfig.sentry,
|
|
91
|
-
});
|
|
92
90
|
}
|
|
93
91
|
createApiContext(context);
|
|
94
92
|
logRequest({ context });
|
|
@@ -97,8 +95,14 @@ function apiWrapper(handler) {
|
|
|
97
95
|
// TODO: Log response time?
|
|
98
96
|
return response;
|
|
99
97
|
} catch (error) {
|
|
100
|
-
await
|
|
101
|
-
|
|
98
|
+
await context.handleError(error);
|
|
99
|
+
const serialized = serializer.serialize(error);
|
|
100
|
+
if (serialized?.['~e']) {
|
|
101
|
+
delete serialized['~e'].received;
|
|
102
|
+
delete serialized['~e'].stack;
|
|
103
|
+
delete serialized['~e'].configKey;
|
|
104
|
+
}
|
|
105
|
+
res.status(500).json(serialized);
|
|
102
106
|
}
|
|
103
107
|
};
|
|
104
108
|
}
|
|
@@ -28,7 +28,7 @@ async function getMockSession() {
|
|
|
28
28
|
try {
|
|
29
29
|
mockUser = JSON.parse(mockUserJson);
|
|
30
30
|
} catch (error) {
|
|
31
|
-
throw new Error(
|
|
31
|
+
throw new Error('Invalid JSON in LOWDEFY_DEV_USER environment variable.', { cause: error });
|
|
32
32
|
}
|
|
33
33
|
} else {
|
|
34
34
|
mockUser = authJson.dev?.mockUser;
|
|
@@ -85,10 +85,14 @@ function getBuildContext(buildDirectory, configDirectory) {
|
|
|
85
85
|
const jsMap = readJsonFile(path.join(buildDirectory, 'jsMap.json')) ?? { client: {}, server: {} };
|
|
86
86
|
const connectionIds = readJsonFile(path.join(buildDirectory, 'connectionIds.json')) ?? [];
|
|
87
87
|
|
|
88
|
+
const customTypesMap = readJsonFile(path.join(buildDirectory, 'customTypesMap.json')) ?? {};
|
|
89
|
+
|
|
88
90
|
cachedBuildContext = createContext({
|
|
91
|
+
customTypesMap,
|
|
89
92
|
directories: {
|
|
90
93
|
build: buildDirectory,
|
|
91
94
|
config: configDirectory,
|
|
95
|
+
server: path.resolve(buildDirectory, '..'),
|
|
92
96
|
},
|
|
93
97
|
logger: jitLogger,
|
|
94
98
|
stage: 'dev',
|
|
@@ -103,6 +107,11 @@ function getBuildContext(buildDirectory, configDirectory) {
|
|
|
103
107
|
cachedBuildContext.connectionIds.add(id);
|
|
104
108
|
}
|
|
105
109
|
|
|
110
|
+
// Load installed packages snapshot from skeleton build for missing-package detection
|
|
111
|
+
const installedPluginPackages =
|
|
112
|
+
readJsonFile(path.join(buildDirectory, 'installedPluginPackages.json')) ?? [];
|
|
113
|
+
cachedBuildContext.installedPluginPackages = new Set(installedPluginPackages);
|
|
114
|
+
|
|
106
115
|
return cachedBuildContext;
|
|
107
116
|
}
|
|
108
117
|
|
|
@@ -126,11 +135,18 @@ async function buildPageIfNeeded({ pageId, buildDirectory, configDirectory }) {
|
|
|
126
135
|
try {
|
|
127
136
|
const context = getBuildContext(buildDirectory, configDirectory);
|
|
128
137
|
const startTime = Date.now();
|
|
129
|
-
await buildPageJit({
|
|
138
|
+
const result = await buildPageJit({
|
|
130
139
|
pageId,
|
|
131
140
|
pageRegistry: registry,
|
|
132
141
|
context,
|
|
133
142
|
});
|
|
143
|
+
if (result && result.installing) {
|
|
144
|
+
jitLogger.info(
|
|
145
|
+
`Installing plugin packages for page "${pageId}": ${result.packages.join(', ')}. ` +
|
|
146
|
+
'The page will be available after the server restarts.'
|
|
147
|
+
);
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
134
150
|
pageCache.markCompiled(pageId);
|
|
135
151
|
jitLogger.info(`Built page "${pageId}" in ${Date.now() - startTime}ms.`);
|
|
136
152
|
return true;
|
|
@@ -0,0 +1,47 @@
|
|
|
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 { LowdefyInternalError, loadAndResolveErrorLocation } from '@lowdefy/errors';
|
|
18
|
+
|
|
19
|
+
function createHandleError({ context }) {
|
|
20
|
+
return async function handleError(error) {
|
|
21
|
+
try {
|
|
22
|
+
// For internal lowdefy errors, don't resolve config location
|
|
23
|
+
const location =
|
|
24
|
+
error instanceof LowdefyInternalError
|
|
25
|
+
? null
|
|
26
|
+
: await loadAndResolveErrorLocation({
|
|
27
|
+
error,
|
|
28
|
+
readConfigFile: context.readConfigFile,
|
|
29
|
+
configDirectory: context.configDirectory,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Attach resolved location to error for display layer
|
|
33
|
+
if (location) {
|
|
34
|
+
error.source = location.source;
|
|
35
|
+
error.config = location.config;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
context.logger.error(error);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.error(error);
|
|
41
|
+
console.error('An error occurred while logging the error.');
|
|
42
|
+
console.error(e);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default createHandleError;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { createNodeLogger
|
|
17
|
+
import { createNodeLogger } from '@lowdefy/logger/node';
|
|
18
18
|
|
|
19
19
|
const logger = createNodeLogger({
|
|
20
20
|
name: 'lowdefy_server',
|
|
@@ -23,7 +23,7 @@ const logger = createNodeLogger({
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
function createLogger(metadata = {}) {
|
|
26
|
-
return
|
|
26
|
+
return logger.child(metadata);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export default createLogger;
|
package/manager/getContext.mjs
CHANGED
|
@@ -19,7 +19,8 @@ import path from 'path';
|
|
|
19
19
|
import yargs from 'yargs';
|
|
20
20
|
import { hideBin } from 'yargs/helpers';
|
|
21
21
|
|
|
22
|
-
import
|
|
22
|
+
import pino from 'pino';
|
|
23
|
+
import { createNodeLogger } from '@lowdefy/logger/node';
|
|
23
24
|
import checkMockUserWarning from './processes/checkMockUserWarning.mjs';
|
|
24
25
|
import initialBuild from './processes/initialBuild.mjs';
|
|
25
26
|
import installPlugins from './processes/installPlugins.mjs';
|
|
@@ -48,7 +49,12 @@ async function getContext() {
|
|
|
48
49
|
config: path.resolve(argv.configDirectory ?? env.LOWDEFY_DIRECTORY_CONFIG ?? process.cwd()),
|
|
49
50
|
server: process.cwd(),
|
|
50
51
|
},
|
|
51
|
-
logger:
|
|
52
|
+
logger: createNodeLogger({
|
|
53
|
+
name: 'lowdefy build',
|
|
54
|
+
level: env.LOWDEFY_LOG_LEVEL ?? 'info',
|
|
55
|
+
base: { pid: undefined, hostname: undefined },
|
|
56
|
+
destination: pino.destination({ dest: 1, sync: true }),
|
|
57
|
+
}),
|
|
52
58
|
options: {
|
|
53
59
|
port: argv.port ?? env.PORT ?? 3000,
|
|
54
60
|
refResolver: argv.refResolver ?? env.LOWDEFY_BUILD_REF_RESOLVER,
|
|
@@ -18,7 +18,7 @@ import { spawnProcess } from '@lowdefy/node-utils';
|
|
|
18
18
|
|
|
19
19
|
function installPlugins({ logger, packageManagerCmd }) {
|
|
20
20
|
return async () => {
|
|
21
|
-
logger.
|
|
21
|
+
logger.info({ spin: true }, 'Installing plugins...');
|
|
22
22
|
await spawnProcess({
|
|
23
23
|
processOptions: {
|
|
24
24
|
// https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2#command-injection-via-args-parameter-of-child_processspawn-without-shell-option-enabled-on-windows-cve-2024-27980---high
|
|
@@ -28,7 +28,7 @@ function installPlugins({ logger, packageManagerCmd }) {
|
|
|
28
28
|
args: ['install', '--no-frozen-lockfile'],
|
|
29
29
|
stdOutLineHandler: (line) => logger.debug(line),
|
|
30
30
|
});
|
|
31
|
-
logger.
|
|
31
|
+
logger.info('Installed plugins.');
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -19,7 +19,7 @@ import createCustomPluginTypesMap from '../utils/createCustomPluginTypesMap.mjs'
|
|
|
19
19
|
|
|
20
20
|
function lowdefyBuild({ directories, logger, options, pageCache }) {
|
|
21
21
|
return async () => {
|
|
22
|
-
logger.
|
|
22
|
+
logger.info({ spin: true }, 'Building config...');
|
|
23
23
|
const customTypesMap = await createCustomPluginTypesMap({ directories, logger });
|
|
24
24
|
|
|
25
25
|
if (pageCache) {
|
|
@@ -35,7 +35,7 @@ function lowdefyBuild({ directories, logger, options, pageCache }) {
|
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
// Return result so getContext can store registries
|
|
38
|
-
logger.
|
|
38
|
+
logger.info('Built config.');
|
|
39
39
|
return result;
|
|
40
40
|
} finally {
|
|
41
41
|
if (pageCache) {
|
|
@@ -14,11 +14,12 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { BuildError } from '@lowdefy/errors';
|
|
17
18
|
import { spawnProcess } from '@lowdefy/node-utils';
|
|
18
19
|
|
|
19
20
|
function nextBuild({ bin, logger }) {
|
|
20
21
|
return async () => {
|
|
21
|
-
logger.
|
|
22
|
+
logger.info({ spin: true }, 'Building app...');
|
|
22
23
|
const errorLines = [];
|
|
23
24
|
try {
|
|
24
25
|
await spawnProcess({
|
|
@@ -34,12 +35,9 @@ function nextBuild({ bin, logger }) {
|
|
|
34
35
|
if (errorLines.length > 0) {
|
|
35
36
|
errorLines.forEach((line) => logger.error(line));
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
error.isFormatted = true;
|
|
39
|
-
error.hideStack = true;
|
|
40
|
-
throw error;
|
|
38
|
+
throw new BuildError('Next.js build failed. See above for details.');
|
|
41
39
|
}
|
|
42
|
-
logger.
|
|
40
|
+
logger.info('Built app.');
|
|
43
41
|
};
|
|
44
42
|
}
|
|
45
43
|
|
|
@@ -19,9 +19,9 @@ import startServer from './startServer.mjs';
|
|
|
19
19
|
function restartServer(context) {
|
|
20
20
|
return () => {
|
|
21
21
|
context.shutdownServer();
|
|
22
|
-
context.logger.
|
|
22
|
+
context.logger.info({ spin: true }, 'Restarting server...');
|
|
23
23
|
startServer(context);
|
|
24
|
-
context.logger.
|
|
24
|
+
context.logger.info({ succeed: true }, 'Restarted server.');
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -21,7 +21,7 @@ function shutdownServer(context) {
|
|
|
21
21
|
`Existing next server with pid ${context.nextServer.pid}, killed: ${context.nextServer.killed}`
|
|
22
22
|
);
|
|
23
23
|
if (!context.nextServer.killed) {
|
|
24
|
-
context.logger.
|
|
24
|
+
context.logger.info({ spin: true }, 'Shutting down server...');
|
|
25
25
|
context.nextServer.kill();
|
|
26
26
|
context.logger.debug(
|
|
27
27
|
`Killed next server with pid ${context.nextServer.pid}, killed: ${context.nextServer.killed}`
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { BuildError } from '@lowdefy/errors';
|
|
17
18
|
import net from 'net';
|
|
18
19
|
|
|
19
20
|
function checkPortAvailable(port) {
|
|
@@ -21,11 +22,11 @@ function checkPortAvailable(port) {
|
|
|
21
22
|
const server = net.createServer();
|
|
22
23
|
server.once('error', (err) => {
|
|
23
24
|
if (err.code === 'EADDRINUSE') {
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
reject(
|
|
26
|
+
new BuildError(
|
|
27
|
+
`Port ${port} is already in use. Stop the other process or use a different port with --port.`
|
|
28
|
+
)
|
|
26
29
|
);
|
|
27
|
-
error.isFormatted = true;
|
|
28
|
-
reject(error);
|
|
29
30
|
} else {
|
|
30
31
|
reject(err);
|
|
31
32
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import path from 'path';
|
|
18
18
|
import { type } from '@lowdefy/helpers';
|
|
19
19
|
import { readFile } from '@lowdefy/node-utils';
|
|
20
|
-
import { ConfigError } from '@lowdefy/errors
|
|
20
|
+
import { ConfigError } from '@lowdefy/errors';
|
|
21
21
|
import YAML from 'yaml';
|
|
22
22
|
|
|
23
23
|
async function getLowdefyVersion(context) {
|
|
@@ -33,11 +33,7 @@ async function getLowdefyVersion(context) {
|
|
|
33
33
|
try {
|
|
34
34
|
lowdefy = YAML.parse(lowdefyYaml);
|
|
35
35
|
} catch (error) {
|
|
36
|
-
throw new ConfigError({
|
|
37
|
-
error,
|
|
38
|
-
filePath,
|
|
39
|
-
configDirectory: context.directories.config,
|
|
40
|
-
});
|
|
36
|
+
throw new ConfigError('Could not parse YAML.', { cause: error, filePath });
|
|
41
37
|
}
|
|
42
38
|
if (!lowdefy.lowdefy) {
|
|
43
39
|
throw new Error(
|
|
@@ -45,12 +41,10 @@ async function getLowdefyVersion(context) {
|
|
|
45
41
|
);
|
|
46
42
|
}
|
|
47
43
|
if (!type.isString(lowdefy.lowdefy)) {
|
|
48
|
-
throw new ConfigError(
|
|
49
|
-
|
|
50
|
-
received: lowdefy.lowdefy,
|
|
51
|
-
|
|
52
|
-
configDirectory: context.directories.config,
|
|
53
|
-
});
|
|
44
|
+
throw new ConfigError(
|
|
45
|
+
'Version number specified in "lowdefy.yaml" file should be a string.',
|
|
46
|
+
{ received: lowdefy.lowdefy, filePath }
|
|
47
|
+
);
|
|
54
48
|
}
|
|
55
49
|
return lowdefy.lowdefy;
|
|
56
50
|
}
|
|
@@ -63,7 +63,7 @@ function lowdefyBuildWatcher(context) {
|
|
|
63
63
|
// (which has its own PageCache) knows which pages to rebuild.
|
|
64
64
|
const invalidationPath = path.join(context.directories.build, 'invalidatePages.json');
|
|
65
65
|
fs.writeFileSync(invalidationPath, JSON.stringify([...affectedPages]));
|
|
66
|
-
context.logger.
|
|
66
|
+
context.logger.info(
|
|
67
67
|
`Invalidated ${affectedPages.size} page(s): ${[...affectedPages].join(', ')}`
|
|
68
68
|
);
|
|
69
69
|
} else {
|
|
@@ -86,14 +86,24 @@ async function nextBuildWatcher(context) {
|
|
|
86
86
|
})
|
|
87
87
|
);
|
|
88
88
|
if (!build) {
|
|
89
|
-
context.logger.
|
|
89
|
+
context.logger.info({ succeed: true }, 'Reloaded app.');
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
context.shutdownServer();
|
|
94
94
|
if (install) {
|
|
95
|
-
context.logger.
|
|
95
|
+
context.logger.warn('Plugin dependencies have changed and will be reinstalled.');
|
|
96
96
|
await context.installPlugins();
|
|
97
|
+
// Rebuild Lowdefy artifacts (blocks.js, icons.js, styles.less, etc.)
|
|
98
|
+
// so newly installed packages are included in the Next.js bundle.
|
|
99
|
+
await context.lowdefyBuild();
|
|
100
|
+
// Re-hash all tracked files to avoid detecting our own build output
|
|
101
|
+
// changes as new changes on the next watcher callback.
|
|
102
|
+
await Promise.all(
|
|
103
|
+
trackedFiles.map(async (filePath) => {
|
|
104
|
+
hashes[filePath] = await sha1(filePath);
|
|
105
|
+
})
|
|
106
|
+
);
|
|
97
107
|
}
|
|
98
108
|
await context.nextBuild();
|
|
99
109
|
context.restartServer();
|
package/next.config.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const { withSentryConfig } = require('@sentry/nextjs');
|
|
2
1
|
const withLess = require('next-with-less');
|
|
3
2
|
const lowdefyConfig = require('./build/config.json');
|
|
4
3
|
|
|
@@ -30,12 +29,4 @@ const nextConfig = withLess({
|
|
|
30
29
|
},
|
|
31
30
|
});
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
// This enables source map uploads when SENTRY_AUTH_TOKEN is present
|
|
35
|
-
module.exports = process.env.SENTRY_DSN
|
|
36
|
-
? withSentryConfig(nextConfig, {
|
|
37
|
-
// Sentry options
|
|
38
|
-
silent: true,
|
|
39
|
-
hideSourceMaps: true,
|
|
40
|
-
})
|
|
41
|
-
: nextConfig;
|
|
32
|
+
module.exports = nextConfig;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/server-dev",
|
|
3
|
-
"version": "0.0.0-experimental-
|
|
3
|
+
"version": "0.0.0-experimental-20260220095000",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -36,35 +36,35 @@
|
|
|
36
36
|
".npmrc"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@lowdefy/actions-core": "0.0.0-experimental-
|
|
40
|
-
"@lowdefy/api": "0.0.0-experimental-
|
|
41
|
-
"@lowdefy/block-utils": "0.0.0-experimental-
|
|
42
|
-
"@lowdefy/blocks-aggrid": "0.0.0-experimental-
|
|
43
|
-
"@lowdefy/blocks-antd": "0.0.0-experimental-
|
|
44
|
-
"@lowdefy/blocks-basic": "0.0.0-experimental-
|
|
45
|
-
"@lowdefy/blocks-color-selectors": "0.0.0-experimental-
|
|
46
|
-
"@lowdefy/blocks-echarts": "0.0.0-experimental-
|
|
47
|
-
"@lowdefy/blocks-loaders": "0.0.0-experimental-
|
|
48
|
-
"@lowdefy/blocks-markdown": "0.0.0-experimental-
|
|
49
|
-
"@lowdefy/blocks-qr": "0.0.0-experimental-
|
|
50
|
-
"@lowdefy/build": "0.0.0-experimental-
|
|
51
|
-
"@lowdefy/client": "0.0.0-experimental-
|
|
52
|
-
"@lowdefy/
|
|
53
|
-
"@lowdefy/
|
|
54
|
-
"@lowdefy/helpers": "0.0.0-experimental-
|
|
55
|
-
"@lowdefy/layout": "0.0.0-experimental-
|
|
56
|
-
"@lowdefy/logger": "0.0.0-experimental-
|
|
57
|
-
"@lowdefy/node-utils": "0.0.0-experimental-
|
|
58
|
-
"@lowdefy/operators-change-case": "0.0.0-experimental-
|
|
59
|
-
"@lowdefy/operators-diff": "0.0.0-experimental-
|
|
60
|
-
"@lowdefy/operators-js": "0.0.0-experimental-
|
|
61
|
-
"@lowdefy/operators-moment": "0.0.0-experimental-
|
|
62
|
-
"@lowdefy/operators-mql": "0.0.0-experimental-
|
|
63
|
-
"@lowdefy/operators-nunjucks": "0.0.0-experimental-
|
|
64
|
-
"@lowdefy/operators-uuid": "0.0.0-experimental-
|
|
65
|
-
"@lowdefy/operators-yaml": "0.0.0-experimental-
|
|
66
|
-
"@lowdefy/plugin-
|
|
67
|
-
"@
|
|
39
|
+
"@lowdefy/actions-core": "0.0.0-experimental-20260220095000",
|
|
40
|
+
"@lowdefy/api": "0.0.0-experimental-20260220095000",
|
|
41
|
+
"@lowdefy/block-utils": "0.0.0-experimental-20260220095000",
|
|
42
|
+
"@lowdefy/blocks-aggrid": "0.0.0-experimental-20260220095000",
|
|
43
|
+
"@lowdefy/blocks-antd": "0.0.0-experimental-20260220095000",
|
|
44
|
+
"@lowdefy/blocks-basic": "0.0.0-experimental-20260220095000",
|
|
45
|
+
"@lowdefy/blocks-color-selectors": "0.0.0-experimental-20260220095000",
|
|
46
|
+
"@lowdefy/blocks-echarts": "0.0.0-experimental-20260220095000",
|
|
47
|
+
"@lowdefy/blocks-loaders": "0.0.0-experimental-20260220095000",
|
|
48
|
+
"@lowdefy/blocks-markdown": "0.0.0-experimental-20260220095000",
|
|
49
|
+
"@lowdefy/blocks-qr": "0.0.0-experimental-20260220095000",
|
|
50
|
+
"@lowdefy/build": "0.0.0-experimental-20260220095000",
|
|
51
|
+
"@lowdefy/client": "0.0.0-experimental-20260220095000",
|
|
52
|
+
"@lowdefy/engine": "0.0.0-experimental-20260220095000",
|
|
53
|
+
"@lowdefy/errors": "0.0.0-experimental-20260220095000",
|
|
54
|
+
"@lowdefy/helpers": "0.0.0-experimental-20260220095000",
|
|
55
|
+
"@lowdefy/layout": "0.0.0-experimental-20260220095000",
|
|
56
|
+
"@lowdefy/logger": "0.0.0-experimental-20260220095000",
|
|
57
|
+
"@lowdefy/node-utils": "0.0.0-experimental-20260220095000",
|
|
58
|
+
"@lowdefy/operators-change-case": "0.0.0-experimental-20260220095000",
|
|
59
|
+
"@lowdefy/operators-diff": "0.0.0-experimental-20260220095000",
|
|
60
|
+
"@lowdefy/operators-js": "0.0.0-experimental-20260220095000",
|
|
61
|
+
"@lowdefy/operators-moment": "0.0.0-experimental-20260220095000",
|
|
62
|
+
"@lowdefy/operators-mql": "0.0.0-experimental-20260220095000",
|
|
63
|
+
"@lowdefy/operators-nunjucks": "0.0.0-experimental-20260220095000",
|
|
64
|
+
"@lowdefy/operators-uuid": "0.0.0-experimental-20260220095000",
|
|
65
|
+
"@lowdefy/operators-yaml": "0.0.0-experimental-20260220095000",
|
|
66
|
+
"@lowdefy/plugin-aws": "0.0.0-experimental-20260220095000",
|
|
67
|
+
"@lowdefy/plugin-next-auth": "0.0.0-experimental-20260220095000",
|
|
68
68
|
"chokidar": "3.5.3",
|
|
69
69
|
"dotenv": "16.3.1",
|
|
70
70
|
"next": "13.5.4",
|
package/package.original.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/server-dev",
|
|
3
|
-
"version": "0.0.0-experimental-
|
|
3
|
+
"version": "0.0.0-experimental-20260220095000",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -43,35 +43,35 @@
|
|
|
43
43
|
"prepublishOnly": "pnpm build"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@lowdefy/actions-core": "0.0.0-experimental-
|
|
47
|
-
"@lowdefy/api": "0.0.0-experimental-
|
|
48
|
-
"@lowdefy/block-utils": "0.0.0-experimental-
|
|
49
|
-
"@lowdefy/blocks-aggrid": "0.0.0-experimental-
|
|
50
|
-
"@lowdefy/blocks-antd": "0.0.0-experimental-
|
|
51
|
-
"@lowdefy/blocks-basic": "0.0.0-experimental-
|
|
52
|
-
"@lowdefy/blocks-color-selectors": "0.0.0-experimental-
|
|
53
|
-
"@lowdefy/blocks-echarts": "0.0.0-experimental-
|
|
54
|
-
"@lowdefy/blocks-loaders": "0.0.0-experimental-
|
|
55
|
-
"@lowdefy/blocks-markdown": "0.0.0-experimental-
|
|
56
|
-
"@lowdefy/blocks-qr": "0.0.0-experimental-
|
|
57
|
-
"@lowdefy/build": "0.0.0-experimental-
|
|
58
|
-
"@lowdefy/client": "0.0.0-experimental-
|
|
59
|
-
"@lowdefy/
|
|
60
|
-
"@lowdefy/
|
|
61
|
-
"@lowdefy/helpers": "0.0.0-experimental-
|
|
62
|
-
"@lowdefy/layout": "0.0.0-experimental-
|
|
63
|
-
"@lowdefy/logger": "0.0.0-experimental-
|
|
64
|
-
"@lowdefy/node-utils": "0.0.0-experimental-
|
|
65
|
-
"@lowdefy/operators-change-case": "0.0.0-experimental-
|
|
66
|
-
"@lowdefy/operators-diff": "0.0.0-experimental-
|
|
67
|
-
"@lowdefy/operators-js": "0.0.0-experimental-
|
|
68
|
-
"@lowdefy/operators-moment": "0.0.0-experimental-
|
|
69
|
-
"@lowdefy/operators-mql": "0.0.0-experimental-
|
|
70
|
-
"@lowdefy/operators-nunjucks": "0.0.0-experimental-
|
|
71
|
-
"@lowdefy/operators-uuid": "0.0.0-experimental-
|
|
72
|
-
"@lowdefy/operators-yaml": "0.0.0-experimental-
|
|
73
|
-
"@lowdefy/plugin-
|
|
74
|
-
"@
|
|
46
|
+
"@lowdefy/actions-core": "0.0.0-experimental-20260220095000",
|
|
47
|
+
"@lowdefy/api": "0.0.0-experimental-20260220095000",
|
|
48
|
+
"@lowdefy/block-utils": "0.0.0-experimental-20260220095000",
|
|
49
|
+
"@lowdefy/blocks-aggrid": "0.0.0-experimental-20260220095000",
|
|
50
|
+
"@lowdefy/blocks-antd": "0.0.0-experimental-20260220095000",
|
|
51
|
+
"@lowdefy/blocks-basic": "0.0.0-experimental-20260220095000",
|
|
52
|
+
"@lowdefy/blocks-color-selectors": "0.0.0-experimental-20260220095000",
|
|
53
|
+
"@lowdefy/blocks-echarts": "0.0.0-experimental-20260220095000",
|
|
54
|
+
"@lowdefy/blocks-loaders": "0.0.0-experimental-20260220095000",
|
|
55
|
+
"@lowdefy/blocks-markdown": "0.0.0-experimental-20260220095000",
|
|
56
|
+
"@lowdefy/blocks-qr": "0.0.0-experimental-20260220095000",
|
|
57
|
+
"@lowdefy/build": "0.0.0-experimental-20260220095000",
|
|
58
|
+
"@lowdefy/client": "0.0.0-experimental-20260220095000",
|
|
59
|
+
"@lowdefy/engine": "0.0.0-experimental-20260220095000",
|
|
60
|
+
"@lowdefy/errors": "0.0.0-experimental-20260220095000",
|
|
61
|
+
"@lowdefy/helpers": "0.0.0-experimental-20260220095000",
|
|
62
|
+
"@lowdefy/layout": "0.0.0-experimental-20260220095000",
|
|
63
|
+
"@lowdefy/logger": "0.0.0-experimental-20260220095000",
|
|
64
|
+
"@lowdefy/node-utils": "0.0.0-experimental-20260220095000",
|
|
65
|
+
"@lowdefy/operators-change-case": "0.0.0-experimental-20260220095000",
|
|
66
|
+
"@lowdefy/operators-diff": "0.0.0-experimental-20260220095000",
|
|
67
|
+
"@lowdefy/operators-js": "0.0.0-experimental-20260220095000",
|
|
68
|
+
"@lowdefy/operators-moment": "0.0.0-experimental-20260220095000",
|
|
69
|
+
"@lowdefy/operators-mql": "0.0.0-experimental-20260220095000",
|
|
70
|
+
"@lowdefy/operators-nunjucks": "0.0.0-experimental-20260220095000",
|
|
71
|
+
"@lowdefy/operators-uuid": "0.0.0-experimental-20260220095000",
|
|
72
|
+
"@lowdefy/operators-yaml": "0.0.0-experimental-20260220095000",
|
|
73
|
+
"@lowdefy/plugin-aws": "0.0.0-experimental-20260220095000",
|
|
74
|
+
"@lowdefy/plugin-next-auth": "0.0.0-experimental-20260220095000",
|
|
75
75
|
"chokidar": "3.5.3",
|
|
76
76
|
"dotenv": "16.3.1",
|
|
77
77
|
"next": "13.5.4",
|
package/pages/_app.js
CHANGED
|
@@ -20,27 +20,18 @@ import dynamic from 'next/dynamic';
|
|
|
20
20
|
import { ErrorBoundary } from '@lowdefy/block-utils';
|
|
21
21
|
|
|
22
22
|
import Auth from '../lib/client/auth/Auth.js';
|
|
23
|
-
import initSentryClient from '../lib/client/sentry/initSentryClient.js';
|
|
24
|
-
import loggerConfig from '../lib/build/logger.js';
|
|
25
|
-
import setSentryUser from '../lib/client/sentry/setSentryUser.js';
|
|
26
23
|
|
|
27
24
|
// Must be in _app due to next specifications.
|
|
28
25
|
import '../build/plugins/styles.less';
|
|
29
26
|
|
|
30
|
-
// Initialize Sentry client once on module load
|
|
31
|
-
initSentryClient({
|
|
32
|
-
sentryDsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
|
33
|
-
sentryConfig: loggerConfig.sentry,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
27
|
function App({ Component }) {
|
|
37
28
|
const lowdefyRef = useRef({});
|
|
38
29
|
|
|
39
30
|
const handleError = useCallback((error) => {
|
|
40
|
-
if (
|
|
41
|
-
|
|
31
|
+
if (lowdefyRef.current?._internal?.handleError) {
|
|
32
|
+
lowdefyRef.current._internal.handleError(error);
|
|
42
33
|
} else {
|
|
43
|
-
console.error(error
|
|
34
|
+
console.error(error);
|
|
44
35
|
}
|
|
45
36
|
}, []);
|
|
46
37
|
|
|
@@ -49,11 +40,6 @@ function App({ Component }) {
|
|
|
49
40
|
<Suspense fallback="">
|
|
50
41
|
<Auth>
|
|
51
42
|
{(auth) => {
|
|
52
|
-
// Set Sentry user context when auth changes
|
|
53
|
-
setSentryUser({
|
|
54
|
-
user: auth.session,
|
|
55
|
-
sentryConfig: loggerConfig.sentry,
|
|
56
|
-
});
|
|
57
43
|
return <Component auth={auth} lowdefy={lowdefyRef.current} />;
|
|
58
44
|
}}
|
|
59
45
|
</Auth>
|
|
@@ -17,23 +17,28 @@
|
|
|
17
17
|
import { logClientError } from '@lowdefy/api';
|
|
18
18
|
|
|
19
19
|
import apiWrapper from '../../lib/server/apiWrapper.js';
|
|
20
|
-
import captureSentryError from '../../lib/server/sentry/captureSentryError.js';
|
|
21
20
|
|
|
22
21
|
async function handler({ context, req, res }) {
|
|
23
22
|
if (req.method !== 'POST') {
|
|
24
23
|
throw new Error('Only POST requests are supported.');
|
|
25
24
|
}
|
|
26
|
-
const response = await logClientError(context, req.body);
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
error:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
const origin = req.headers.origin;
|
|
27
|
+
if (!origin) {
|
|
28
|
+
res.status(403).json({ error: 'Forbidden' });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
if (new URL(origin).host !== req.headers.host) {
|
|
33
|
+
res.status(403).json({ error: 'Forbidden' });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
res.status(403).json({ error: 'Forbidden' });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
36
40
|
|
|
41
|
+
const { error, ...response } = await logClientError(context, req.body);
|
|
37
42
|
res.status(200).json(response);
|
|
38
43
|
}
|
|
39
44
|
|
|
@@ -18,14 +18,14 @@ import { getPageConfig } from '@lowdefy/api';
|
|
|
18
18
|
|
|
19
19
|
import apiWrapper from '../../../lib/server/apiWrapper.js';
|
|
20
20
|
import buildPageIfNeeded from '../../../lib/server/jitPageBuilder.js';
|
|
21
|
-
import logError from '../../../lib/server/log/logError.js';
|
|
22
21
|
|
|
23
22
|
async function handler({ context, req, res }) {
|
|
24
23
|
const { pageId } = req.query;
|
|
25
24
|
|
|
26
25
|
// Attempt JIT build if page not yet compiled
|
|
26
|
+
let buildResult;
|
|
27
27
|
try {
|
|
28
|
-
await buildPageIfNeeded({
|
|
28
|
+
buildResult = await buildPageIfNeeded({
|
|
29
29
|
pageId,
|
|
30
30
|
buildDirectory: context.buildDirectory,
|
|
31
31
|
configDirectory: context.configDirectory,
|
|
@@ -34,7 +34,7 @@ async function handler({ context, req, res }) {
|
|
|
34
34
|
const rawErrors = error.buildErrors ?? [error];
|
|
35
35
|
const errors = [];
|
|
36
36
|
for (const err of rawErrors) {
|
|
37
|
-
await
|
|
37
|
+
await context.handleError(err);
|
|
38
38
|
errors.push({
|
|
39
39
|
type: err.name ?? 'Error',
|
|
40
40
|
message: err.message,
|
|
@@ -51,6 +51,14 @@ async function handler({ context, req, res }) {
|
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
if (buildResult && buildResult.installing) {
|
|
55
|
+
res.status(200).json({
|
|
56
|
+
installing: true,
|
|
57
|
+
packages: buildResult.packages,
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
const pageConfig = await getPageConfig(context, { pageId });
|
|
55
63
|
if (pageConfig === null) {
|
|
56
64
|
res.status(404).send('Page not found.');
|
|
@@ -24,7 +24,10 @@ async function handler({ context, req, res }) {
|
|
|
24
24
|
}
|
|
25
25
|
const { pageId, requestId } = req.query;
|
|
26
26
|
const { actionId, blockId, payload } = req.body;
|
|
27
|
-
context.logger.
|
|
27
|
+
context.logger.info(
|
|
28
|
+
{ color: 'gray' },
|
|
29
|
+
`Request: ${pageId} · ${blockId} · ${actionId} → ${requestId}`
|
|
30
|
+
);
|
|
28
31
|
const response = await callRequest(context, { blockId, pageId, payload, requestId });
|
|
29
32
|
res.status(200).json(response);
|
|
30
33
|
}
|
|
@@ -1,43 +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 * as Sentry from '@sentry/nextjs';
|
|
18
|
-
|
|
19
|
-
function captureSentryError({ error, pageId, blockId, configLocation }) {
|
|
20
|
-
const tags = {};
|
|
21
|
-
const extra = {};
|
|
22
|
-
|
|
23
|
-
// Add Lowdefy-specific context
|
|
24
|
-
if (pageId) {
|
|
25
|
-
tags.pageId = pageId;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (blockId) {
|
|
29
|
-
tags.blockId = blockId;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Add config location context
|
|
33
|
-
if (configLocation) {
|
|
34
|
-
extra.configLocation = configLocation;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
Sentry.captureException(error, {
|
|
38
|
-
tags,
|
|
39
|
-
extra,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export default captureSentryError;
|
|
@@ -1,55 +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 * as Sentry from '@sentry/nextjs';
|
|
18
|
-
|
|
19
|
-
let initialized = false;
|
|
20
|
-
|
|
21
|
-
function initSentryClient({ sentryDsn, sentryConfig }) {
|
|
22
|
-
// No-op if already initialized
|
|
23
|
-
if (initialized) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// No-op if SENTRY_DSN not set
|
|
28
|
-
if (!sentryDsn) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// No-op if client logging is explicitly disabled
|
|
33
|
-
if (sentryConfig?.client === false) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const config = sentryConfig || {};
|
|
38
|
-
|
|
39
|
-
Sentry.init({
|
|
40
|
-
dsn: sentryDsn,
|
|
41
|
-
environment: config.environment || process.env.NODE_ENV || 'production',
|
|
42
|
-
tracesSampleRate: config.tracesSampleRate ?? 0.1,
|
|
43
|
-
replaysSessionSampleRate: config.replaysSessionSampleRate ?? 0,
|
|
44
|
-
replaysOnErrorSampleRate: config.replaysOnErrorSampleRate ?? 0.1,
|
|
45
|
-
integrations: [
|
|
46
|
-
Sentry.replayIntegration(),
|
|
47
|
-
...(config.feedback ? [Sentry.feedbackIntegration({ colorScheme: 'system' })] : []),
|
|
48
|
-
],
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
initialized = true;
|
|
52
|
-
console.log('Sentry enabled: client');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export default initSentryClient;
|
|
@@ -1,41 +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 * as Sentry from '@sentry/nextjs';
|
|
18
|
-
|
|
19
|
-
function setSentryUser({ user, sentryConfig }) {
|
|
20
|
-
// No-op if no user
|
|
21
|
-
if (!user) {
|
|
22
|
-
Sentry.setUser(null);
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const userFields = sentryConfig?.userFields || ['id', '_id'];
|
|
27
|
-
const sentryUser = {};
|
|
28
|
-
|
|
29
|
-
userFields.forEach((field) => {
|
|
30
|
-
if (user[field] !== undefined) {
|
|
31
|
-
sentryUser[field] = user[field];
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Only set user if we have at least one field
|
|
36
|
-
if (Object.keys(sentryUser).length > 0) {
|
|
37
|
-
Sentry.setUser(sentryUser);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export default setSentryUser;
|
|
@@ -1,56 +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 { resolveErrorConfigLocation } from '@lowdefy/errors/build';
|
|
18
|
-
|
|
19
|
-
import captureSentryError from '../sentry/captureSentryError.js';
|
|
20
|
-
|
|
21
|
-
async function logError({ context, error }) {
|
|
22
|
-
try {
|
|
23
|
-
const isServiceError = error?.isServiceError === true;
|
|
24
|
-
|
|
25
|
-
// For service errors, don't resolve config location (not a config issue)
|
|
26
|
-
const location = isServiceError
|
|
27
|
-
? null
|
|
28
|
-
: await resolveErrorConfigLocation({
|
|
29
|
-
error,
|
|
30
|
-
readConfigFile: context.readConfigFile,
|
|
31
|
-
configDirectory: context.configDirectory,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// Attach resolved location to error for display layer
|
|
35
|
-
if (location) {
|
|
36
|
-
error.source = location.source;
|
|
37
|
-
error.config = location.config;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Log error - logger handles source, name prefix, and received formatting
|
|
41
|
-
context.logger.error(error);
|
|
42
|
-
|
|
43
|
-
// Capture error to Sentry (no-op if Sentry not configured)
|
|
44
|
-
captureSentryError({
|
|
45
|
-
error,
|
|
46
|
-
context,
|
|
47
|
-
configLocation: location,
|
|
48
|
-
});
|
|
49
|
-
} catch (e) {
|
|
50
|
-
console.error(error);
|
|
51
|
-
console.error('An error occurred while logging the error.');
|
|
52
|
-
console.error(e);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export default logError;
|
|
@@ -1,57 +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 * as Sentry from '@sentry/nextjs';
|
|
18
|
-
|
|
19
|
-
function captureSentryError({ error, context, configLocation }) {
|
|
20
|
-
// No-op if Sentry not initialized (DSN not set)
|
|
21
|
-
if (!process.env.SENTRY_DSN) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const tags = {};
|
|
26
|
-
const extra = {};
|
|
27
|
-
|
|
28
|
-
// Add Lowdefy-specific context
|
|
29
|
-
if (context?.pageId) {
|
|
30
|
-
tags.pageId = context.pageId;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (error?.blockId) {
|
|
34
|
-
tags.blockId = error.blockId;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (error?.isServiceError !== undefined) {
|
|
38
|
-
tags.isServiceError = error.isServiceError;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Add config location context
|
|
42
|
-
if (configLocation) {
|
|
43
|
-
extra.configLocation = configLocation;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Add config key for reference
|
|
47
|
-
if (error?.configKey) {
|
|
48
|
-
extra.configKey = error.configKey;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
Sentry.captureException(error, {
|
|
52
|
-
tags,
|
|
53
|
-
extra,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export default captureSentryError;
|
|
@@ -1,44 +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 * as Sentry from '@sentry/nextjs';
|
|
18
|
-
|
|
19
|
-
import loggerConfig from '../../build/logger.js';
|
|
20
|
-
|
|
21
|
-
function initSentryServer() {
|
|
22
|
-
// No-op if SENTRY_DSN not set
|
|
23
|
-
if (!process.env.SENTRY_DSN) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const sentryConfig = loggerConfig.sentry || {};
|
|
28
|
-
|
|
29
|
-
// No-op if server logging is explicitly disabled
|
|
30
|
-
if (sentryConfig.server === false) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
Sentry.init({
|
|
35
|
-
dsn: process.env.SENTRY_DSN,
|
|
36
|
-
environment: sentryConfig.environment || process.env.NODE_ENV || 'production',
|
|
37
|
-
tracesSampleRate: sentryConfig.tracesSampleRate ?? 0.1,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
console.log('Sentry enabled: server');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export default initSentryServer;
|
|
44
|
-
export { initSentryServer };
|
|
@@ -1,46 +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 * as Sentry from '@sentry/nextjs';
|
|
18
|
-
|
|
19
|
-
function setSentryUser({ user, sentryConfig }) {
|
|
20
|
-
// No-op if Sentry not initialized (DSN not set)
|
|
21
|
-
if (!process.env.SENTRY_DSN) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// No-op if no user
|
|
26
|
-
if (!user) {
|
|
27
|
-
Sentry.setUser(null);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const userFields = sentryConfig?.userFields || ['id', '_id'];
|
|
32
|
-
const sentryUser = {};
|
|
33
|
-
|
|
34
|
-
userFields.forEach((field) => {
|
|
35
|
-
if (user[field] !== undefined) {
|
|
36
|
-
sentryUser[field] = user[field];
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Only set user if we have at least one field
|
|
41
|
-
if (Object.keys(sentryUser).length > 0) {
|
|
42
|
-
Sentry.setUser(sentryUser);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export default setSentryUser;
|