@lowdefy/server-dev 0.0.0-experimental-20251203205559 → 0.0.0-experimental-20260112140412
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/sentry/captureSentryError.js +43 -0
- package/lib/client/sentry/initSentryClient.js +55 -0
- package/lib/client/sentry/setSentryUser.js +41 -0
- package/lib/client/utils/request.js +0 -1
- package/lib/server/apiWrapper.js +17 -3
- package/lib/server/log/logError.js +63 -13
- package/lib/server/sentry/captureSentryError.js +57 -0
- package/lib/server/sentry/initSentry.js +52 -0
- package/lib/server/sentry/setSentryUser.js +46 -0
- package/manager/processes/lowdefyBuild.mjs +2 -33
- package/manager/run.mjs +1 -11
- package/manager/watchers/lowdefyBuildWatcher.mjs +4 -7
- package/next.config.js +12 -2
- package/package.json +30 -28
- package/package.original.json +30 -28
- package/pages/_app.js +25 -1
- package/pages/api/client-error.js +47 -0
- package/pages/api/reload.js +1 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 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;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 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;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 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;
|
package/lib/server/apiWrapper.js
CHANGED
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import path from 'path';
|
|
17
|
-
import crypto from 'crypto';
|
|
18
17
|
import { createApiContext } from '@lowdefy/api';
|
|
19
18
|
import { getSecretsFromEnv } from '@lowdefy/node-utils';
|
|
19
|
+
import { v4 as uuid } from 'uuid';
|
|
20
20
|
|
|
21
21
|
import config from '../../build/config.json';
|
|
22
22
|
import connections from '../../build/plugins/connections.js';
|
|
@@ -28,6 +28,14 @@ import logRequest from './log/logRequest.js';
|
|
|
28
28
|
import operators from '../../build/plugins/operators/server.js';
|
|
29
29
|
import jsMap from '../../build/plugins/operators/serverJsMap.js';
|
|
30
30
|
import getAuthOptions from './auth/getAuthOptions.js';
|
|
31
|
+
import setSentryUser from './sentry/setSentryUser.js';
|
|
32
|
+
|
|
33
|
+
let loggerConfig = {};
|
|
34
|
+
try {
|
|
35
|
+
loggerConfig = require('../../build/logger.json');
|
|
36
|
+
} catch {
|
|
37
|
+
// logger.json may not exist if logger is not configured
|
|
38
|
+
}
|
|
31
39
|
|
|
32
40
|
const secrets = getSecretsFromEnv();
|
|
33
41
|
|
|
@@ -35,8 +43,9 @@ function apiWrapper(handler) {
|
|
|
35
43
|
return async function wrappedHandler(req, res) {
|
|
36
44
|
const context = {
|
|
37
45
|
// Important to give absolute path so Next can trace build files
|
|
38
|
-
rid:
|
|
46
|
+
rid: uuid(),
|
|
39
47
|
buildDirectory: path.join(process.cwd(), 'build'),
|
|
48
|
+
configDirectory: process.env.LOWDEFY_DIRECTORY_CONFIG || process.cwd(),
|
|
40
49
|
config,
|
|
41
50
|
connections,
|
|
42
51
|
fileCache,
|
|
@@ -53,6 +62,11 @@ function apiWrapper(handler) {
|
|
|
53
62
|
context.authOptions = getAuthOptions(context);
|
|
54
63
|
if (!req.url.startsWith('/api/auth')) {
|
|
55
64
|
context.session = await getServerSession(context);
|
|
65
|
+
// Set Sentry user context for authenticated requests
|
|
66
|
+
setSentryUser({
|
|
67
|
+
user: context.session?.user,
|
|
68
|
+
sentryConfig: loggerConfig.sentry,
|
|
69
|
+
});
|
|
56
70
|
}
|
|
57
71
|
createApiContext(context);
|
|
58
72
|
logRequest({ context });
|
|
@@ -61,7 +75,7 @@ function apiWrapper(handler) {
|
|
|
61
75
|
// TODO: Log response time?
|
|
62
76
|
return response;
|
|
63
77
|
} catch (error) {
|
|
64
|
-
logError({ error, context });
|
|
78
|
+
await logError({ error, context });
|
|
65
79
|
res.status(500).json({ name: error.name, message: error.message });
|
|
66
80
|
}
|
|
67
81
|
};
|
|
@@ -14,21 +14,71 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
import { resolveConfigLocation } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
import captureSentryError from '../sentry/captureSentryError.js';
|
|
20
|
+
|
|
21
|
+
async function resolveErrorConfigLocation(context, error) {
|
|
22
|
+
if (!error.configKey) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
18
25
|
try {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const [keyMap, refMap] = await Promise.all([
|
|
27
|
+
context.readConfigFile('keyMap.json'),
|
|
28
|
+
context.readConfigFile('refMap.json'),
|
|
29
|
+
]);
|
|
30
|
+
const location = resolveConfigLocation({
|
|
31
|
+
configKey: error.configKey,
|
|
32
|
+
keyMap,
|
|
33
|
+
refMap,
|
|
34
|
+
configDirectory: context.configDirectory,
|
|
35
|
+
});
|
|
36
|
+
return location || null;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function logError({ context, error }) {
|
|
43
|
+
try {
|
|
44
|
+
const message = error?.message || 'Unknown error';
|
|
45
|
+
const isServiceError = error?.isServiceError === true;
|
|
46
|
+
|
|
47
|
+
// For service errors, don't resolve config location (not a config issue)
|
|
48
|
+
const location = isServiceError ? null : await resolveErrorConfigLocation(context, error);
|
|
49
|
+
|
|
50
|
+
// Human-readable console output (single log entry)
|
|
51
|
+
const errorType = isServiceError ? 'Service Error' : 'Config Error';
|
|
52
|
+
const source = location?.source ? `${location.source} at ${location.config}` : '';
|
|
53
|
+
const link = location?.link || '';
|
|
54
|
+
|
|
55
|
+
if (isServiceError) {
|
|
56
|
+
console.error(`[${errorType}] ${message}`);
|
|
57
|
+
} else {
|
|
58
|
+
console.error(`[${errorType}] ${message}\n ${source}\n ${link}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Structured logging
|
|
62
|
+
context.logger.error(
|
|
63
|
+
{
|
|
64
|
+
event: isServiceError ? 'service_error' : 'config_error',
|
|
65
|
+
errorName: error?.name || 'Error',
|
|
66
|
+
errorMessage: message,
|
|
67
|
+
isServiceError,
|
|
68
|
+
pageId: context.pageId || null,
|
|
69
|
+
timestamp: new Date().toISOString(),
|
|
70
|
+
source: location?.source || null,
|
|
71
|
+
config: location?.config || null,
|
|
72
|
+
link: location?.link || null,
|
|
28
73
|
},
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
74
|
+
message
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Capture error to Sentry (no-op if Sentry not configured)
|
|
78
|
+
captureSentryError({
|
|
79
|
+
error,
|
|
80
|
+
context,
|
|
81
|
+
configLocation: location,
|
|
32
82
|
});
|
|
33
83
|
} catch (e) {
|
|
34
84
|
console.error(error);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 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;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 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 loadLoggerConfig() {
|
|
20
|
+
try {
|
|
21
|
+
// Dynamic require to handle missing file gracefully
|
|
22
|
+
return require('../../../build/logger.json');
|
|
23
|
+
} catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function initSentryServer() {
|
|
29
|
+
// No-op if SENTRY_DSN not set
|
|
30
|
+
if (!process.env.SENTRY_DSN) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const loggerConfig = loadLoggerConfig();
|
|
35
|
+
const sentryConfig = loggerConfig.sentry || {};
|
|
36
|
+
|
|
37
|
+
// No-op if server logging is explicitly disabled
|
|
38
|
+
if (sentryConfig.server === false) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Sentry.init({
|
|
43
|
+
dsn: process.env.SENTRY_DSN,
|
|
44
|
+
environment: sentryConfig.environment || process.env.NODE_ENV || 'production',
|
|
45
|
+
tracesSampleRate: sentryConfig.tracesSampleRate ?? 0.1,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
console.log('Sentry enabled: server');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default initSentryServer;
|
|
52
|
+
export { initSentryServer };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 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;
|
|
@@ -14,40 +14,12 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import path from 'path';
|
|
18
17
|
import build from '@lowdefy/build';
|
|
19
18
|
import createCustomPluginTypesMap from '../utils/createCustomPluginTypesMap.mjs';
|
|
20
19
|
|
|
21
20
|
function lowdefyBuild({ directories, logger, options }) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
dependencyGraph: new Map(),
|
|
25
|
-
parsedContentCache: new Map(),
|
|
26
|
-
refCache: new Map(),
|
|
27
|
-
pathToRefHashes: new Map(), // Maps file path -> Set of ref hashes for that path
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
return async ({ changedFiles = [] } = {}) => {
|
|
31
|
-
const isIncremental = changedFiles.length > 0 && buildState.dependencyGraph.size > 0;
|
|
32
|
-
if (isIncremental) {
|
|
33
|
-
logger.info(
|
|
34
|
-
{ print: 'spin' },
|
|
35
|
-
`Rebuilding config (${changedFiles.length} file(s) changed)...`
|
|
36
|
-
);
|
|
37
|
-
} else {
|
|
38
|
-
logger.info({ print: 'spin' }, 'Building config...');
|
|
39
|
-
// Clear caches for full rebuild
|
|
40
|
-
buildState.dependencyGraph.clear();
|
|
41
|
-
buildState.parsedContentCache.clear();
|
|
42
|
-
buildState.refCache.clear();
|
|
43
|
-
buildState.pathToRefHashes.clear();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Convert absolute paths to relative paths (relative to config directory)
|
|
47
|
-
const relativeChangedFiles = changedFiles.map((filePath) =>
|
|
48
|
-
path.relative(directories.config, filePath)
|
|
49
|
-
);
|
|
50
|
-
|
|
21
|
+
return async () => {
|
|
22
|
+
logger.info({ print: 'spin' }, 'Building config...');
|
|
51
23
|
const customTypesMap = await createCustomPluginTypesMap({ directories, logger });
|
|
52
24
|
await build({
|
|
53
25
|
customTypesMap,
|
|
@@ -55,9 +27,6 @@ function lowdefyBuild({ directories, logger, options }) {
|
|
|
55
27
|
logger,
|
|
56
28
|
refResolver: options.refResolver,
|
|
57
29
|
stage: 'dev',
|
|
58
|
-
changedFiles: isIncremental ? relativeChangedFiles : [],
|
|
59
|
-
// Pass persistent state for incremental builds
|
|
60
|
-
buildState,
|
|
61
30
|
});
|
|
62
31
|
logger.info({ print: 'log' }, 'Built config.');
|
|
63
32
|
};
|
package/manager/run.mjs
CHANGED
|
@@ -76,20 +76,10 @@ The run script does the following:
|
|
|
76
76
|
pinging the /api/ping route, until it detects a new server has started, and then reloads the window.
|
|
77
77
|
*/
|
|
78
78
|
|
|
79
|
-
/* TODO:
|
|
80
|
-
Not killing server on errors properly
|
|
81
|
-
when:
|
|
82
|
-
- initial build fails
|
|
83
|
-
*/
|
|
84
|
-
|
|
85
79
|
const context = await getContext();
|
|
86
80
|
|
|
87
81
|
try {
|
|
88
|
-
|
|
89
|
-
await context.initialBuild();
|
|
90
|
-
} catch (error) {
|
|
91
|
-
context.logger.error(error);
|
|
92
|
-
}
|
|
82
|
+
await context.initialBuild();
|
|
93
83
|
|
|
94
84
|
// We are not waiting for the startWatchers promise to resolve (all watchers have fired the ready event)
|
|
95
85
|
// because chokidar sometimes doesn't fire this event, and it seems like there isn't an issue with not waiting.
|
|
@@ -23,10 +23,9 @@ function lowdefyBuildWatcher(context) {
|
|
|
23
23
|
path.isAbsolute(item) ? item : path.resolve(context.directories.config, item);
|
|
24
24
|
|
|
25
25
|
const callback = async (filePaths) => {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
(filePath) => filePath.includes('lowdefy.yaml') || filePath.includes('lowdefy.yml')
|
|
29
|
-
);
|
|
26
|
+
const lowdefyYamlModified = filePaths
|
|
27
|
+
.flat()
|
|
28
|
+
.some((filePath) => filePath.includes('lowdefy.yaml') || filePath.includes('lowdefy.yml'));
|
|
30
29
|
if (lowdefyYamlModified) {
|
|
31
30
|
const lowdefyVersion = await getLowdefyVersion(context);
|
|
32
31
|
if (lowdefyVersion !== context.version && lowdefyVersion !== 'local') {
|
|
@@ -36,9 +35,7 @@ function lowdefyBuildWatcher(context) {
|
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
const changedFiles = lowdefyYamlModified ? [] : flatFilePaths;
|
|
41
|
-
await context.lowdefyBuild({ changedFiles });
|
|
38
|
+
await context.lowdefyBuild();
|
|
42
39
|
context.reloadClients();
|
|
43
40
|
};
|
|
44
41
|
return setupWatcher({
|
package/next.config.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
const { withSentryConfig } = require('@sentry/nextjs');
|
|
1
2
|
const withLess = require('next-with-less');
|
|
2
3
|
const lowdefyConfig = require('./build/config.json');
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
const nextConfig = withLess({
|
|
5
6
|
basePath: lowdefyConfig.basePath,
|
|
6
7
|
// reactStrictMode: true,
|
|
7
8
|
webpack: (config, { isServer }) => {
|
|
@@ -19,7 +20,6 @@ module.exports = withLess({
|
|
|
19
20
|
}
|
|
20
21
|
return config;
|
|
21
22
|
},
|
|
22
|
-
swcMinify: false,
|
|
23
23
|
compress: false,
|
|
24
24
|
outputFileTracing: false,
|
|
25
25
|
poweredByHeader: false,
|
|
@@ -29,3 +29,13 @@ module.exports = withLess({
|
|
|
29
29
|
ignoreDuringBuilds: true,
|
|
30
30
|
},
|
|
31
31
|
});
|
|
32
|
+
|
|
33
|
+
// Only wrap with Sentry if SENTRY_DSN is configured
|
|
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;
|
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-20260112140412",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -36,33 +36,34 @@
|
|
|
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/connection-axios-http": "0.0.0-experimental-
|
|
53
|
-
"@lowdefy/engine": "0.0.0-experimental-
|
|
54
|
-
"@lowdefy/helpers": "0.0.0-experimental-
|
|
55
|
-
"@lowdefy/layout": "0.0.0-experimental-
|
|
56
|
-
"@lowdefy/node-utils": "0.0.0-experimental-
|
|
57
|
-
"@lowdefy/operators-change-case": "0.0.0-experimental-
|
|
58
|
-
"@lowdefy/operators-diff": "0.0.0-experimental-
|
|
59
|
-
"@lowdefy/operators-js": "0.0.0-experimental-
|
|
60
|
-
"@lowdefy/operators-moment": "0.0.0-experimental-
|
|
61
|
-
"@lowdefy/operators-mql": "0.0.0-experimental-
|
|
62
|
-
"@lowdefy/operators-nunjucks": "0.0.0-experimental-
|
|
63
|
-
"@lowdefy/operators-uuid": "0.0.0-experimental-
|
|
64
|
-
"@lowdefy/operators-yaml": "0.0.0-experimental-
|
|
65
|
-
"@lowdefy/plugin-next-auth": "0.0.0-experimental-
|
|
39
|
+
"@lowdefy/actions-core": "0.0.0-experimental-20260112140412",
|
|
40
|
+
"@lowdefy/api": "0.0.0-experimental-20260112140412",
|
|
41
|
+
"@lowdefy/block-utils": "0.0.0-experimental-20260112140412",
|
|
42
|
+
"@lowdefy/blocks-aggrid": "0.0.0-experimental-20260112140412",
|
|
43
|
+
"@lowdefy/blocks-antd": "0.0.0-experimental-20260112140412",
|
|
44
|
+
"@lowdefy/blocks-basic": "0.0.0-experimental-20260112140412",
|
|
45
|
+
"@lowdefy/blocks-color-selectors": "0.0.0-experimental-20260112140412",
|
|
46
|
+
"@lowdefy/blocks-echarts": "0.0.0-experimental-20260112140412",
|
|
47
|
+
"@lowdefy/blocks-loaders": "0.0.0-experimental-20260112140412",
|
|
48
|
+
"@lowdefy/blocks-markdown": "0.0.0-experimental-20260112140412",
|
|
49
|
+
"@lowdefy/blocks-qr": "0.0.0-experimental-20260112140412",
|
|
50
|
+
"@lowdefy/build": "0.0.0-experimental-20260112140412",
|
|
51
|
+
"@lowdefy/client": "0.0.0-experimental-20260112140412",
|
|
52
|
+
"@lowdefy/connection-axios-http": "0.0.0-experimental-20260112140412",
|
|
53
|
+
"@lowdefy/engine": "0.0.0-experimental-20260112140412",
|
|
54
|
+
"@lowdefy/helpers": "0.0.0-experimental-20260112140412",
|
|
55
|
+
"@lowdefy/layout": "0.0.0-experimental-20260112140412",
|
|
56
|
+
"@lowdefy/node-utils": "0.0.0-experimental-20260112140412",
|
|
57
|
+
"@lowdefy/operators-change-case": "0.0.0-experimental-20260112140412",
|
|
58
|
+
"@lowdefy/operators-diff": "0.0.0-experimental-20260112140412",
|
|
59
|
+
"@lowdefy/operators-js": "0.0.0-experimental-20260112140412",
|
|
60
|
+
"@lowdefy/operators-moment": "0.0.0-experimental-20260112140412",
|
|
61
|
+
"@lowdefy/operators-mql": "0.0.0-experimental-20260112140412",
|
|
62
|
+
"@lowdefy/operators-nunjucks": "0.0.0-experimental-20260112140412",
|
|
63
|
+
"@lowdefy/operators-uuid": "0.0.0-experimental-20260112140412",
|
|
64
|
+
"@lowdefy/operators-yaml": "0.0.0-experimental-20260112140412",
|
|
65
|
+
"@lowdefy/plugin-next-auth": "0.0.0-experimental-20260112140412",
|
|
66
|
+
"@sentry/nextjs": "8.53.0",
|
|
66
67
|
"chokidar": "3.5.3",
|
|
67
68
|
"dotenv": "16.3.1",
|
|
68
69
|
"next": "13.5.4",
|
|
@@ -74,6 +75,7 @@
|
|
|
74
75
|
"react-dom": "18.2.0",
|
|
75
76
|
"react-icons": "4.12.0",
|
|
76
77
|
"swr": "2.2.4",
|
|
78
|
+
"uuid": "13.0.0",
|
|
77
79
|
"yaml": "2.3.4",
|
|
78
80
|
"yargs": "17.7.2"
|
|
79
81
|
},
|
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-20260112140412",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -43,33 +43,34 @@
|
|
|
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/connection-axios-http": "0.0.0-experimental-
|
|
60
|
-
"@lowdefy/engine": "0.0.0-experimental-
|
|
61
|
-
"@lowdefy/helpers": "0.0.0-experimental-
|
|
62
|
-
"@lowdefy/layout": "0.0.0-experimental-
|
|
63
|
-
"@lowdefy/node-utils": "0.0.0-experimental-
|
|
64
|
-
"@lowdefy/operators-change-case": "0.0.0-experimental-
|
|
65
|
-
"@lowdefy/operators-diff": "0.0.0-experimental-
|
|
66
|
-
"@lowdefy/operators-js": "0.0.0-experimental-
|
|
67
|
-
"@lowdefy/operators-moment": "0.0.0-experimental-
|
|
68
|
-
"@lowdefy/operators-mql": "0.0.0-experimental-
|
|
69
|
-
"@lowdefy/operators-nunjucks": "0.0.0-experimental-
|
|
70
|
-
"@lowdefy/operators-uuid": "0.0.0-experimental-
|
|
71
|
-
"@lowdefy/operators-yaml": "0.0.0-experimental-
|
|
72
|
-
"@lowdefy/plugin-next-auth": "0.0.0-experimental-
|
|
46
|
+
"@lowdefy/actions-core": "0.0.0-experimental-20260112140412",
|
|
47
|
+
"@lowdefy/api": "0.0.0-experimental-20260112140412",
|
|
48
|
+
"@lowdefy/block-utils": "0.0.0-experimental-20260112140412",
|
|
49
|
+
"@lowdefy/blocks-aggrid": "0.0.0-experimental-20260112140412",
|
|
50
|
+
"@lowdefy/blocks-antd": "0.0.0-experimental-20260112140412",
|
|
51
|
+
"@lowdefy/blocks-basic": "0.0.0-experimental-20260112140412",
|
|
52
|
+
"@lowdefy/blocks-color-selectors": "0.0.0-experimental-20260112140412",
|
|
53
|
+
"@lowdefy/blocks-echarts": "0.0.0-experimental-20260112140412",
|
|
54
|
+
"@lowdefy/blocks-loaders": "0.0.0-experimental-20260112140412",
|
|
55
|
+
"@lowdefy/blocks-markdown": "0.0.0-experimental-20260112140412",
|
|
56
|
+
"@lowdefy/blocks-qr": "0.0.0-experimental-20260112140412",
|
|
57
|
+
"@lowdefy/build": "0.0.0-experimental-20260112140412",
|
|
58
|
+
"@lowdefy/client": "0.0.0-experimental-20260112140412",
|
|
59
|
+
"@lowdefy/connection-axios-http": "0.0.0-experimental-20260112140412",
|
|
60
|
+
"@lowdefy/engine": "0.0.0-experimental-20260112140412",
|
|
61
|
+
"@lowdefy/helpers": "0.0.0-experimental-20260112140412",
|
|
62
|
+
"@lowdefy/layout": "0.0.0-experimental-20260112140412",
|
|
63
|
+
"@lowdefy/node-utils": "0.0.0-experimental-20260112140412",
|
|
64
|
+
"@lowdefy/operators-change-case": "0.0.0-experimental-20260112140412",
|
|
65
|
+
"@lowdefy/operators-diff": "0.0.0-experimental-20260112140412",
|
|
66
|
+
"@lowdefy/operators-js": "0.0.0-experimental-20260112140412",
|
|
67
|
+
"@lowdefy/operators-moment": "0.0.0-experimental-20260112140412",
|
|
68
|
+
"@lowdefy/operators-mql": "0.0.0-experimental-20260112140412",
|
|
69
|
+
"@lowdefy/operators-nunjucks": "0.0.0-experimental-20260112140412",
|
|
70
|
+
"@lowdefy/operators-uuid": "0.0.0-experimental-20260112140412",
|
|
71
|
+
"@lowdefy/operators-yaml": "0.0.0-experimental-20260112140412",
|
|
72
|
+
"@lowdefy/plugin-next-auth": "0.0.0-experimental-20260112140412",
|
|
73
|
+
"@sentry/nextjs": "8.53.0",
|
|
73
74
|
"chokidar": "3.5.3",
|
|
74
75
|
"dotenv": "16.3.1",
|
|
75
76
|
"next": "13.5.4",
|
|
@@ -81,6 +82,7 @@
|
|
|
81
82
|
"react-dom": "18.2.0",
|
|
82
83
|
"react-icons": "4.12.0",
|
|
83
84
|
"swr": "2.2.4",
|
|
85
|
+
"uuid": "13.0.0",
|
|
84
86
|
"yaml": "2.3.4",
|
|
85
87
|
"yargs": "17.7.2"
|
|
86
88
|
},
|
package/pages/_app.js
CHANGED
|
@@ -20,16 +20,40 @@ 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 setSentryUser from '../lib/client/sentry/setSentryUser.js';
|
|
25
|
+
|
|
26
|
+
let loggerConfig = {};
|
|
27
|
+
try {
|
|
28
|
+
loggerConfig = require('../build/logger.json');
|
|
29
|
+
} catch {
|
|
30
|
+
// logger.json may not exist if Sentry is not configured
|
|
31
|
+
}
|
|
23
32
|
|
|
24
33
|
// Must be in _app due to next specifications.
|
|
25
34
|
import '../build/plugins/styles.less';
|
|
26
35
|
|
|
36
|
+
// Initialize Sentry client once on module load
|
|
37
|
+
initSentryClient({
|
|
38
|
+
sentryDsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
|
39
|
+
sentryConfig: loggerConfig.sentry,
|
|
40
|
+
});
|
|
41
|
+
|
|
27
42
|
function App({ Component }) {
|
|
28
43
|
const lowdefyRef = useRef({});
|
|
29
44
|
return (
|
|
30
45
|
<ErrorBoundary fullPage>
|
|
31
46
|
<Suspense fallback="">
|
|
32
|
-
<Auth>
|
|
47
|
+
<Auth>
|
|
48
|
+
{(auth) => {
|
|
49
|
+
// Set Sentry user context when auth changes
|
|
50
|
+
setSentryUser({
|
|
51
|
+
user: auth.session,
|
|
52
|
+
sentryConfig: loggerConfig.sentry,
|
|
53
|
+
});
|
|
54
|
+
return <Component auth={auth} lowdefy={lowdefyRef.current} />;
|
|
55
|
+
}}
|
|
56
|
+
</Auth>
|
|
33
57
|
</Suspense>
|
|
34
58
|
</ErrorBoundary>
|
|
35
59
|
);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 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 { logClientError } from '@lowdefy/api';
|
|
18
|
+
|
|
19
|
+
import apiWrapper from '../../lib/server/apiWrapper.js';
|
|
20
|
+
import captureSentryError from '../../lib/server/sentry/captureSentryError.js';
|
|
21
|
+
|
|
22
|
+
async function handler({ context, req, res }) {
|
|
23
|
+
if (req.method !== 'POST') {
|
|
24
|
+
throw new Error('Only POST requests are supported.');
|
|
25
|
+
}
|
|
26
|
+
const { configKey, message, name, pageId, timestamp } = req.body;
|
|
27
|
+
const response = await logClientError(context, {
|
|
28
|
+
configKey,
|
|
29
|
+
message,
|
|
30
|
+
name,
|
|
31
|
+
pageId,
|
|
32
|
+
timestamp,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Capture client error to Sentry (no-op if Sentry not configured)
|
|
36
|
+
captureSentryError({
|
|
37
|
+
error: new Error(message),
|
|
38
|
+
context: { ...context, pageId },
|
|
39
|
+
configLocation: response.source
|
|
40
|
+
? { source: response.source, config: response.config, link: response.link }
|
|
41
|
+
: null,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
res.status(200).json(response);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default apiWrapper(handler);
|