@lowdefy/server-dev 0.0.0-experimental-20260220143146 → 0.0.0-experimental-20260220145512

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.
Files changed (37) hide show
  1. package/lib/client/BuildErrorPage.js +3 -4
  2. package/lib/client/Page.js +0 -4
  3. package/lib/client/RestartingPage.js +0 -2
  4. package/lib/client/sentry/captureSentryError.js +43 -0
  5. package/lib/client/sentry/initSentryClient.js +55 -0
  6. package/lib/client/sentry/setSentryUser.js +41 -0
  7. package/lib/client/utils/request.js +3 -1
  8. package/lib/client/utils/usePageConfig.js +0 -3
  9. package/lib/server/apiWrapper.js +10 -14
  10. package/lib/server/auth/getMockSession.js +1 -1
  11. package/lib/server/jitPageBuilder.js +1 -17
  12. package/lib/server/log/createLogger.js +2 -2
  13. package/lib/server/log/logError.js +56 -0
  14. package/lib/server/pageCache.mjs +13 -0
  15. package/lib/server/pageCache.test.mjs +39 -0
  16. package/lib/server/sentry/captureSentryError.js +57 -0
  17. package/lib/server/sentry/initSentry.js +44 -0
  18. package/lib/server/sentry/setSentryUser.js +46 -0
  19. package/manager/getContext.mjs +4 -8
  20. package/manager/processes/installPlugins.mjs +2 -2
  21. package/manager/processes/lowdefyBuild.mjs +2 -2
  22. package/manager/processes/nextBuild.mjs +6 -4
  23. package/manager/processes/restartServer.mjs +2 -2
  24. package/manager/processes/shutdownServer.mjs +1 -1
  25. package/manager/utils/checkPortAvailable.mjs +4 -5
  26. package/manager/utils/getLowdefyVersion.mjs +12 -6
  27. package/manager/watchers/lowdefyBuildWatcher.mjs +24 -4
  28. package/manager/watchers/nextBuildWatcher.mjs +2 -12
  29. package/next.config.js +10 -1
  30. package/package.json +30 -30
  31. package/package.original.json +30 -30
  32. package/pages/_app.js +17 -3
  33. package/pages/api/client-error.js +10 -15
  34. package/pages/api/page/[pageId].js +3 -11
  35. package/pages/api/request/[pageId]/[requestId].js +1 -4
  36. package/lib/client/InstallingPluginsPage.js +0 -50
  37. package/lib/server/log/createHandleError.js +0 -47
@@ -21,7 +21,7 @@ const typeColors = {
21
21
  ConfigWarning: '#d48806',
22
22
  PluginError: '#531dab',
23
23
  ServiceError: '#096dd9',
24
- LowdefyInternalError: '#cf1322',
24
+ LowdefyError: '#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', fontFamily: 'monospace' }}>{message}</p>
52
+ <p style={{ fontSize: 14, margin: '4px 0' }}>{message}</p>
53
53
  {source && <p style={{ fontSize: 13, color: '#8c8c8c', margin: 0 }}>{source}</p>}
54
54
  </div>
55
55
  );
@@ -67,8 +67,7 @@ const BuildErrorPage = ({ errors, message, source }) => {
67
67
  flexDirection: 'column',
68
68
  alignItems: 'center',
69
69
  justifyContent: 'center',
70
- fontFamily:
71
- "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
70
+ fontFamily: 'monospace',
72
71
  padding: '0 24px',
73
72
  }}
74
73
  >
@@ -18,7 +18,6 @@ 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';
22
21
  import RestartingPage from './RestartingPage.js';
23
22
  import usePageConfig from './utils/usePageConfig.js';
24
23
 
@@ -48,9 +47,6 @@ const Page = ({
48
47
  />
49
48
  );
50
49
  }
51
- if (pageConfig.installing) {
52
- return <InstallingPluginsPage packages={pageConfig.packages} />;
53
- }
54
50
  if (resetContext.restarting) {
55
51
  return <RestartingPage />;
56
52
  }
@@ -26,8 +26,6 @@ const RestartingPage = () => {
26
26
  flexDirection: 'column',
27
27
  alignItems: 'center',
28
28
  justifyContent: 'center',
29
- fontFamily:
30
- "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
31
29
  }}
32
30
  >
33
31
  <div
@@ -0,0 +1,43 @@
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;
@@ -0,0 +1,55 @@
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;
@@ -0,0 +1,41 @@
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;
@@ -27,7 +27,9 @@ async function request({ url, method = 'GET', body }) {
27
27
  }
28
28
  if (!res.ok) {
29
29
  const body = await res.json();
30
- throw new Error(body?.['~e']?.message ?? body?.message ?? 'Request error');
30
+ console.log(res);
31
+ console.log(body);
32
+ throw new Error(body.message || 'Request error');
31
33
  }
