@lowdefy/server 5.6.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/{pages/_app.js → client/App.jsx} +26 -34
  2. package/client/Page.jsx +122 -0
  3. package/client/clientBuildImports.test.mjs +61 -0
  4. package/client/main.jsx +39 -0
  5. package/client/shouldReloadForBuild.js +48 -0
  6. package/client/shouldReloadForBuild.test.mjs +75 -0
  7. package/lib/build/app.js +8 -1
  8. package/lib/build/appMeta.js +8 -1
  9. package/lib/build/auth.js +8 -1
  10. package/lib/build/config.js +8 -1
  11. package/lib/build/i18n.js +8 -1
  12. package/lib/build/logger.js +8 -1
  13. package/lib/build/theme.js +8 -1
  14. package/lib/client/auth/{Auth.js → Auth.jsx} +10 -2
  15. package/lib/client/auth/{AuthConfigured.js → AuthConfigured.jsx} +21 -10
  16. package/lib/client/sentry/captureSentryError.js +1 -1
  17. package/lib/client/sentry/initSentryClient.js +1 -1
  18. package/lib/client/sentry/setSentryUser.js +1 -1
  19. package/lib/server/auth/{getAuthOptions.js → getAuthConfig.js} +7 -4
  20. package/{pages/api/auth/[...nextauth].js → lib/server/auth/session.js} +9 -15
  21. package/lib/server/auth/strategies.js +47 -0
  22. package/lib/server/log/createHandleError.js +10 -5
  23. package/lib/server/log/createHandleError.test.mjs +85 -0
  24. package/lib/server/log/createLogger.js +28 -1
  25. package/lib/server/log/createLogger.test.mjs +102 -0
  26. package/lib/server/log/logRequest.js +47 -45
  27. package/lib/server/log/maskCredentialKeys.js +37 -0
  28. package/lib/server/log/projectErrorForLog.js +55 -0
  29. package/lib/server/log/redactUrlQuery.js +75 -0
  30. package/lib/server/log/redactUrlQuery.test.mjs +73 -0
  31. package/lib/server/{auth/getServerSession.js → log/serializeErrorForLog.js} +5 -8
  32. package/lib/server/log/serializeErrorForLog.test.mjs +325 -0
  33. package/lib/server/scrubSecrets.js +21 -0
  34. package/lib/server/sentry/captureSentryError.js +13 -1
  35. package/lib/server/sentry/captureSentryError.test.mjs +104 -0
  36. package/lib/server/sentry/initSentry.js +17 -4
  37. package/lib/server/sentry/initSentry.test.mjs +237 -0
  38. package/lib/server/sentry/scrubEvent.js +29 -0
  39. package/lib/server/sentry/scrubEvent.test.mjs +88 -0
  40. package/lib/server/sentry/setSentryUser.js +1 -1
  41. package/lowdefy/createCustomPluginTypesMap.mjs +1 -0
  42. package/package.json +54 -42
  43. package/package.original.json +56 -44
  44. package/src/app.js +140 -0
  45. package/src/html/getAssets.js +45 -0
  46. package/src/html/renderPage.js +80 -0
  47. package/src/html/template.js +95 -0
  48. package/src/index.js +95 -0
  49. package/src/lib/getPathSegments.js +25 -0
  50. package/src/lib/safeScriptJson.js +38 -0
  51. package/src/middleware/apiContext.js +138 -0
  52. package/src/middleware/apiContext.test.mjs +118 -0
  53. package/src/middleware/errorHandler.js +54 -0
  54. package/src/middleware/sentry.js +33 -0
  55. package/{pages/api/agent/[...path].js → src/routes/agent.js} +20 -42
  56. package/src/routes/apiPage.js +40 -0
  57. package/src/routes/apiPage.test.mjs +59 -0
  58. package/src/routes/auth.js +38 -0
  59. package/{pages/api/client-error.js → src/routes/clientError.js} +14 -16
  60. package/src/routes/cron.js +44 -0
  61. package/src/routes/cronForward.js +47 -0
  62. package/src/routes/detached.js +47 -0
  63. package/src/routes/endpoints.js +71 -0
  64. package/src/routes/mcp.js +34 -0
  65. package/{pages/api/request/[...path].js → src/routes/request.js} +10 -10
  66. package/src/routes/usage.js +46 -0
  67. package/src/routes/websocket.js +46 -0
  68. package/vite.config.js +61 -0
  69. package/.eslintrc.yaml +0 -1
  70. package/lib/client/Page.js +0 -56
  71. package/lib/server/apiWrapper.js +0 -87
  72. package/lib/server/serverSidePropsWrapper.js +0 -66
  73. package/next.config.js +0 -34
  74. package/pages/404.js +0 -49
  75. package/pages/[[...pageId]].js +0 -85
  76. package/pages/_document.js +0 -108
  77. package/pages/api/endpoints/[...endpointId].js +0 -32
  78. package/pages/api/usage.js +0 -67
  79. /package/{postcss.config.js → postcss.config.cjs} +0 -0
