@lowdefy/build 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.
- package/dist/build/buildAgents.js +61 -16
- package/dist/build/buildApi/buildApi.js +4 -1
- package/dist/build/buildApi/buildEndpoint.js +8 -2
- package/dist/build/buildApi/buildRoutine/countStepTypes.js +1 -1
- package/dist/build/buildApi/buildRoutine/setStepId.js +2 -0
- package/dist/build/buildApi/buildRoutine/validateStep.js +61 -0
- package/dist/build/buildApi/resolveEndpointSchedules.js +40 -0
- package/dist/build/buildApi/validateEndpoint.js +96 -1
- package/dist/build/buildApp.js +13 -20
- package/dist/build/buildAppMeta.js +138 -0
- package/dist/build/buildAuth/buildAgentAuth.js +14 -1
- package/dist/build/buildAuth/buildAuth.js +8 -3
- package/dist/build/buildAuth/buildAuthPlugins.js +6 -0
- package/dist/build/buildAuth/buildAuthStrategies.js +117 -0
- package/dist/build/buildAuth/buildWebsocketAuth.js +55 -0
- package/dist/build/buildAuth/getProtectedWebsockets.js +29 -0
- package/dist/build/buildAuth/getWebsocketRoles.js +39 -0
- package/dist/build/buildAuth/validateAuthConfig.js +15 -3
- package/dist/build/buildEnvironments.js +261 -0
- package/dist/build/buildImports/buildImportsDev.js +15 -0
- package/dist/build/buildImports/buildImportsProd.js +4 -1
- package/dist/build/buildJs/generateClientJsModule.js +27 -0
- package/dist/build/buildJs/writeJs.js +3 -5
- package/dist/build/buildLogger.js +22 -6
- package/dist/build/buildMcp.js +73 -0
- package/dist/build/buildModuleDefs.js +90 -48
- package/dist/build/buildModules.js +61 -14
- package/dist/build/buildNotifications.js +98 -0
- package/dist/build/buildPages/buildBlock/buildBlock.js +3 -1
- package/dist/build/buildPages/buildBlock/buildDynamicBlock.js +131 -0
- package/dist/build/buildPages/buildBlock/buildEvents.js +186 -21
- package/dist/build/buildPages/buildBlock/buildRequests.js +9 -0
- package/dist/build/buildPages/buildBlock/normalizeClassAndStyles.js +2 -11
- package/dist/build/buildPages/buildBlock/setBlockId.js +5 -2
- package/dist/build/buildPages/buildBlock/validateBlock.js +7 -0
- package/dist/build/buildPages/buildPage.js +27 -3
- package/dist/build/buildPages/buildSubscriptions.js +145 -0
- package/dist/build/buildPages/validateDynamicBlockRefs.js +30 -0
- package/dist/build/buildPages/validateStateReferences.js +4 -2
- package/dist/build/buildPages/validateWebsocketRefs.js +30 -0
- package/dist/build/buildRefs/buildRefs.js +21 -12
- package/dist/build/buildRefs/cloneWithMarkers.js +57 -0
- package/dist/build/buildRefs/deferredRegions.js +80 -0
- package/dist/build/buildRefs/deferredRegistry.js +292 -0
- package/dist/build/buildRefs/getModuleRefContent.js +21 -6
- package/dist/build/buildRefs/getUserJavascriptFunction.js +1 -1
- package/dist/build/buildRefs/parseRefContent.js +24 -0
- package/dist/build/buildRefs/{evaluateStaticOperators.js → precomputeRuntimeOperators.js} +7 -6
- package/dist/build/buildRefs/walker.js +471 -207
- package/dist/build/buildTypes.js +22 -1
- package/dist/build/buildWebsockets.js +99 -0
- package/dist/build/collectPageContent.js +7 -1
- package/dist/build/copyAgentFileSystems.js +2 -2
- package/dist/build/full/buildPages.js +16 -0
- package/dist/build/full/updateServerPackageJson.js +21 -0
- package/dist/build/jit/buildPageJit.js +46 -6
- package/dist/build/jit/collectSkeletonSourceFiles.js +14 -0
- package/dist/build/jit/pageContentKeys.js +1 -0
- package/dist/build/jit/shallowBuild.js +84 -3
- package/dist/build/jit/writePageJit.js +11 -8
- package/dist/build/registerModules.js +125 -24
- package/dist/build/validateCallAgentSteps.js +58 -0
- package/dist/build/validateConfig.js +6 -1
- package/dist/build/validateEnvironmentGuards.js +69 -0
- package/dist/build/validateRenderNotificationSteps.js +50 -0
- package/dist/build/writeApi.js +98 -1
- package/dist/build/writeMcp.js +21 -0
- package/dist/build/writeNotifications.js +30 -0
- package/dist/build/writePluginImports/importPluginModule.js +39 -0
- package/dist/build/writePluginImports/writeActionSchemaMap.js +6 -7
- package/dist/build/writePluginImports/writeAuthImports.js +4 -0
- package/dist/build/writePluginImports/writeAvailableTypes.js +99 -0
- package/dist/build/writePluginImports/writeBlockSchemaMap.js +10 -9
- package/dist/build/writePluginImports/writeConnectionSchemaMap.js +101 -0
- package/dist/build/writePluginImports/writeGlobalsCss.js +20 -10
- package/dist/build/writePluginImports/writeNotificationImports.js +31 -0
- package/dist/build/writePluginImports/writeOperatorSchemaMap.js +6 -7
- package/dist/build/writePluginImports/writePluginImports.js +18 -4
- package/dist/build/writePluginImports/writeWebsocketImports.js +22 -0
- package/dist/build/writeWebsockets.js +30 -0
- package/dist/createContext.js +6 -1
- package/dist/defaultPackages.js +7 -1
- package/dist/defaultTypesMap.js +688 -388
- package/dist/dynamic/buildDynamicBlocks.js +120 -0
- package/dist/index.js +81 -10
- package/dist/indexDev.js +4 -0
- package/dist/lowdefySchema.js +969 -6
- package/dist/scripts/generateDefaultTypes.js +5 -2
- package/dist/test-utils/expectTerminates.js +33 -0
- package/dist/test-utils/runBuild.js +15 -1
- package/dist/test-utils/runBuildForSnapshots.js +37 -4
- package/dist/test-utils/testContext.js +8 -2
- package/dist/utils/countOperators.js +5 -17
- package/dist/utils/createPluginTypesMap.js +21 -0
- package/dist/utils/getEnvironmentNames.js +26 -0
- package/dist/utils/logCollectedErrors.js +15 -9
- package/dist/utils/serializeBuildException.js +29 -0
- package/dist/utils/validateCronExpression.js +131 -0
- package/dist/utils/writeBuildArtifact.js +5 -2
- package/package.json +67 -60
- package/dist/build/writePluginImports/writeServerExternalPackages.js +0 -121
- /package/dist/build/{resolveModuleOperators.js → buildRefs/scopeMenuItemIds.js} +0 -0
|
@@ -26,17 +26,20 @@ async function generateDefaultTypesMap() {
|
|
|
26
26
|
adapters: {},
|
|
27
27
|
callbacks: {},
|
|
28
28
|
events: {},
|
|
29
|
-
providers: {}
|
|
29
|
+
providers: {},
|
|
30
|
+
strategies: {}
|
|
30
31
|
},
|
|
31
32
|
blockMetas: {},
|
|
32
33
|
blocks: {},
|
|
33
34
|
connections: {},
|
|
34
35
|
icons: {},
|
|
36
|
+
notifications: {},
|
|
35
37
|
operators: {
|
|
36
38
|
client: {},
|
|
37
39
|
server: {}
|
|
38
40
|
},
|
|
39
|
-
requests: {}
|
|
41
|
+
requests: {},
|
|
42
|
+
websockets: {}
|
|
40
43
|
};
|
|
41
44
|
for (const packageName of defaultPackages){
|
|
42
45
|
const { default: types } = await import(`${packageName}/types`);
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
*/ // Termination guard for cycle-sensitive tests: a cycle-detection regression
|
|
16
|
+
// that deadlocks should fail fast with an actionable message, not an opaque
|
|
17
|
+
// CI timeout. Passes the promise's settlement (value or rejection) through
|
|
18
|
+
// unchanged when it settles in time.
|
|
19
|
+
async function expectTerminates(promise, ms, hint) {
|
|
20
|
+
let timer;
|
|
21
|
+
const timeout = new Promise((_, reject)=>{
|
|
22
|
+
timer = setTimeout(()=>reject(new Error(`promise did not settle within ${ms}ms — ${hint}`)), ms);
|
|
23
|
+
});
|
|
24
|
+
try {
|
|
25
|
+
return await Promise.race([
|
|
26
|
+
promise,
|
|
27
|
+
timeout
|
|
28
|
+
]);
|
|
29
|
+
} finally{
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export default expectTerminates;
|
|
@@ -54,9 +54,15 @@ import createTestLogger from './createTestLogger.js';
|
|
|
54
54
|
Button: {
|
|
55
55
|
package: '@lowdefy/blocks-basic'
|
|
56
56
|
},
|
|
57
|
+
ClickableHtml: {
|
|
58
|
+
package: '@lowdefy/blocks-basic'
|
|
59
|
+
},
|
|
57
60
|
DangerousHtml: {
|
|
58
61
|
package: '@lowdefy/blocks-basic'
|
|
59
62
|
},
|
|
63
|
+
Dynamic: {
|
|
64
|
+
package: '@lowdefy/blocks-basic'
|
|
65
|
+
},
|
|
60
66
|
Html: {
|
|
61
67
|
package: '@lowdefy/blocks-basic'
|
|
62
68
|
},
|
|
@@ -142,7 +148,15 @@ import createTestLogger from './createTestLogger.js';
|
|
|
142
148
|
events: {},
|
|
143
149
|
providers: {
|
|
144
150
|
GoogleProvider: {
|
|
145
|
-
package: 'next-auth'
|
|
151
|
+
package: '@lowdefy/plugin-next-auth'
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
strategies: {
|
|
155
|
+
apiKey: {
|
|
156
|
+
package: '@lowdefy/plugin-next-auth'
|
|
157
|
+
},
|
|
158
|
+
jwt: {
|
|
159
|
+
package: '@lowdefy/plugin-next-auth'
|
|
146
160
|
}
|
|
147
161
|
}
|
|
148
162
|
},
|
|
@@ -87,9 +87,15 @@ import path from 'path';
|
|
|
87
87
|
Button: {
|
|
88
88
|
package: '@lowdefy/blocks-basic'
|
|
89
89
|
},
|
|
90
|
+
ClickableHtml: {
|
|
91
|
+
package: '@lowdefy/blocks-basic'
|
|
92
|
+
},
|
|
90
93
|
DangerousHtml: {
|
|
91
94
|
package: '@lowdefy/blocks-basic'
|
|
92
95
|
},
|
|
96
|
+
Dynamic: {
|
|
97
|
+
package: '@lowdefy/blocks-basic'
|
|
98
|
+
},
|
|
93
99
|
Html: {
|
|
94
100
|
package: '@lowdefy/blocks-basic'
|
|
95
101
|
},
|
|
@@ -280,6 +286,9 @@ import path from 'path';
|
|
|
280
286
|
}
|
|
281
287
|
},
|
|
282
288
|
connections: {
|
|
289
|
+
Anthropic: {
|
|
290
|
+
package: '@lowdefy/connection-anthropic'
|
|
291
|
+
},
|
|
283
292
|
AxiosHttp: {
|
|
284
293
|
package: '@lowdefy/connection-axios-http'
|
|
285
294
|
},
|
|
@@ -312,6 +321,12 @@ import path from 'path';
|
|
|
312
321
|
AxiosHttp: {
|
|
313
322
|
package: '@lowdefy/connection-axios-http'
|
|
314
323
|
},
|
|
324
|
+
GenerateObject: {
|
|
325
|
+
package: '@lowdefy/connection-anthropic'
|
|
326
|
+
},
|
|
327
|
+
GenerateText: {
|
|
328
|
+
package: '@lowdefy/connection-anthropic'
|
|
329
|
+
},
|
|
315
330
|
MongoDBFind: {
|
|
316
331
|
package: '@lowdefy/connection-mongodb'
|
|
317
332
|
},
|
|
@@ -370,6 +385,16 @@ import path from 'path';
|
|
|
370
385
|
package: '@lowdefy/plugin-aws'
|
|
371
386
|
}
|
|
372
387
|
},
|
|
388
|
+
agents: {
|
|
389
|
+
ClaudeAgent: {
|
|
390
|
+
package: '@lowdefy/connection-anthropic'
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
notifications: {
|
|
394
|
+
NotificationEmail: {
|
|
395
|
+
package: '@lowdefy/email-templates'
|
|
396
|
+
}
|
|
397
|
+
},
|
|
373
398
|
auth: {
|
|
374
399
|
adapters: {
|
|
375
400
|
MongoDBAdapter: {
|
|
@@ -415,16 +440,24 @@ import path from 'path';
|
|
|
415
440
|
package: '@lowdefy/plugin-auth0'
|
|
416
441
|
},
|
|
417
442
|
GoogleProvider: {
|
|
418
|
-
package: 'next-auth'
|
|
443
|
+
package: '@lowdefy/plugin-next-auth'
|
|
419
444
|
},
|
|
420
445
|
GitHubProvider: {
|
|
421
|
-
package: 'next-auth'
|
|
446
|
+
package: '@lowdefy/plugin-next-auth'
|
|
422
447
|
},
|
|
423
448
|
CredentialsProvider: {
|
|
424
|
-
package: 'next-auth'
|
|
449
|
+
package: '@lowdefy/plugin-next-auth'
|
|
425
450
|
},
|
|
426
451
|
EmailProvider: {
|
|
427
|
-
package: 'next-auth'
|
|
452
|
+
package: '@lowdefy/plugin-next-auth'
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
strategies: {
|
|
456
|
+
apiKey: {
|
|
457
|
+
package: '@lowdefy/plugin-next-auth'
|
|
458
|
+
},
|
|
459
|
+
jwt: {
|
|
460
|
+
package: '@lowdefy/plugin-next-auth'
|
|
428
461
|
}
|
|
429
462
|
}
|
|
430
463
|
},
|
|
@@ -34,11 +34,13 @@ function testContext({ writeBuildArtifact, configDirectory, readConfigFile, logg
|
|
|
34
34
|
adapters: createCounter(),
|
|
35
35
|
callbacks: createCounter(),
|
|
36
36
|
events: createCounter(),
|
|
37
|
-
providers: createCounter()
|
|
37
|
+
providers: createCounter(),
|
|
38
|
+
strategies: createCounter()
|
|
38
39
|
},
|
|
39
40
|
blocks: createCounter(),
|
|
40
41
|
connections: createCounter(),
|
|
41
42
|
requests: createCounter(),
|
|
43
|
+
websockets: createCounter(),
|
|
42
44
|
controls: createCounter(),
|
|
43
45
|
operators: {
|
|
44
46
|
client: createCounter(),
|
|
@@ -51,7 +53,8 @@ function testContext({ writeBuildArtifact, configDirectory, readConfigFile, logg
|
|
|
51
53
|
keyMap: {},
|
|
52
54
|
jsMap: {},
|
|
53
55
|
agentIds: new Set(),
|
|
54
|
-
connectionIds: new Set()
|
|
56
|
+
connectionIds: new Set(),
|
|
57
|
+
websocketIds: new Set()
|
|
55
58
|
};
|
|
56
59
|
context.logger = {
|
|
57
60
|
...defaultLogger,
|
|
@@ -66,6 +69,9 @@ function testContext({ writeBuildArtifact, configDirectory, readConfigFile, logg
|
|
|
66
69
|
};
|
|
67
70
|
// handleError delegates to logger.error
|
|
68
71
|
context.handleError = context.logger.error;
|
|
72
|
+
// No-op stub for demand-driven module entry resolution used by resolveModuleConnectionId.
|
|
73
|
+
// In tests, module entries are already in their final state so no resolution is needed.
|
|
74
|
+
context.ensureEntryConfigResolved = ()=>Promise.resolve();
|
|
69
75
|
return context;
|
|
70
76
|
}
|
|
71
77
|
export default testContext;
|
|
@@ -12,11 +12,7 @@
|
|
|
12
12
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
|
-
*/ import { type } from '@lowdefy/helpers';
|
|
16
|
-
// Keys that look like operators (single key starting with _) but are not.
|
|
17
|
-
const KNOWN_NON_OPERATORS = new Set([
|
|
18
|
-
'_id'
|
|
19
|
-
]);
|
|
15
|
+
*/ import { getOperatorType, type } from '@lowdefy/helpers';
|
|
20
16
|
function walkAndCount(value, counter, parentConfigKey) {
|
|
21
17
|
if (type.isArray(value)) {
|
|
22
18
|
value.forEach((item)=>walkAndCount(item, counter, parentConfigKey));
|
|
@@ -26,20 +22,12 @@ function walkAndCount(value, counter, parentConfigKey) {
|
|
|
26
22
|
return;
|
|
27
23
|
}
|
|
28
24
|
const configKey = value['~k'] || parentConfigKey;
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const nonTildeKeys = keys.filter((k)=>!k.startsWith('~'));
|
|
33
|
-
if (nonTildeKeys.length === 1) {
|
|
34
|
-
const key = nonTildeKeys[0];
|
|
35
|
-
const [op] = key.split('.');
|
|
36
|
-
const operator = op.replace(/^(_+)/gm, '_');
|
|
37
|
-
if (operator.length > 1 && operator[0] === '_' && !KNOWN_NON_OPERATORS.has(operator)) {
|
|
38
|
-
counter.increment(operator, configKey);
|
|
39
|
-
}
|
|
25
|
+
const operator = getOperatorType(value);
|
|
26
|
+
if (operator) {
|
|
27
|
+
counter.increment(operator, configKey);
|
|
40
28
|
}
|
|
41
29
|
// Recurse into all values
|
|
42
|
-
keys.forEach((key)=>{
|
|
30
|
+
Object.keys(value).forEach((key)=>{
|
|
43
31
|
walkAndCount(value[key], counter, configKey);
|
|
44
32
|
});
|
|
45
33
|
}
|
|
@@ -67,6 +67,13 @@ function createPluginTypesMap({ packageName, packageTypes, typePrefix = '', type
|
|
|
67
67
|
typePrefix,
|
|
68
68
|
version
|
|
69
69
|
});
|
|
70
|
+
createTypeDefinitions({
|
|
71
|
+
typeNames: type.isObject(packageTypes.auth) ? packageTypes.auth.strategies : [],
|
|
72
|
+
store: typesMap.auth.strategies,
|
|
73
|
+
packageName,
|
|
74
|
+
typePrefix,
|
|
75
|
+
version
|
|
76
|
+
});
|
|
70
77
|
createTypeDefinitions({
|
|
71
78
|
typeNames: packageTypes.blocks,
|
|
72
79
|
store: typesMap.blocks,
|
|
@@ -95,6 +102,13 @@ function createPluginTypesMap({ packageName, packageTypes, typePrefix = '', type
|
|
|
95
102
|
typePrefix,
|
|
96
103
|
version
|
|
97
104
|
});
|
|
105
|
+
createTypeDefinitions({
|
|
106
|
+
typeNames: packageTypes.notifications,
|
|
107
|
+
store: typesMap.notifications,
|
|
108
|
+
packageName,
|
|
109
|
+
typePrefix,
|
|
110
|
+
version
|
|
111
|
+
});
|
|
98
112
|
createTypeDefinitions({
|
|
99
113
|
typeNames: packageTypes.requests,
|
|
100
114
|
store: typesMap.requests,
|
|
@@ -102,6 +116,13 @@ function createPluginTypesMap({ packageName, packageTypes, typePrefix = '', type
|
|
|
102
116
|
typePrefix,
|
|
103
117
|
version
|
|
104
118
|
});
|
|
119
|
+
createTypeDefinitions({
|
|
120
|
+
typeNames: packageTypes.websockets,
|
|
121
|
+
store: typesMap.websockets,
|
|
122
|
+
packageName,
|
|
123
|
+
typePrefix,
|
|
124
|
+
version
|
|
125
|
+
});
|
|
105
126
|
if (type.isObject(packageTypes.icons)) {
|
|
106
127
|
Object.entries(packageTypes.icons).forEach(([blockType, icons])=>{
|
|
107
128
|
typesMap.icons[`${typePrefix}${blockType}`] = icons;
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
*/ // The declared environment names in config.environments.
|
|
16
|
+
// Build key markers (~k, ~r, ~l) live on the same object as the environments, so they are skipped
|
|
17
|
+
// rather than treated as names.
|
|
18
|
+
const markerKeys = new Set([
|
|
19
|
+
'~k',
|
|
20
|
+
'~r',
|
|
21
|
+
'~l'
|
|
22
|
+
]);
|
|
23
|
+
function getEnvironmentNames(environments) {
|
|
24
|
+
return Object.keys(environments ?? {}).filter((key)=>!markerKeys.has(key));
|
|
25
|
+
}
|
|
26
|
+
export default getEnvironmentNames;
|
|
@@ -13,9 +13,12 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { BuildError, LowdefyInternalError, resolveErrorLocation } from '@lowdefy/errors';
|
|
16
|
+
import serializeBuildException from './serializeBuildException.js';
|
|
16
17
|
function logCollectedErrors(context) {
|
|
17
18
|
if (context.errors.length === 0) return;
|
|
18
|
-
|
|
19
|
+
// handleError mutates each error in place (resolves source/config), so
|
|
20
|
+
// capture the handled instance (not the original) for serialization below.
|
|
21
|
+
const handledErrors = [];
|
|
19
22
|
const seenSourceMessages = new Set();
|
|
20
23
|
for (const err of context.errors){
|
|
21
24
|
const lowdefyErr = err.isLowdefyError ? err : new LowdefyInternalError(err.message, {
|
|
@@ -39,20 +42,23 @@ function logCollectedErrors(context) {
|
|
|
39
42
|
location = null;
|
|
40
43
|
}
|
|
41
44
|
// Two build steps can report the same config line: buildAuth reaches page,
|
|
42
|
-
// endpoint and
|
|
43
|
-
// same message. Two genuinely distinct errors never share both a
|
|
44
|
-
// source line and a message, so that pair is safe to collapse.
|
|
45
|
-
// resolved source line there is no such evidence
|
|
46
|
-
// collapse distinct internal errors and under-count the
|
|
47
|
-
// unlocated errors are always reported.
|
|
45
|
+
// endpoint and websocket ids before validateId does, and both gates give
|
|
46
|
+
// the same message. Two genuinely distinct errors never share both a
|
|
47
|
+
// resolved source line and a message, so that pair is safe to collapse.
|
|
48
|
+
// Without a resolved source line there is no such evidence — the message
|
|
49
|
+
// alone would collapse distinct internal errors, and under-count the
|
|
50
|
+
// failures — so unlocated errors are always reported.
|
|
48
51
|
if (location?.source) {
|
|
49
52
|
const dedupKey = `${location.source}\n${lowdefyErr.message}`;
|
|
50
53
|
if (seenSourceMessages.has(dedupKey)) continue;
|
|
51
54
|
seenSourceMessages.add(dedupKey);
|
|
52
55
|
}
|
|
53
56
|
context.handleError(lowdefyErr);
|
|
54
|
-
|
|
57
|
+
handledErrors.push(lowdefyErr);
|
|
55
58
|
}
|
|
56
|
-
|
|
59
|
+
const buildError = new BuildError(`Build failed with ${handledErrors.length} error(s). See above for details.`);
|
|
60
|
+
buildError.errors = handledErrors.map(serializeBuildException);
|
|
61
|
+
buildError.warnings = (context.warnings ?? []).map(serializeBuildException);
|
|
62
|
+
throw buildError;
|
|
57
63
|
}
|
|
58
64
|
export default logCollectedErrors;
|
|
@@ -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
|
+
*/ // Error.prototype.message is non-enumerable, so JSON.stringify(error) drops it
|
|
16
|
+
// (while enumerable fields like configKey/source survive). Build a plain,
|
|
17
|
+
// JSON-serializable summary instead - no stack, no cause, no `received`
|
|
18
|
+
// (arbitrary config values that may be huge or circular).
|
|
19
|
+
function serializeBuildException(exception) {
|
|
20
|
+
return {
|
|
21
|
+
message: exception.message,
|
|
22
|
+
name: exception.name,
|
|
23
|
+
source: exception.source ?? null,
|
|
24
|
+
config: exception.config ?? null,
|
|
25
|
+
configKey: exception.configKey ?? null,
|
|
26
|
+
checkSlug: exception.checkSlug ?? null
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export default serializeBuildException;
|
|
@@ -0,0 +1,131 @@
|
|
|
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
|
+
*/ // Validates a cron expression against the restricted subset Vercel Cron Jobs accept, so an invalid
|
|
16
|
+
// schedule fails at build instead of during the Vercel deployment. Vercel rules:
|
|
17
|
+
// - Exactly 5 fields: minute hour day-of-month month day-of-week.
|
|
18
|
+
// - Numeric values only — named values like MON or JAN are not supported.
|
|
19
|
+
// - Day-of-month and day-of-week are mutually exclusive: when one is set the other must be "*".
|
|
20
|
+
// Returns null when valid, otherwise a human-readable reason string.
|
|
21
|
+
const FIELDS = [
|
|
22
|
+
{
|
|
23
|
+
name: 'minute',
|
|
24
|
+
min: 0,
|
|
25
|
+
max: 59
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'hour',
|
|
29
|
+
min: 0,
|
|
30
|
+
max: 23
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'day-of-month',
|
|
34
|
+
min: 1,
|
|
35
|
+
max: 31
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'month',
|
|
39
|
+
min: 1,
|
|
40
|
+
max: 12
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'day-of-week',
|
|
44
|
+
min: 0,
|
|
45
|
+
max: 6
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
function validateInt(value, { min, max }) {
|
|
49
|
+
if (!/^[0-9]+$/.test(value)) {
|
|
50
|
+
return `contains non-numeric value "${value}" (named values like MON/JAN are not supported)`;
|
|
51
|
+
}
|
|
52
|
+
const n = Number(value);
|
|
53
|
+
if (n < min || n > max) {
|
|
54
|
+
return `value "${value}" is out of range (${min}-${max})`;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
function validatePart(part, { min, max }) {
|
|
59
|
+
let range = part;
|
|
60
|
+
if (part.includes('/')) {
|
|
61
|
+
const stepSplit = part.split('/');
|
|
62
|
+
if (stepSplit.length !== 2) {
|
|
63
|
+
return `has an invalid step in "${part}"`;
|
|
64
|
+
}
|
|
65
|
+
const [rangePart, stepPart] = stepSplit;
|
|
66
|
+
if (!/^[1-9][0-9]*$/.test(stepPart)) {
|
|
67
|
+
return `has an invalid step in "${part}"`;
|
|
68
|
+
}
|
|
69
|
+
range = rangePart;
|
|
70
|
+
}
|
|
71
|
+
if (range === '*') {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (range.includes('-')) {
|
|
75
|
+
const rangeSplit = range.split('-');
|
|
76
|
+
if (rangeSplit.length !== 2) {
|
|
77
|
+
return `has an invalid range in "${part}"`;
|
|
78
|
+
}
|
|
79
|
+
const [startStr, endStr] = rangeSplit;
|
|
80
|
+
const startReason = validateInt(startStr, {
|
|
81
|
+
min,
|
|
82
|
+
max
|
|
83
|
+
});
|
|
84
|
+
if (startReason) return startReason;
|
|
85
|
+
const endReason = validateInt(endStr, {
|
|
86
|
+
min,
|
|
87
|
+
max
|
|
88
|
+
});
|
|
89
|
+
if (endReason) return endReason;
|
|
90
|
+
if (Number(startStr) > Number(endStr)) {
|
|
91
|
+
return `has a descending range in "${part}"`;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return validateInt(range, {
|
|
96
|
+
min,
|
|
97
|
+
max
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function validateField(field, spec) {
|
|
101
|
+
if (field === '') {
|
|
102
|
+
return `${spec.name} field is empty`;
|
|
103
|
+
}
|
|
104
|
+
const parts = field.split(',');
|
|
105
|
+
for (const part of parts){
|
|
106
|
+
const reason = validatePart(part, spec);
|
|
107
|
+
if (reason) {
|
|
108
|
+
return `${spec.name} field "${field}" ${reason}`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
function validateCronExpression(expression) {
|
|
114
|
+
if (typeof expression !== 'string') {
|
|
115
|
+
return 'cron expression must be a string';
|
|
116
|
+
}
|
|
117
|
+
const fields = expression.trim().split(/\s+/);
|
|
118
|
+
if (fields.length !== 5) {
|
|
119
|
+
return 'cron expression must have exactly 5 fields (minute hour day-of-month month day-of-week)';
|
|
120
|
+
}
|
|
121
|
+
for(let i = 0; i < FIELDS.length; i += 1){
|
|
122
|
+
const reason = validateField(fields[i], FIELDS[i]);
|
|
123
|
+
if (reason) return reason;
|
|
124
|
+
}
|
|
125
|
+
const [, , dayOfMonth, , dayOfWeek] = fields;
|
|
126
|
+
if (dayOfMonth !== '*' && dayOfWeek !== '*') {
|
|
127
|
+
return 'cannot specify both day-of-month and day-of-week; one must be "*"';
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
export default validateCronExpression;
|
|
@@ -13,10 +13,13 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import path from 'path';
|
|
16
|
-
import {
|
|
16
|
+
import { writeFileIfChanged } from '@lowdefy/node-utils';
|
|
17
17
|
function createWriteBuildArtifact({ directories }) {
|
|
18
|
+
// Skipping byte-identical writes keeps artifact mtimes stable so Vite does
|
|
19
|
+
// not invalidate modules (clientJsMap → Routing HMR, serverJsMap → SSR
|
|
20
|
+
// reload) on JIT page builds that changed nothing.
|
|
18
21
|
async function writeBuildArtifact(filePath, content) {
|
|
19
|
-
await
|
|
22
|
+
await writeFileIfChanged(path.join(directories.build, filePath), content);
|
|
20
23
|
}
|
|
21
24
|
return writeBuildArtifact;
|
|
22
25
|
}
|