32
34
  return res.json();
33
35
  }
@@ -43,9 +43,6 @@ async function fetchPageConfig(url) {
43
43
  if (data?.buildError) {
44
44
  return data;
45
45
  }
46
- if (data?.installing) {
47
- return data;
48
- }
49
46
  if (!res.ok) {
50
47
  throw new Error(data.message || 'Request error');
51
48
  }
@@ -17,7 +17,6 @@ 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';
21
20
  import { v4 as uuid } from 'uuid';
22
21
 
23
22
  import config from '../build/config.js';
@@ -25,11 +24,13 @@ import connections from '../../build/plugins/connections.js';
25
24
  import createLogger from './log/createLogger.js';
26
25
  import fileCache from './fileCache.js';
27
26
  import getServerSession from './auth/getServerSession.js';
28
- import createHandleError from './log/createHandleError.js';
27
+ import logError from './log/logError.js';
29
28
  import logRequest from './log/logRequest.js';
30
29
  import operators from '../../build/plugins/operators/server.js';
31
30
  import staticJsMap from '../../build/plugins/operators/serverJsMap.js';
32
31
  import getAuthOptions from './auth/getAuthOptions.js';
32
+ import loggerConfig from '../build/logger.js';
33
+ import setSentryUser from './sentry/setSentryUser.js';
33
34
 
34
35
  const secrets = getSecretsFromEnv();
35
36
 
@@ -72,9 +73,6 @@ function apiWrapper(handler) {
72
73
  fileCache,
73
74
  headers: req?.headers,
74
75
  jsMap,
75
- handleError: async (err) => {
76
- console.error(err);
77
- },
78
76
  logger: console,
79
77
  operators,
80
78
  req,
@@ -83,10 +81,14 @@ function apiWrapper(handler) {
83
81
  };
84
82
  try {
85
83
  context.logger = createLogger({ rid: context.rid });
86
- context.handleError = createHandleError({ context });
87
84
  context.authOptions = getAuthOptions(context);
88
85
  if (!req.url.startsWith('/api/auth')) {
89
86
  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
+ });
90
92
  }
91
93
  createApiContext(context);
92
94
  logRequest({ context });
@@ -95,14 +97,8 @@ function apiWrapper(handler) {
95
97
  // TODO: Log response time?
96
98
  return response;
97
99
  } catch (error) {
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);
100
+ await logError({ error, context });
101
+ res.status(500).json({ name: error.name, message: error.message });
106
102
  }
107
103
  };
108
104
  }