@@ -14,30 +14,43 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import * as Sentry from '@sentry/nextjs';
17
+ import * as Sentry from '@sentry/node';
18
18
 
19
19
  import loggerConfig from '../../build/logger.js';
20
+ import scrubEvent from './scrubEvent.js';
20
21
 
22
+ // Returns true when Sentry was initialized, so the caller can log it through
23
+ // the structured logger — this module runs before the logger exists.
21
24
  function initSentryServer() {
22
25
  // No-op if SENTRY_DSN not set
23
26
  if (!process.env.SENTRY_DSN) {
24
- return;
27
+ return false;
25
28
  }
26
29
 
27
30
  const sentryConfig = loggerConfig.sentry || {};
28
31
 
29
32
  // No-op if server logging is explicitly disabled
30
33
  if (sentryConfig.server === false) {
31
- return;
34
+ return false;
32
35
  }
33
36
 
34
37
  Sentry.init({
35
38
  dsn: process.env.SENTRY_DSN,
36
39
  environment: sentryConfig.environment || process.env.NODE_ENV || 'production',
37
40
  tracesSampleRate: sentryConfig.tracesSampleRate ?? 0.1,
41
+ // Every path out to Sentry gets the log's secret scrub: error events with their cause
42
+ // chain, and the spans and breadcrumbs that carry outgoing URLs with their query strings.
43
+ beforeSend: (event) => scrubEvent(event),
44
+ beforeSendTransaction: (event) => scrubEvent(event),
45
+ beforeSendSpan: (span) => scrubEvent(span),
46
+ beforeBreadcrumb: (breadcrumb) => scrubEvent(breadcrumb),
47
+ // Incoming request bodies are attached to error events by default - an endpoint payload,
48
+ // a sign-in password - and no value scrub can recognise them. This replaces the default
49
+ // Http integration by name rather than adding a second one.
50
+ integrations: [Sentry.httpIntegration({ maxIncomingRequestBodySize: 'none' })],
38
51
  });
39
52
 
40
- console.log('Sentry enabled: server');
53
+ return true;
41
54
  }
42
55
 
43
56
  export default initSentryServer;
