@lowdefy/build 5.5.1 → 6.0.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 +70 -17
- 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 +37 -0
- package/dist/build/buildApi/validateEndpoint.js +96 -1
- package/dist/build/buildApp.js +13 -20
- package/dist/build/buildAppMeta.js +130 -0
- package/dist/build/buildAuth/buildAgentAuth.js +69 -0
- package/dist/build/buildAuth/buildAuth.js +14 -0
- 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/getAgentRoles.js +39 -0
- package/dist/build/buildAuth/getProtectedAgents.js +29 -0
- package/dist/build/buildAuth/getProtectedWebsockets.js +29 -0
- package/dist/build/buildAuth/getWebsocketRoles.js +39 -0
- package/dist/build/buildAuth/rejectReservedEntityIds.js +52 -0
- package/dist/build/buildAuth/validateAuthConfig.js +15 -3
- 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/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 +197 -22
- 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 +19 -1
- 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/getKey.js +16 -5
- 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 +490 -209
- 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/fetchModules.js +18 -4
- 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 +81 -1
- package/dist/build/jit/writePageJit.js +11 -8
- package/dist/build/registerModules.js +149 -42
- package/dist/build/validateCallAgentSteps.js +58 -0
- package/dist/build/validateConfig.js +4 -0
- package/dist/build/validateCronConfig.js +110 -0
- package/dist/build/validateRenderNotificationSteps.js +50 -0
- package/dist/build/writeApi.js +58 -1
- package/dist/build/writeI18n.js +8 -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 -11
- 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 +689 -386
- package/dist/dynamic/buildDynamicBlocks.js +120 -0
- package/dist/index.js +78 -8
- package/dist/indexDev.js +4 -0
- package/dist/lowdefySchema.js +861 -6
- package/dist/scripts/generateDefaultTypes.js +5 -2
- package/dist/test-utils/expectTerminates.js +33 -0
- package/dist/test-utils/runBuild.js +20 -1
- package/dist/test-utils/runBuildForSnapshots.js +34 -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/getCronEnvironmentNames.js +25 -0
- package/dist/utils/logCollectedErrors.js +44 -10
- package/dist/utils/serializeBuildException.js +29 -0
- package/dist/utils/validateCronExpression.js +131 -0
- package/dist/utils/validateId.js +11 -0
- package/dist/utils/writeBuildArtifact.js +5 -2
- package/package.json +66 -59
- package/dist/build/writePluginImports/writeServerExternalPackages.js +0 -121
- /package/dist/build/{resolveModuleOperators.js → buildRefs/scopeMenuItemIds.js} +0 -0
package/dist/build/buildTypes.js
CHANGED
|
@@ -70,11 +70,14 @@ function buildTypes({ components, context }) {
|
|
|
70
70
|
adapters: {},
|
|
71
71
|
callbacks: {},
|
|
72
72
|
events: {},
|
|
73
|
-
providers: {}
|
|
73
|
+
providers: {},
|
|
74
|
+
strategies: {}
|
|
74
75
|
},
|
|
75
76
|
blocks: {},
|
|
76
77
|
connections: {},
|
|
78
|
+
notifications: {},
|
|
77
79
|
requests: {},
|
|
80
|
+
websockets: {},
|
|
78
81
|
api: {},
|
|
79
82
|
operators: {
|
|
80
83
|
client: {},
|
|
@@ -117,6 +120,12 @@ function buildTypes({ components, context }) {
|
|
|
117
120
|
store: components.types.auth.providers,
|
|
118
121
|
typeClass: 'Auth provider'
|
|
119
122
|
});
|
|
123
|
+
buildTypeClass(context, {
|
|
124
|
+
counter: typeCounters.auth.strategies,
|
|
125
|
+
definitions: context.typesMap.auth.strategies,
|
|
126
|
+
store: components.types.auth.strategies,
|
|
127
|
+
typeClass: 'Auth strategy'
|
|
128
|
+
});
|
|
120
129
|
buildTypeClass(context, {
|
|
121
130
|
counter: typeCounters.blocks,
|
|
122
131
|
definitions: context.typesMap.blocks,
|
|
@@ -129,12 +138,24 @@ function buildTypes({ components, context }) {
|
|
|
129
138
|
store: components.types.connections,
|
|
130
139
|
typeClass: 'Connection'
|
|
131
140
|
});
|
|
141
|
+
buildTypeClass(context, {
|
|
142
|
+
counter: typeCounters.notifications,
|
|
143
|
+
definitions: context.typesMap.notifications,
|
|
144
|
+
store: components.types.notifications,
|
|
145
|
+
typeClass: 'Notification'
|
|
146
|
+
});
|
|
132
147
|
buildTypeClass(context, {
|
|
133
148
|
counter: typeCounters.requests,
|
|
134
149
|
definitions: context.typesMap.requests,
|
|
135
150
|
store: components.types.requests,
|
|
136
151
|
typeClass: 'Request'
|
|
137
152
|
});
|
|
153
|
+
buildTypeClass(context, {
|
|
154
|
+
counter: typeCounters.websockets,
|
|
155
|
+
definitions: context.typesMap.websockets,
|
|
156
|
+
store: components.types.websockets,
|
|
157
|
+
typeClass: 'Websocket'
|
|
158
|
+
});
|
|
138
159
|
buildTypeClass(context, {
|
|
139
160
|
counter: typeCounters.operators.client,
|
|
140
161
|
definitions: context.typesMap.operators.client,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */ /*
|
|
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
|
+
*/ import { type } from '@lowdefy/helpers';
|
|
16
|
+
import { ConfigError } from '@lowdefy/errors';
|
|
17
|
+
import countOperators from '../utils/countOperators.js';
|
|
18
|
+
import createCheckDuplicateId from '../utils/createCheckDuplicateId.js';
|
|
19
|
+
import validateId from '../utils/validateId.js';
|
|
20
|
+
function buildWebsockets({ components, context }) {
|
|
21
|
+
if (components.websockets && !type.isArray(components.websockets)) {
|
|
22
|
+
throw new ConfigError('Websockets is not an array.', {
|
|
23
|
+
received: components.websockets
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const websockets = type.isArray(components.websockets) ? components.websockets : [];
|
|
27
|
+
context.websocketIds = new Set();
|
|
28
|
+
const checkDuplicateWebsocketId = createCheckDuplicateId({
|
|
29
|
+
message: 'Duplicate websocketId "{{ id }}".'
|
|
30
|
+
});
|
|
31
|
+
websockets.forEach((websocket, index)=>{
|
|
32
|
+
const configKey = websocket['~k'];
|
|
33
|
+
if (type.isUndefined(websocket.id)) {
|
|
34
|
+
throw new ConfigError(`Websocket id missing at websocket ${index}.`, {
|
|
35
|
+
configKey
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (!type.isString(websocket.id)) {
|
|
39
|
+
throw new ConfigError(`Websocket id is not a string at websocket ${index}.`, {
|
|
40
|
+
received: websocket.id,
|
|
41
|
+
configKey
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
validateId({
|
|
45
|
+
id: websocket.id,
|
|
46
|
+
field: 'Websocket id',
|
|
47
|
+
configKey
|
|
48
|
+
});
|
|
49
|
+
checkDuplicateWebsocketId({
|
|
50
|
+
id: websocket.id,
|
|
51
|
+
configKey
|
|
52
|
+
});
|
|
53
|
+
if (!type.isString(websocket.type)) {
|
|
54
|
+
throw new ConfigError(`Websocket type is not a string at websocket "${websocket.id}".`, {
|
|
55
|
+
received: websocket.type,
|
|
56
|
+
configKey
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Track type usage for buildTypes validation
|
|
60
|
+
context.typeCounters.websockets.increment(websocket.type, configKey);
|
|
61
|
+
// connectionId is optional — Channel and other connectionless types don't need one
|
|
62
|
+
if (!type.isNone(websocket.connectionId)) {
|
|
63
|
+
if (!type.isString(websocket.connectionId)) {
|
|
64
|
+
throw new ConfigError(`Websocket connectionId is not a string at websocket "${websocket.id}".`, {
|
|
65
|
+
received: websocket.connectionId,
|
|
66
|
+
configKey
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
// Connections may have been renamed by buildConnections:
|
|
70
|
+
// connection.connectionId = original id, connection.id = 'connection:' + original id
|
|
71
|
+
const connectionExists = (components.connections ?? []).some((c)=>c.id === websocket.connectionId || c.connectionId === websocket.connectionId);
|
|
72
|
+
if (!connectionExists) {
|
|
73
|
+
throw new ConfigError(`Websocket "${websocket.id}" references connectionId "${websocket.connectionId}" which does not exist.`, {
|
|
74
|
+
configKey,
|
|
75
|
+
checkSlug: 'connection-refs'
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (type.isNone(websocket.properties)) {
|
|
80
|
+
websocket.properties = {};
|
|
81
|
+
}
|
|
82
|
+
if (!type.isObject(websocket.properties)) {
|
|
83
|
+
throw new ConfigError(`Websocket properties is not an object at websocket "${websocket.id}".`, {
|
|
84
|
+
received: websocket.properties,
|
|
85
|
+
configKey
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// Rename id to internal format
|
|
89
|
+
websocket.websocketId = websocket.id;
|
|
90
|
+
context.websocketIds.add(websocket.websocketId);
|
|
91
|
+
websocket.id = `websocket:${websocket.websocketId}`;
|
|
92
|
+
// Count server operators in properties
|
|
93
|
+
countOperators(websocket.properties, {
|
|
94
|
+
counter: context.typeCounters.operators.server
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
return components;
|
|
98
|
+
}
|
|
99
|
+
export default buildWebsockets;
|
|
@@ -24,7 +24,13 @@
|
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
if (value && typeof value === 'object') {
|
|
27
|
-
for (const v of Object.
|
|
27
|
+
for (const [key, v] of Object.entries(value)){
|
|
28
|
+
// Build marker values (~k keys regenerate on every JIT rebuild) are
|
|
29
|
+
// never tailwind candidates — including them would make the collected
|
|
30
|
+
// content differ between byte-identical page builds.
|
|
31
|
+
if (key === '~k' || key === '~r' || key === '~l') {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
28
34
|
walkValue(v, strings);
|
|
29
35
|
}
|
|
30
36
|
}
|
|
@@ -25,8 +25,8 @@ async function copyAgentFileSystems({ components, context }) {
|
|
|
25
25
|
seen.add(basePath);
|
|
26
26
|
basePaths.push(basePath);
|
|
27
27
|
}
|
|
28
|
-
// Manifest
|
|
29
|
-
//
|
|
28
|
+
// Manifest of agent file system base paths copied into the server directory —
|
|
29
|
+
// available to deployment tooling that needs to include these directories.
|
|
30
30
|
await context.writeBuildArtifact('agentFileSystems.json', JSON.stringify(basePaths));
|
|
31
31
|
if (context.directories.config === context.directories.server) return;
|
|
32
32
|
for (const basePath of basePaths){
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import fs from 'node:fs';
|
|
16
16
|
import path from 'node:path';
|
|
17
|
+
import { ReservedKeyError, setKey } from '@lowdefy/helpers';
|
|
17
18
|
import { ConfigError } from '@lowdefy/errors';
|
|
18
19
|
import fetchGitHubModule from './fetchGitHubModule.js';
|
|
19
20
|
import parseModuleSource from './parseModuleSource.js';
|
|
@@ -28,6 +29,19 @@ function findGitRoot(startPath) {
|
|
|
28
29
|
dir = parent;
|
|
29
30
|
}
|
|
30
31
|
}
|
|
32
|
+
// Module ids come from the app's lowdefy.yaml, so they key this map from user
|
|
33
|
+
// config. Surface a reserved id as the build's ConfigError rather than a bare
|
|
34
|
+
// ReservedKeyError.
|
|
35
|
+
function setResolvedPaths(resolved, id, paths) {
|
|
36
|
+
try {
|
|
37
|
+
setKey(resolved, id, paths);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (!(error instanceof ReservedKeyError)) throw error;
|
|
40
|
+
throw new ConfigError(`Module entry id "${id}" is a reserved name.`, {
|
|
41
|
+
cause: error
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
31
45
|
async function fetchModules({ moduleEntries, context }) {
|
|
32
46
|
const resolved = {};
|
|
33
47
|
for (const entry of moduleEntries){
|
|
@@ -37,22 +51,22 @@ async function fetchModules({ moduleEntries, context }) {
|
|
|
37
51
|
if (!fs.existsSync(path.join(resolvedPath, 'module.lowdefy.yaml'))) {
|
|
38
52
|
throw new ConfigError(`Module "${entry.id}": module.lowdefy.yaml not found at ${resolvedPath}`);
|
|
39
53
|
}
|
|
40
|
-
resolved
|
|
54
|
+
setResolvedPaths(resolved, entry.id, {
|
|
41
55
|
packageRoot: findGitRoot(resolvedPath) ?? resolvedPath,
|
|
42
56
|
moduleRoot: resolvedPath,
|
|
43
57
|
isLocal: true
|
|
44
|
-
};
|
|
58
|
+
});
|
|
45
59
|
} else if (source.type === 'github') {
|
|
46
60
|
const cached = await fetchGitHubModule(source, context);
|
|
47
61
|
const moduleRoot = source.path ? path.join(cached.packageRoot, source.path) : cached.packageRoot;
|
|
48
62
|
if (!fs.existsSync(path.join(moduleRoot, 'module.lowdefy.yaml'))) {
|
|
49
63
|
throw new ConfigError(`Module "${entry.id}": module.lowdefy.yaml not found at path "${source.path || '/'}" in ${source.owner}/${source.repo}@${source.ref}`);
|
|
50
64
|
}
|
|
51
|
-
resolved
|
|
65
|
+
setResolvedPaths(resolved, entry.id, {
|
|
52
66
|
packageRoot: cached.packageRoot,
|
|
53
67
|
moduleRoot,
|
|
54
68
|
isLocal: false
|
|
55
|
-
};
|
|
69
|
+
});
|
|
56
70
|
}
|
|
57
71
|
}
|
|
58
72
|
return resolved;
|
|
@@ -17,10 +17,12 @@ import { ConfigError, shouldSuppressBuildCheck } from '@lowdefy/errors';
|
|
|
17
17
|
import buildPage from '../buildPages/buildPage.js';
|
|
18
18
|
import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
|
|
19
19
|
import validateCallApiRefs from '../buildPages/validateCallApiRefs.js';
|
|
20
|
+
import validateDynamicBlockRefs from '../buildPages/validateDynamicBlockRefs.js';
|
|
20
21
|
import validateLinkReferences from '../buildPages/validateLinkReferences.js';
|
|
21
22
|
import validatePayloadReferences from '../buildPages/validatePayloadReferences.js';
|
|
22
23
|
import validateServerStateReferences from '../buildPages/validateServerStateReferences.js';
|
|
23
24
|
import validateStateReferences from '../buildPages/validateStateReferences.js';
|
|
25
|
+
import validateWebsocketRefs from '../buildPages/validateWebsocketRefs.js';
|
|
24
26
|
function buildPages({ components, context }) {
|
|
25
27
|
const pages = type.isArray(components.pages) ? components.pages : [];
|
|
26
28
|
const checkDuplicatePageId = createCheckDuplicateId({
|
|
@@ -29,6 +31,8 @@ function buildPages({ components, context }) {
|
|
|
29
31
|
// Initialize action ref collections across all pages
|
|
30
32
|
context.linkActionRefs = [];
|
|
31
33
|
context.callApiActionRefs = [];
|
|
34
|
+
context.websocketActionRefs = [];
|
|
35
|
+
context.dynamicBlockRefs = [];
|
|
32
36
|
// Track which pages failed to build so we skip them in validation
|
|
33
37
|
const failedPageIndices = new Set();
|
|
34
38
|
// Wrap each page build to collect errors instead of stopping on first error
|
|
@@ -73,6 +77,18 @@ function buildPages({ components, context }) {
|
|
|
73
77
|
endpointConfigs,
|
|
74
78
|
context
|
|
75
79
|
});
|
|
80
|
+
// Validate that Dynamic blocks reference existing endpoints
|
|
81
|
+
validateDynamicBlockRefs({
|
|
82
|
+
dynamicBlockRefs: context.dynamicBlockRefs,
|
|
83
|
+
endpointConfigs,
|
|
84
|
+
context
|
|
85
|
+
});
|
|
86
|
+
// Validate that Subscribe/Unsubscribe/Publish actions reference defined websockets
|
|
87
|
+
validateWebsocketRefs({
|
|
88
|
+
websocketActionRefs: context.websocketActionRefs,
|
|
89
|
+
websocketIds: context.websocketIds ?? new Set(),
|
|
90
|
+
context
|
|
91
|
+
});
|
|
76
92
|
// Validate that _state references use defined block IDs
|
|
77
93
|
// and _payload references use defined payload keys
|
|
78
94
|
// Skip pages that failed to build
|
|
@@ -21,6 +21,12 @@ async function updateServerPackageJson({ components, context }) {
|
|
|
21
21
|
const dependencies = packageJson.dependencies;
|
|
22
22
|
function getPackages(types) {
|
|
23
23
|
Object.values(types).forEach((type)=>{
|
|
24
|
+
// Deployment tooling may have rewritten a workspace plugin to a link:
|
|
25
|
+
// path — overwriting it with the configured version (e.g. workspace:*)
|
|
26
|
+
// would break installs outside the monorepo workspace.
|
|
27
|
+
if (dependencies[type.package]?.startsWith('link:')) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
24
30
|
dependencies[type.package] = type.version;
|
|
25
31
|
});
|
|
26
32
|
}
|
|
@@ -30,11 +36,26 @@ async function updateServerPackageJson({ components, context }) {
|
|
|
30
36
|
getPackages(components.types.auth.callbacks);
|
|
31
37
|
getPackages(components.types.auth.events);
|
|
32
38
|
getPackages(components.types.auth.providers);
|
|
39
|
+
getPackages(components.types.auth.strategies);
|
|
33
40
|
getPackages(components.types.blocks);
|
|
34
41
|
getPackages(components.types.connections);
|
|
42
|
+
getPackages(components.types.notifications);
|
|
35
43
|
getPackages(components.types.requests);
|
|
44
|
+
getPackages(components.types.websockets);
|
|
36
45
|
getPackages(components.types.operators.client);
|
|
37
46
|
getPackages(components.types.operators.server);
|
|
47
|
+
if ((components.notifications ?? []).length > 0) {
|
|
48
|
+
// plugins/notifications.js re-exports renderEmail from @lowdefy/email-templates
|
|
49
|
+
// for every app with notifications — including apps that only use custom
|
|
50
|
+
// template types, where no framework template appears in components.types.
|
|
51
|
+
// The react-email preview CLI is deliberately NOT added here — it is a
|
|
52
|
+
// dev-only tool the `lowdefy emails` command installs just-in-time, so
|
|
53
|
+
// production servers never carry it.
|
|
54
|
+
const emailTemplates = Object.values(context.typesMap.notifications ?? {}).find((t)=>t.package === '@lowdefy/email-templates');
|
|
55
|
+
if (emailTemplates && !dependencies['@lowdefy/email-templates']?.startsWith('link:')) {
|
|
56
|
+
dependencies['@lowdefy/email-templates'] = emailTemplates.version;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
38
59
|
// Sort dependencies
|
|
39
60
|
packageJson.dependencies = {};
|
|
40
61
|
Object.keys(dependencies).sort().forEach((name)=>{
|
|
@@ -20,19 +20,23 @@ import operators from '@lowdefy/operators-js/operators/build';
|
|
|
20
20
|
import addKeys from '../addKeys.js';
|
|
21
21
|
import buildPage from '../buildPages/buildPage.js';
|
|
22
22
|
import validateCallApiRefs from '../buildPages/validateCallApiRefs.js';
|
|
23
|
+
import validateDynamicBlockRefs from '../buildPages/validateDynamicBlockRefs.js';
|
|
23
24
|
import validateLinkReferences from '../buildPages/validateLinkReferences.js';
|
|
24
25
|
import validatePayloadReferences from '../buildPages/validatePayloadReferences.js';
|
|
25
26
|
import validateServerStateReferences from '../buildPages/validateServerStateReferences.js';
|
|
26
27
|
import validateStateReferences from '../buildPages/validateStateReferences.js';
|
|
28
|
+
import validateWebsocketRefs from '../buildPages/validateWebsocketRefs.js';
|
|
27
29
|
import collectDynamicIdentifiers from '../collectDynamicIdentifiers.js';
|
|
30
|
+
import collectPageContent from '../collectPageContent.js';
|
|
28
31
|
import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
|
|
29
32
|
import createContext from '../../createContext.js';
|
|
30
|
-
import
|
|
33
|
+
import precomputeRuntimeOperators from '../buildRefs/precomputeRuntimeOperators.js';
|
|
31
34
|
import getRefContent from '../buildRefs/getRefContent.js';
|
|
32
35
|
import jsMapParser from '../buildJs/jsMapParser.js';
|
|
33
36
|
import makeRefDefinition from '../buildRefs/makeRefDefinition.js';
|
|
34
37
|
import rebaseModuleRefPaths from '../buildRefs/rebaseModuleRefPaths.js';
|
|
35
|
-
import { resolve, WalkContext,
|
|
38
|
+
import { resolve, WalkContext, tagRefDeep } from '../buildRefs/walker.js';
|
|
39
|
+
import cloneWithMarkers from '../buildRefs/cloneWithMarkers.js';
|
|
36
40
|
import validateOperatorsDynamic from '../validateOperatorsDynamic.js';
|
|
37
41
|
import writeMaps from '../writeMaps.js';
|
|
38
42
|
import detectMissingIcons from './detectMissingIcons.js';
|
|
@@ -131,10 +135,11 @@ async function buildPageJit({ pageId, pageRegistry, context, directories, logger
|
|
|
131
135
|
refChain: new Set(),
|
|
132
136
|
operators,
|
|
133
137
|
env: process.env,
|
|
138
|
+
lowdefyApp: buildContext.appMeta,
|
|
134
139
|
dynamicIdentifiers,
|
|
135
140
|
shouldStop: null
|
|
136
141
|
});
|
|
137
|
-
resolvedVars = await resolve(
|
|
142
|
+
resolvedVars = await resolve(cloneWithMarkers(unresolvedVars), varCtx);
|
|
138
143
|
}
|
|
139
144
|
let refDef;
|
|
140
145
|
if (pageEntry.resolverOriginal) {
|
|
@@ -188,11 +193,12 @@ async function buildPageJit({ pageId, pageRegistry, context, directories, logger
|
|
|
188
193
|
refChain: new Set(),
|
|
189
194
|
operators,
|
|
190
195
|
env: process.env,
|
|
196
|
+
lowdefyApp: buildContext.appMeta,
|
|
191
197
|
dynamicIdentifiers,
|
|
192
198
|
shouldStop: null
|
|
193
199
|
});
|
|
194
200
|
let processed = await resolve(pageContent, pageCtx);
|
|
195
|
-
processed =
|
|
201
|
+
processed = precomputeRuntimeOperators({
|
|
196
202
|
context: buildContext,
|
|
197
203
|
input: processed,
|
|
198
204
|
refDef
|
|
@@ -232,6 +238,19 @@ async function buildPageJit({ pageId, pageRegistry, context, directories, logger
|
|
|
232
238
|
if (!buildContext.callApiActionRefs) {
|
|
233
239
|
buildContext.callApiActionRefs = [];
|
|
234
240
|
}
|
|
241
|
+
if (!buildContext.websocketActionRefs) {
|
|
242
|
+
buildContext.websocketActionRefs = [];
|
|
243
|
+
}
|
|
244
|
+
if (!buildContext.dynamicBlockRefs) {
|
|
245
|
+
buildContext.dynamicBlockRefs = [];
|
|
246
|
+
}
|
|
247
|
+
// buildSubscriptions validates against websocketIds — the dev server
|
|
248
|
+
// restores the set from the websocketIds.json skeleton artifact. Rebuild
|
|
249
|
+
// it from skeleton-built websockets when the context doesn't carry it
|
|
250
|
+
// (createContext initializes an empty set, so check size, not presence).
|
|
251
|
+
if (!buildContext.websocketIds?.size) {
|
|
252
|
+
buildContext.websocketIds = new Set((buildContext.components?.websockets ?? []).map((websocket)=>websocket.websocketId));
|
|
253
|
+
}
|
|
235
254
|
// Build the page (validation, block processing)
|
|
236
255
|
const checkDuplicatePageId = createCheckDuplicateId({
|
|
237
256
|
message: 'Duplicate pageId "{{ id }}".'
|
|
@@ -285,6 +304,16 @@ async function buildPageJit({ pageId, pageRegistry, context, directories, logger
|
|
|
285
304
|
endpointConfigs,
|
|
286
305
|
context: buildContext
|
|
287
306
|
});
|
|
307
|
+
validateDynamicBlockRefs({
|
|
308
|
+
dynamicBlockRefs: buildContext.dynamicBlockRefs,
|
|
309
|
+
endpointConfigs,
|
|
310
|
+
context: buildContext
|
|
311
|
+
});
|
|
312
|
+
validateWebsocketRefs({
|
|
313
|
+
websocketActionRefs: buildContext.websocketActionRefs,
|
|
314
|
+
websocketIds: buildContext.websocketIds,
|
|
315
|
+
context: buildContext
|
|
316
|
+
});
|
|
288
317
|
validateStateReferences({
|
|
289
318
|
page: processed,
|
|
290
319
|
context: buildContext
|
|
@@ -297,6 +326,12 @@ async function buildPageJit({ pageId, pageRegistry, context, directories, logger
|
|
|
297
326
|
page: processed,
|
|
298
327
|
context: buildContext
|
|
299
328
|
});
|
|
329
|
+
// Collect Tailwind class candidates before _js extraction — jsMapParser
|
|
330
|
+
// replaces _js source with hashes, so classes used only inside _js source
|
|
331
|
+
// would otherwise never reach the Tailwind scanner.
|
|
332
|
+
const tailwindContent = collectPageContent([
|
|
333
|
+
processed
|
|
334
|
+
]);
|
|
300
335
|
// Extract JS functions from the page
|
|
301
336
|
const pageRequests = [
|
|
302
337
|
...processed.requests ?? []
|
|
@@ -323,10 +358,15 @@ async function buildPageJit({ pageId, pageRegistry, context, directories, logger
|
|
|
323
358
|
throw error;
|
|
324
359
|
}
|
|
325
360
|
// Write page artifacts
|
|
326
|
-
await writePageJit({
|
|
361
|
+
const { tailwindChanged } = await writePageJit({
|
|
327
362
|
page: finalPage,
|
|
328
|
-
context: buildContext
|
|
363
|
+
context: buildContext,
|
|
364
|
+
tailwindContent
|
|
329
365
|
});
|
|
366
|
+
// Attached after the disk write (like _warnings) so it never persists in
|
|
367
|
+
// artifacts — the JIT server uses it to decide whether to trigger a CSS
|
|
368
|
+
// recompile for newly discovered tailwind class candidates.
|
|
369
|
+
finalPage._tailwindChanged = tailwindChanged;
|
|
330
370
|
// Attach warnings after disk write so they don't persist in artifacts
|
|
331
371
|
if (buildWarnings.length > 0) {
|
|
332
372
|
finalPage._warnings = buildWarnings.map((w)=>({
|
|
@@ -47,6 +47,18 @@ function collectSkeletonSourceFiles({ components, context }) {
|
|
|
47
47
|
for (const moduleEntry of Object.values(context.modules ?? {})){
|
|
48
48
|
walkRefIds(moduleEntry.consumerVars, refIds);
|
|
49
49
|
}
|
|
50
|
+
// The walker only ~r-tags resolved _ref content, never the root file's own
|
|
51
|
+
// objects — so a scalar ref parented directly on the root (e.g.
|
|
52
|
+
// app.html.appendHead: {_ref: head.html}) has no collected ancestor and
|
|
53
|
+
// would be missed by the descendant scan. Add the root ref id explicitly.
|
|
54
|
+
if (context.rootRefDef?.id != null) {
|
|
55
|
+
refIds.add(context.rootRefDef.id);
|
|
56
|
+
}
|
|
57
|
+
// Page refs are stop boundaries for the descendant scan: with the root id
|
|
58
|
+
// collected, a scalar ref'd inside a page file would otherwise reach the
|
|
59
|
+
// root through its page ref and wrongly become a skeleton source.
|
|
60
|
+
const pageRefIds = new Set();
|
|
61
|
+
walkRefIds(components.pages ?? [], pageRefIds);
|
|
50
62
|
const sourceFiles = new Set();
|
|
51
63
|
// Walk parent chains for each collected ref ID
|
|
52
64
|
for (const refId of refIds){
|
|
@@ -65,6 +77,7 @@ function collectSkeletonSourceFiles({ components, context }) {
|
|
|
65
77
|
// their paths if any ancestor in their parent chain is a collected ref ID.
|
|
66
78
|
for (const [id, entry] of Object.entries(context.refMap)){
|
|
67
79
|
if (refIds.has(id)) continue;
|
|
80
|
+
if (pageRefIds.has(id)) continue;
|
|
68
81
|
let current = entry.parent;
|
|
69
82
|
while(current != null){
|
|
70
83
|
if (refIds.has(current)) {
|
|
@@ -73,6 +86,7 @@ function collectSkeletonSourceFiles({ components, context }) {
|
|
|
73
86
|
}
|
|
74
87
|
break;
|
|
75
88
|
}
|
|
89
|
+
if (pageRefIds.has(current)) break;
|
|
76
90
|
const parentEntry = context.refMap[current];
|
|
77
91
|
if (!parentEntry) break;
|
|
78
92
|
current = parentEntry.parent;
|
|
@@ -21,17 +21,25 @@ import tryBuildStep from '../../utils/tryBuildStep.js';
|
|
|
21
21
|
import addDefaultPages from '../addDefaultPages/addDefaultPages.js';
|
|
22
22
|
import addKeys from '../addKeys.js';
|
|
23
23
|
import buildApp from '../buildApp.js';
|
|
24
|
+
import buildAppMeta from '../buildAppMeta.js';
|
|
24
25
|
import buildAuth from '../buildAuth/buildAuth.js';
|
|
25
26
|
import buildConnections from '../buildConnections.js';
|
|
26
27
|
import buildAgents from '../buildAgents.js';
|
|
27
28
|
import buildApi from '../buildApi/buildApi.js';
|
|
28
29
|
import buildLogger from '../buildLogger.js';
|
|
29
30
|
import buildImports from '../buildImports/buildImports.js';
|
|
31
|
+
import buildMcp from '../buildMcp.js';
|
|
30
32
|
import buildMenu from '../buildMenu.js';
|
|
31
33
|
import buildModuleDefs from '../buildModuleDefs.js';
|
|
32
34
|
import buildModules from '../buildModules.js';
|
|
35
|
+
import buildNotifications from '../buildNotifications.js';
|
|
33
36
|
import buildRefs from '../buildRefs/buildRefs.js';
|
|
37
|
+
import precomputeRuntimeOperators from '../buildRefs/precomputeRuntimeOperators.js';
|
|
38
|
+
import { serializeRegistry } from '../buildRefs/deferredRegistry.js';
|
|
34
39
|
import buildTypes from '../buildTypes.js';
|
|
40
|
+
import buildWebsockets from '../buildWebsockets.js';
|
|
41
|
+
import validateCallAgentSteps from '../validateCallAgentSteps.js';
|
|
42
|
+
import validateRenderNotificationSteps from '../validateRenderNotificationSteps.js';
|
|
35
43
|
import cleanBuildDirectory from '../cleanBuildDirectory.js';
|
|
36
44
|
import copyAgentFileSystems from '../copyAgentFileSystems.js';
|
|
37
45
|
import copyPublicFolder from '../copyPublicFolder.js';
|
|
@@ -44,8 +52,11 @@ import writeConfig from '../writeConfig.js';
|
|
|
44
52
|
import writeConnections from '../writeConnections.js';
|
|
45
53
|
import writeAgents from '../writeAgents.js';
|
|
46
54
|
import writeApi from '../writeApi.js';
|
|
55
|
+
import writeMcp from '../writeMcp.js';
|
|
56
|
+
import writeNotifications from '../writeNotifications.js';
|
|
47
57
|
import writeGlobal from '../writeGlobal.js';
|
|
48
58
|
import writeJs from '../buildJs/writeJs.js';
|
|
59
|
+
import writeWebsockets from '../writeWebsockets.js';
|
|
49
60
|
import writeLogger from '../writeLogger.js';
|
|
50
61
|
import codegenI18nLocales from '../codegenI18nLocales.js';
|
|
51
62
|
import writeI18n from '../writeI18n.js';
|
|
@@ -53,6 +64,7 @@ import writeTheme from '../writeTheme.js';
|
|
|
53
64
|
import writeMaps from '../writeMaps.js';
|
|
54
65
|
import updateServerPackageJson from '../full/updateServerPackageJson.js';
|
|
55
66
|
import writeMenus from '../writeMenus.js';
|
|
67
|
+
import writeTypes from '../full/writeTypes.js';
|
|
56
68
|
import writePageRegistry from './writePageRegistry.js';
|
|
57
69
|
import writePluginImports from '../writePluginImports/writePluginImports.js';
|
|
58
70
|
import addInstalledTypes from './addInstalledTypes.js';
|
|
@@ -66,6 +78,12 @@ async function shallowBuild(options) {
|
|
|
66
78
|
let context;
|
|
67
79
|
try {
|
|
68
80
|
context = createContext(options);
|
|
81
|
+
// Phase 0: Resolve root app metadata before any operator evaluation.
|
|
82
|
+
await buildAppMeta({
|
|
83
|
+
context
|
|
84
|
+
});
|
|
85
|
+
// Surface bad root metadata before module operators evaluate against it.
|
|
86
|
+
logCollectedErrors(context);
|
|
69
87
|
// Phase 1: Build module definitions
|
|
70
88
|
await buildModuleDefs({
|
|
71
89
|
context
|
|
@@ -98,6 +116,18 @@ async function shallowBuild(options) {
|
|
|
98
116
|
components,
|
|
99
117
|
context
|
|
100
118
|
});
|
|
119
|
+
// Phase 3.5: Constant-fold static runtime operators, mirroring the full
|
|
120
|
+
// build (index.js). Without this, content preserved at skeleton — inline
|
|
121
|
+
// pages, slot content, module components consumed into them — reaches
|
|
122
|
+
// testSchema (spurious warnings) and the served dev artifacts (broken
|
|
123
|
+
// block ids: the client never operator-evaluates id positions) with raw
|
|
124
|
+
// runtime operators. Ref-backed page content is already stripped here and
|
|
125
|
+
// folds per page in buildPageJit.
|
|
126
|
+
components = precomputeRuntimeOperators({
|
|
127
|
+
context,
|
|
128
|
+
input: components,
|
|
129
|
+
refDef: context.rootRefDef
|
|
130
|
+
});
|
|
101
131
|
// addKeys + testSchema first for error location info
|
|
102
132
|
tryBuildStep(addKeys, 'addKeys', {
|
|
103
133
|
components,
|
|
@@ -124,6 +154,9 @@ async function shallowBuild(options) {
|
|
|
124
154
|
components,
|
|
125
155
|
context
|
|
126
156
|
});
|
|
157
|
+
// appMeta is computed in Phase 0; attach it here (where buildApp used to
|
|
158
|
+
// create it) so the following addKeys pass keys it identically.
|
|
159
|
+
components.appMeta = context.appMeta;
|
|
127
160
|
tryBuildStep(buildLogger, 'buildLogger', {
|
|
128
161
|
components,
|
|
129
162
|
context
|
|
@@ -156,6 +189,28 @@ async function shallowBuild(options) {
|
|
|
156
189
|
components,
|
|
157
190
|
context
|
|
158
191
|
});
|
|
192
|
+
tryBuildStep(buildMcp, 'buildMcp', {
|
|
193
|
+
components,
|
|
194
|
+
context
|
|
195
|
+
});
|
|
196
|
+
tryBuildStep(buildWebsockets, 'buildWebsockets', {
|
|
197
|
+
components,
|
|
198
|
+
context
|
|
199
|
+
});
|
|
200
|
+
tryBuildStep(buildNotifications, 'buildNotifications', {
|
|
201
|
+
components,
|
|
202
|
+
context
|
|
203
|
+
});
|
|
204
|
+
// Cross-config validations — need buildApi (stepIds) and the buildAgents/
|
|
205
|
+
// buildNotifications id sets. Match the full build (index.js).
|
|
206
|
+
tryBuildStep(validateCallAgentSteps, 'validateCallAgentSteps', {
|
|
207
|
+
components,
|
|
208
|
+
context
|
|
209
|
+
});
|
|
210
|
+
tryBuildStep(validateRenderNotificationSteps, 'validateRenderNotificationSteps', {
|
|
211
|
+
components,
|
|
212
|
+
context
|
|
213
|
+
});
|
|
159
214
|
const { pageRegistry, sourcelessPageArtifacts } = buildShallowPages({
|
|
160
215
|
components,
|
|
161
216
|
context
|
|
@@ -175,7 +230,7 @@ async function shallowBuild(options) {
|
|
|
175
230
|
// Update server package.json before addInstalledTypes so that addInstalledTypes
|
|
176
231
|
// sees the full set of dependencies on every run (not just after the first build).
|
|
177
232
|
// This prevents plugin import files from differing between the initial and
|
|
178
|
-
// subsequent builds, which would trigger unnecessary
|
|
233
|
+
// subsequent builds, which would trigger unnecessary server restarts.
|
|
179
234
|
await updateServerPackageJson({
|
|
180
235
|
components,
|
|
181
236
|
context
|
|
@@ -221,10 +276,22 @@ async function shallowBuild(options) {
|
|
|
221
276
|
components,
|
|
222
277
|
context
|
|
223
278
|
});
|
|
279
|
+
await writeMcp({
|
|
280
|
+
components,
|
|
281
|
+
context
|
|
282
|
+
});
|
|
224
283
|
await writeAgents({
|
|
225
284
|
components,
|
|
226
285
|
context
|
|
227
286
|
});
|
|
287
|
+
await writeWebsockets({
|
|
288
|
+
components,
|
|
289
|
+
context
|
|
290
|
+
});
|
|
291
|
+
await writeNotifications({
|
|
292
|
+
components,
|
|
293
|
+
context
|
|
294
|
+
});
|
|
228
295
|
await writeConfig({
|
|
229
296
|
components,
|
|
230
297
|
context
|
|
@@ -255,6 +322,9 @@ async function shallowBuild(options) {
|
|
|
255
322
|
await context.writeBuildArtifact('connectionIds.json', JSON.stringify([
|
|
256
323
|
...context.connectionIds
|
|
257
324
|
].sort()));
|
|
325
|
+
await context.writeBuildArtifact('websocketIds.json', JSON.stringify([
|
|
326
|
+
...context.websocketIds
|
|
327
|
+
].sort()));
|
|
258
328
|
await context.writeBuildArtifact('skeletonSourceFiles.json', JSON.stringify([
|
|
259
329
|
...skeletonSourceFiles
|
|
260
330
|
].sort()));
|
|
@@ -262,6 +332,13 @@ async function shallowBuild(options) {
|
|
|
262
332
|
components,
|
|
263
333
|
context
|
|
264
334
|
});
|
|
335
|
+
// The dev client bundle imports every installed type (addInstalledTypes),
|
|
336
|
+
// so types.json here describes that full bundle — dynamic page content
|
|
337
|
+
// resolution validates fragment types against it at page get.
|
|
338
|
+
await writeTypes({
|
|
339
|
+
components,
|
|
340
|
+
context
|
|
341
|
+
});
|
|
265
342
|
await writeJs({
|
|
266
343
|
context
|
|
267
344
|
});
|
|
@@ -276,6 +353,9 @@ async function shallowBuild(options) {
|
|
|
276
353
|
...context.installedPackages ?? []
|
|
277
354
|
]));
|
|
278
355
|
await context.writeBuildArtifact('modules.json', serializer.serializeToString(context.modules ?? {}));
|
|
356
|
+
// Deferred-record bodies referenced by placeholders in modules.json.
|
|
357
|
+
// JIT hydrates the registry from this artifact (hydrateDeferredRecords).
|
|
358
|
+
await context.writeBuildArtifact('deferredRecords.json', serializeRegistry(context));
|
|
279
359
|
await writePluginImports({
|
|
280
360
|
components,
|
|
281
361
|
context
|