@@ -28,7 +28,7 @@ async function getMockSession() {
28
28
  try {
29
29
  mockUser = JSON.parse(mockUserJson);
30
30
  } catch (error) {
31
- throw new Error('Invalid JSON in LOWDEFY_DEV_USER environment variable.', { cause: error });
31
+ throw new Error(`Invalid JSON in LOWDEFY_DEV_USER environment variable: ${error.message}`);
32
32
  }
33
33
  } else {
34
34
  mockUser = authJson.dev?.mockUser;
@@ -85,14 +85,10 @@ 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
-
90
88
  cachedBuildContext = createContext({
91
- customTypesMap,
92
89
  directories: {
93
90
  build: buildDirectory,
94
91
  config: configDirectory,
95
- server: path.resolve(buildDirectory, '..'),
96
92
  },
97
93
  logger: jitLogger,
98
94
  stage: 'dev',
@@ -107,11 +103,6 @@ function getBuildContext(buildDirectory, configDirectory) {
107
103
  cachedBuildContext.connectionIds.add(id);
108
104
  }
109
105
 
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
-
115
106
  return cachedBuildContext;
116
107
  }
117
108
 
@@ -135,18 +126,11 @@ async function buildPageIfNeeded({ pageId, buildDirectory, configDirectory }) {
135
126
  try {
136
127
  const context = getBuildContext(buildDirectory, configDirectory);
137
128
  const startTime = Date.now();
138
- const result = await buildPageJit({
129
+ await buildPageJit({
139
130
  pageId,
140
131
  pageRegistry: registry,
141
132
  context,
142
133
  });
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
- }
150
134
  pageCache.markCompiled(pageId);
151
135
  jitLogger.info(`Built page "${pageId}" in ${Date.now() - startTime}ms.`);
152
136
  return true;
@@ -14,7 +14,7 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { createNodeLogger } from '@lowdefy/logger/node';
17
+ import { createNodeLogger, wrapErrorLogger } 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 logger.child(metadata);
26
+ return wrapErrorLogger(logger.child(metadata));
27
27
  }
28
28
 
29
29
  export default createLogger;
@@ -0,0 +1,56 @@
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;
@@ -86,6 +86,19 @@ class PageCache {
86
86
  }
87
87
  }
88
88
 
89
+ invalidateByFiles(changedFiles, fileDependencyMap) {
90
+ const affectedPages = new Set();
91
+ for (const filePath of changedFiles) {
92
+ const pageIds = fileDependencyMap.get(filePath);
93
+ if (pageIds) {
94
+ for (const pageId of pageIds) {
95
+ affectedPages.add(pageId);
96
+ }
97
+ }
98
+ }
99
+ this.invalidatePages(affectedPages);
100
+ return affectedPages;
101
+ }
89
102
  }
90
103
 
91
104
  export default PageCache;
@@ -47,6 +47,45 @@ test('invalidatePages removes specific pages', () => {
47
47
  expect(cache.isCompiled('settings')).toBe(false);
48
48
  });
49
49
 
50
+ test('invalidateByFiles returns affected pages and invalidates them', () => {
51
+ const cache = new PageCache();
52
+ cache.markCompiled('home');
53
+ cache.markCompiled('dashboard');
54
+
55
+ const fileDependencyMap = new Map([
56
+ ['pages/home/blocks.yaml', new Set(['home'])],
57
+ ['shared/layout.yaml', new Set(['home', 'dashboard'])],
58
+ ]);
59
+
60
+ const affected = cache.invalidateByFiles(['pages/home/blocks.yaml'], fileDependencyMap);
61
+ expect(affected).toEqual(new Set(['home']));
62
+ expect(cache.isCompiled('home')).toBe(false);
63
+ expect(cache.isCompiled('dashboard')).toBe(true);
64
+ });
65
+
66
+ test('invalidateByFiles handles shared files affecting multiple pages', () => {
67
+ const cache = new PageCache();
68
+ cache.markCompiled('home');
69
+ cache.markCompiled('dashboard');
70
+
71
+ const fileDependencyMap = new Map([['shared/layout.yaml', new Set(['home', 'dashboard'])]]);
72
+
73
+ const affected = cache.invalidateByFiles(['shared/layout.yaml'], fileDependencyMap);
74
+ expect(affected).toEqual(new Set(['home', 'dashboard']));
75
+ expect(cache.isCompiled('home')).toBe(false);
76
+ expect(cache.isCompiled('dashboard')).toBe(false);
77
+ });
78
+
79
+ test('invalidateByFiles returns empty set for unknown files', () => {
80
+ const cache = new PageCache();
81
+ cache.markCompiled('home');
82
+
83
+ const fileDependencyMap = new Map();
84
+ const affected = cache.invalidateByFiles(['unknown.yaml'], fileDependencyMap);
85
+ expect(affected).toEqual(new Set());
86
+ expect(cache.isCompiled('home')).toBe(true);
87
+ });
88
+
50
89
  test('acquireBuildLock returns true for first request', async () => {
51
90
  const cache = new PageCache();
52
91
  const shouldBuild = await cache.acquireBuildLock('home');
@@ -0,0 +1,57 @@
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;
@@ -0,0 +1,44 @@
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 };
@@ -0,0 +1,46 @@
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;
@@ -19,8 +19,7 @@ import path from 'path';
19
19
  import yargs from 'yargs';
20
20
  import { hideBin } from 'yargs/helpers';
21
21
 
22
- import pino from 'pino';
23
- import { createNodeLogger } from '@lowdefy/logger/node';
22
+ import { createDevLogger as createLogger } from '@lowdefy/logger/dev';
24
23
  import checkMockUserWarning from './processes/checkMockUserWarning.mjs';
25
24
  import initialBuild from './processes/initialBuild.mjs';
26
25
  import installPlugins from './processes/installPlugins.mjs';
@@ -49,12 +48,7 @@ async function getContext() {
49
48
  config: path.resolve(argv.configDirectory ?? env.LOWDEFY_DIRECTORY_CONFIG ?? process.cwd()),
50
49
  server: process.cwd(),
51
50
  },
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
- }),
51
+ logger: createLogger({ level: env.LOWDEFY_LOG_LEVEL }),
58
52
  options: {
59
53
  port: argv.port ?? env.PORT ?? 3000,
60
54
  refResolver: argv.refResolver ?? env.LOWDEFY_BUILD_REF_RESOLVER,
@@ -70,6 +64,7 @@ async function getContext() {
70
64
  // JIT build state
71
65
  pageCache: new PageCache(),
72
66
  pageRegistry: null,
67
+ fileDependencyMap: null,
73
68
  buildContext: null,
74
69
  };
75
70
 
@@ -84,6 +79,7 @@ async function getContext() {
84
79
  const result = await buildFn();
85
80
  if (result) {
86
81
  context.pageRegistry = result.pageRegistry;
82
+ context.fileDependencyMap = result.fileDependencyMap;
87
83
  context.buildContext = result.context;
88
84
  }
89
85
  };