@@ -0,0 +1,237 @@
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 http from 'node:http';
18
+
19
+ import { jest } from '@jest/globals';
20
+ import * as RealSentry from '@sentry/node';
21
+
22
+ const secret = 'planted/secret+value=1';
23
+
24
+ const envelopes = [];
25
+ function createStubTransport() {
26
+ return {
27
+ send: async (envelope) => {
28
+ envelopes.push(envelope);
29
+ return {};
30
+ },
31
+ flush: async () => true,
32
+ };
33
+ }
34
+
35
+ const httpIntegrations = [];
36
+
37
+ // The real SDK with its network transport swapped for memory, so each test reads the exact
38
+ // event Sentry would send, after the hooks initSentry installs.
39
+ jest.unstable_mockModule('@sentry/node', () => ({
40
+ ...RealSentry,
41
+ init: (options) => RealSentry.init({ ...options, transport: createStubTransport }),
42
+ httpIntegration: (options) => {
43
+ const integration = RealSentry.httpIntegration(options);
44
+ httpIntegrations.push(integration);
45
+ return integration;
46
+ },
47
+ }));
48
+ jest.unstable_mockModule('../../build/logger.js', () => ({ default: { sentry: {} } }));
49
+
50
+ process.env.SENTRY_DSN = 'https://public@o0.ingest.sentry.io/1';
51
+ process.env.LOWDEFY_SECRET_API_KEY = secret;
52
+
53
+ const { default: initSentryServer } = await import('./initSentry.js');
54
+
55
+ const enabled = initSentryServer();
56
+ const client = RealSentry.getClient();
57
+ const options = client.getOptions();
58
+
59
+ function sentEvents() {
60
+ return envelopes.flatMap(([, items]) =>
61
+ items.filter(([header]) => header.type === 'event').map(([, payload]) => payload)
62
+ );
63
+ }
64
+
65
+ beforeEach(() => {
66
+ envelopes.length = 0;
67
+ });
68
+
69
+ afterAll(async () => {
70
+ await RealSentry.close();
71
+ });
72
+
73
+ test('initSentry initializes Sentry when SENTRY_DSN is set', () => {
74
+ expect(enabled).toBe(true);
75
+ expect(client).toBeDefined();
76
+ });
77
+
78
+ test('initSentry installs exactly one Http integration, the one that attaches no request bodies', () => {
79
+ expect(httpIntegrations).toHaveLength(1);
80
+ const installedHttp = options.integrations.filter(({ name }) => name === 'Http');
81
+ expect(installedHttp).toHaveLength(1);
82
+ expect(installedHttp[0]).toBe(httpIntegrations[0]);
83
+ expect(client.getIntegrationByName('Http')).toBe(httpIntegrations[0]);
84
+ });
85
+
86
+ test('beforeSend replaces a planted secret in an exception value, a cause-chain exception value and extra', () => {
87
+ const event = {
88
+ event_id: 'a'.repeat(32),
89
+ level: 'error',
90
+ exception: {
91
+ values: [
92
+ {
93
+ type: 'AxiosError',
94
+ value: `connect failed for key ${secret}`,
95
+ mechanism: { type: 'chained', source: 'cause', exception_id: 1, parent_id: 0 },
96
+ },
97
+ {
98
+ type: 'RequestError',
99
+ value: `Request failed: ${encodeURIComponent(secret)}`,
100
+ stacktrace: { frames: [{ filename: 'app.js', function: 'handler', lineno: 12 }] },
101
+ mechanism: { type: 'generic', handled: true, exception_id: 0 },
102
+ },
103
+ ],
104
+ },
105
+ extra: {
106
+ configKey: 'key-1',
107
+ error: { name: 'RequestError', message: `failed with ${secret}`, received: [secret] },
108
+ },
109
+ tags: { requestId: 'req-1' },
110
+ };
111
+ const scrubbed = options.beforeSend(event, {});
112
+ expect(JSON.stringify(scrubbed)).not.toContain(secret);
113
+ expect(JSON.stringify(scrubbed)).not.toContain(encodeURIComponent(secret));
114
+ expect(scrubbed.exception.values[0].value).toEqual('connect failed for key [REDACTED]');
115
+ expect(scrubbed.exception.values[1].value).toEqual('Request failed: [REDACTED]');
116
+ expect(scrubbed.exception.values[1].stacktrace).toEqual(event.exception.values[1].stacktrace);
117
+ expect(scrubbed.extra).toEqual({
118
+ configKey: 'key-1',
119
+ error: { name: 'RequestError', message: 'failed with [REDACTED]', received: ['[REDACTED]'] },
120
+ });
121
+ expect(scrubbed.tags).toEqual({ requestId: 'req-1' });
122
+ });
123
+
124
+ test('beforeSend scrubs an exception captured through the SDK, cause chain included', async () => {
125
+ const cause = new Error(`upstream rejected ${secret}`);
126
+ RealSentry.captureException(new Error('Request failed.', { cause }), {
127
+ extra: { detail: `sent ${secret}` },
128
+ });
129
+ await RealSentry.flush();
130
+ const events = sentEvents();
131
+ expect(events).toHaveLength(1);
132
+ expect(JSON.stringify(events[0])).not.toContain(secret);
133
+ const values = events[0].exception.values.map(({ value }) => value);
134
+ expect(values).toEqual(['upstream rejected [REDACTED]', 'Request failed.']);
135
+ expect(events[0].extra.detail).toEqual('sent [REDACTED]');
136
+ });
137
+
138
+ test('beforeBreadcrumb replaces a planted secret in a breadcrumb URL', () => {
139
+ const breadcrumb = {
140
+ type: 'http',
141
+ category: 'http',
142
+ level: 'info',
143
+ data: {
144
+ url: `https://api.example.com/v1/items?api_key=${secret}`,
145
+ 'http.method': 'GET',
146
+ status_code: 200,
147
+ },
148
+ timestamp: 1700000000,
149
+ };
150
+ expect(options.beforeBreadcrumb(breadcrumb)).toEqual({
151
+ ...breadcrumb,
152
+ data: {
153
+ url: 'https://api.example.com/v1/items?api_key=[REDACTED]',
154
+ 'http.method': 'GET',
155
+ status_code: 200,
156
+ },
157
+ });
158
+ });
159
+
160
+ function createFetchSpan() {
161
+ return {
162
+ span_id: 'b'.repeat(16),
163
+ trace_id: 'c'.repeat(32),
164
+ parent_span_id: 'd'.repeat(16),
165
+ op: 'http.client',
166
+ description: `GET https://api.example.com/v1/items?api_key=${secret}`,
167
+ origin: 'auto.http.otel.node_fetch',
168
+ start_timestamp: 1700000000,
169
+ timestamp: 1700000001,
170
+ status: 'ok',
171
+ data: {
172
+ 'sentry.op': 'http.client',
173
+ 'http.method': 'GET',
174
+ url: `https://api.example.com/v1/items?api_key=${secret}`,
175
+ 'http.url': `https://api.example.com/v1/items?api_key=${secret}`,
176
+ 'http.query': `?api_key=${secret}`,
177
+ 'http.response.status_code': 200,
178
+ },
179
+ };
180
+ }
181
+
182
+ test('beforeSendSpan replaces a planted secret in a fetch span http.url and http.query', () => {
183
+ const scrubbed = options.beforeSendSpan(createFetchSpan());
184
+ expect(JSON.stringify(scrubbed)).not.toContain(secret);
185
+ expect(scrubbed.data['http.url']).toEqual('https://api.example.com/v1/items?api_key=[REDACTED]');
186
+ expect(scrubbed.data['http.query']).toEqual('?api_key=[REDACTED]');
187
+ expect(scrubbed.data['http.response.status_code']).toEqual(200);
188
+ });
189
+
190
+ test('beforeSendTransaction replaces a planted secret in a fetch span http.url and http.query', () => {
191
+ const transaction = {
192
+ type: 'transaction',
193
+ transaction: 'POST /api/endpoints/fetch-items',
194
+ start_timestamp: 1700000000,
195
+ timestamp: 1700000002,
196
+ contexts: {
197
+ trace: { span_id: 'd'.repeat(16), trace_id: 'c'.repeat(32), op: 'http.server' },
198
+ },
199
+ spans: [createFetchSpan()],
200
+ };
201
+ const scrubbed = options.beforeSendTransaction(transaction, {});
202
+ expect(JSON.stringify(scrubbed)).not.toContain(secret);
203
+ expect(scrubbed.spans[0].data['http.url']).toEqual(
204
+ 'https://api.example.com/v1/items?api_key=[REDACTED]'
205
+ );
206
+ expect(scrubbed.spans[0].data['http.query']).toEqual('?api_key=[REDACTED]');
207
+ expect(scrubbed.contexts).toEqual(transaction.contexts);
208
+ });
209
+
210
+ test('an error event captured during a request with a JSON body carries no request data', async () => {
211
+ const server = http.createServer((req, res) => {
212
+ const chunks = [];
213
+ req.on('data', (chunk) => chunks.push(chunk));
214
+ req.on('end', () => {
215
+ RealSentry.captureException(new Error('Endpoint failed.'));
216
+ res.end('{}');
217
+ });
218
+ });
219
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
220
+ const { port } = server.address();
221
+ try {
222
+ const response = await fetch(`http://127.0.0.1:${port}/api/endpoints/sign-in`, {
223
+ method: 'POST',
224
+ headers: { 'content-type': 'application/json' },
225
+ body: JSON.stringify({ email: 'user@example.com', password: 'planted-user-password' }),
226
+ });
227
+ await response.text();
228
+ } finally {
229
+ await new Promise((resolve) => server.close(resolve));
230
+ }
231
+ await RealSentry.flush();
232
+ const events = sentEvents();
233
+ expect(events).toHaveLength(1);
234
+ expect(events[0].request.url).toMatch(/\/api\/endpoints\/sign-in$/);
235
+ expect(events[0].request.data).toBeUndefined();
236
+ expect(JSON.stringify(events[0])).not.toContain('planted-user-password');
237
+ });
@@ -0,0 +1,29 @@
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 { mapPlainValues, type } from '@lowdefy/helpers';
18
+
19
+ import scrubSecrets from '../scrubSecrets.js';
20
+
21
+ // Class instances are left as they are: Sentry normalizes those before beforeSend
22
+ // runs, so the event hook still reaches their strings. Breadcrumbs reach
23
+ // beforeBreadcrumb un-normalized - a console breadcrumb holds the logged arguments
24
+ // as they are - so the walk must be cycle-safe, which mapPlainValues is.
25
+ function scrubEvent(value) {
26
+ return mapPlainValues(value, (item) => (type.isString(item) ? scrubSecrets(item) : item));
27
+ }
28
+
29
+ export default scrubEvent;
@@ -0,0 +1,88 @@
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
+ const secret = 'planted/secret+value=1';
18
+ const shortSecret = 'short77';
19
+
20
+ process.env.LOWDEFY_SECRET_API_KEY = secret;
21
+ process.env.LOWDEFY_SECRET_SHORT = shortSecret;
22
+
23
+ const { default: scrubEvent } = await import('./scrubEvent.js');
24
+
25
+ test('scrubEvent replaces a planted secret in strings at every depth of objects and arrays', () => {
26
+ expect(
27
+ scrubEvent({
28
+ message: `failed ${secret}`,
29
+ nested: { list: [`a ${secret}`, { deeper: [[secret]] }] },
30
+ })
31
+ ).toEqual({
32
+ message: 'failed [REDACTED]',
33
+ nested: { list: ['a [REDACTED]', { deeper: [['[REDACTED]']] }] },
34
+ });
35
+ });
36
+
37
+ test('scrubEvent leaves keys as they are, even a key holding a secret', () => {
38
+ expect(scrubEvent({ [secret]: 'value' })).toEqual({ [secret]: 'value' });
39
+ });
40
+
41
+ test('scrubEvent returns a copy and does not mutate the value it scrubs', () => {
42
+ const event = { extra: { detail: secret }, values: [secret] };
43
+ const scrubbed = scrubEvent(event);
44
+ expect(scrubbed).not.toBe(event);
45
+ expect(scrubbed.extra).not.toBe(event.extra);
46
+ expect(event).toEqual({ extra: { detail: secret }, values: [secret] });
47
+ });
48
+
49
+ test('scrubEvent returns numbers, booleans, null and undefined unchanged', () => {
50
+ expect(scrubEvent({ a: 1, b: true, c: null, d: undefined })).toEqual({
51
+ a: 1,
52
+ b: true,
53
+ c: null,
54
+ d: undefined,
55
+ });
56
+ expect(scrubEvent(null)).toBeNull();
57
+ });
58
+
59
+ test('scrubEvent scrubs a top-level string', () => {
60
+ expect(scrubEvent(`value ${secret}`)).toEqual('value [REDACTED]');
61
+ });
62
+
63
+ test('scrubEvent leaves a secret shorter than 8 characters alone', () => {
64
+ expect(scrubEvent({ detail: `value ${shortSecret}` })).toEqual({
65
+ detail: `value ${shortSecret}`,
66
+ });
67
+ });
68
+
69
+ test('scrubEvent returns class instances unchanged', () => {
70
+ class Client {
71
+ constructor() {
72
+ this.detail = secret;
73
+ }
74
+ }
75
+ const client = new Client();
76
+ const date = new Date(0);
77
+ const scrubbed = scrubEvent({ client, date });
78
+ expect(scrubbed.client).toBe(client);
79
+ expect(scrubbed.date).toBe(date);
80
+ });
81
+
82
+ test('scrubEvent copies a cycle as a cycle', () => {
83
+ const data = { message: secret };
84
+ data.self = data;
85
+ const scrubbed = scrubEvent({ data });
86
+ expect(scrubbed.data.message).toEqual('[REDACTED]');
87
+ expect(scrubbed.data.self).toBe(scrubbed.data);
88
+ });
@@ -14,7 +14,7 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import * as Sentry from '@sentry/nextjs';
17
+ import * as Sentry from '@sentry/node';
18
18
 
19
19
  function setSentryUser({ user, sentryConfig }) {
20
20
  // No-op if Sentry not initialized (DSN not set)
@@ -55,6 +55,7 @@ async function createCustomPluginTypesMap({ directories }) {
55
55
  server: {},
56
56
  },
57
57
  requests: {},
58
+ websockets: {},
58
59
  };
59
60
 
60
61
  const pluginDefinitions = await getPluginDefinitions({ directories });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/server",
3
- "version": "5.6.0",
3
+ "version": "6.1.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "",
6
6
  "homepage": "https://lowdefy.com",
@@ -28,68 +28,80 @@
28
28
  "files": [
29
29
  "lib/*",
30
30
  "lowdefy/*",
31
- "pages/*",
32
31
  "public_default/*",
33
- "next.config.js",
34
- "postcss.config.js",
35
32
  "package.original.json",
36
33
  ".eslintrc.yaml",
37
- ".npmrc"
34
+ ".npmrc",
35
+ "src/*",
36
+ "client/*",
37
+ "vite.config.js",
38
+ "postcss.config.cjs"
38
39
  ],
39
40
  "dependencies": {
40
41
  "@ant-design/cssinjs": "2.1.2",
41
42
  "@ant-design/x": "2.7.0",
42
- "@lowdefy/actions-core": "5.6.0",
43
- "@lowdefy/api": "5.6.0",
44
- "@lowdefy/block-utils": "5.6.0",
45
- "@lowdefy/blocks-antd": "5.6.0",
46
- "@lowdefy/blocks-antd-x": "5.6.0",
47
- "@lowdefy/blocks-basic": "5.6.0",
48
- "@lowdefy/blocks-loaders": "5.6.0",
49
- "@lowdefy/blocks-tiptap": "5.6.0",
50
- "@lowdefy/client": "5.6.0",
51
- "@lowdefy/errors": "5.6.0",
52
- "@lowdefy/helpers": "5.6.0",
53
- "@lowdefy/layout": "5.6.0",
54
- "@lowdefy/logger": "5.6.0",
55
- "@lowdefy/node-utils": "5.6.0",
56
- "@lowdefy/operators-js": "5.6.0",
57
- "@lowdefy/plugin-next-auth": "5.6.0",
58
- "@sentry/nextjs": "8.53.0",
59
- "@tailwindcss/postcss": "4.2.1",
43
+ "@auth/core": "0.41.3",
44
+ "@hono/auth-js": "1.1.1",
45
+ "@hono/mcp": "0.3.0",
46
+ "@hono/node-server": "2.0.10",
47
+ "@lowdefy/actions-core": "6.1.0",
48
+ "@lowdefy/api": "6.1.0",
49
+ "@lowdefy/block-utils": "6.1.0",
50
+ "@lowdefy/blocks-aggrid": "6.1.0",
51
+ "@lowdefy/blocks-antd": "6.1.0",
52
+ "@lowdefy/blocks-antd-x": "6.1.0",
53
+ "@lowdefy/blocks-basic": "6.1.0",
54
+ "@lowdefy/blocks-echarts": "6.1.0",
55
+ "@lowdefy/blocks-files": "6.1.0",
56
+ "@lowdefy/blocks-loaders": "6.1.0",
57
+ "@lowdefy/blocks-markdown": "6.1.0",
58
+ "@lowdefy/blocks-tiptap": "6.1.0",
59
+ "@lowdefy/client": "6.1.0",
60
+ "@lowdefy/errors": "6.1.0",
61
+ "@lowdefy/helpers": "6.1.0",
62
+ "@lowdefy/layout": "6.1.0",
63
+ "@lowdefy/logger": "6.1.0",
64
+ "@lowdefy/node-utils": "6.1.0",
65
+ "@lowdefy/operators-js": "6.1.0",
66
+ "@lowdefy/plugin-next-auth": "6.1.0",
67
+ "@lowdefy/websockets-core": "6.1.0",
68
+ "@sentry/browser": "10.57.0",
69
+ "@sentry/node": "10.57.0",
70
+ "@sentry/vite-plugin": "5.3.0",
71
+ "@tailwindcss/postcss": "4.3.1",
72
+ "@vitejs/plugin-react": "6.0.2",
60
73
  "antd": "6.3.1",
61
- "dayjs": "1.11.19",
62
- "next": "16.1.6",
63
- "next-auth": "4.24.10",
64
- "pino": "8.16.2",
74
+ "dayjs": "1.11.20",
75
+ "hono": "4.13.5",
76
+ "pino": "10.3.1",
65
77
  "react": "18.2.0",
66
78
  "react-dom": "18.2.0",
67
79
  "react-icons": "5.6.0",
68
- "tailwindcss": "4.2.1",
69
- "uuid": "13.0.0"
80
+ "tailwindcss": "4.3.1",
81
+ "uuid": "14.0.0",
82
+ "vite": "8.0.16",
83
+ "ws": "8.21.0"
70
84
  },
71
85
  "devDependencies": {
72
- "@lowdefy/build": "5.6.0",
73
- "@next/eslint-plugin-next": "16.1.6",
74
- "@tailwindcss/postcss": "4.2.1",
75
- "tailwindcss": "4.2.1",
76
- "webpack": "5.94.0",
77
- "yaml": "2.3.4",
78
- "yargs": "17.7.2"
86
+ "@lowdefy/build": "6.1.0",
87
+ "@tailwindcss/postcss": "4.3.1",
88
+ "jest": "28.1.3",
89
+ "tailwindcss": "4.3.1",
90
+ "yaml": "2.9.0",
91
+ "yargs": "18.0.0"
79
92
  },
80
93
  "engines": {
81
- "node": ">=18"
94
+ "node": ">=22"
82
95
  },
83
96
  "publishConfig": {
84
97
  "access": "public"
85
98
  },
99
+ "type": "module",
86
100
  "scripts": {
87
101
  "build": "cp package.json package.original.json || copy package.json package.original.json",
102
+ "build:client": "vite build",
88
103
  "build:lowdefy": "node lowdefy/build.mjs",
89
- "build:next": "next build",
90
- "dev": "next dev",
91
- "start": "next start",
92
- "lint": "next lint",
93
- "next": "next"
104
+ "start": "node src/index.js",
105
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
94
106
  }
95
107
  }