@lowdefy/build 4.5.2 → 4.7.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/addDefaultPages/404.js +1 -1
- package/dist/build/addDefaultPages/addDefaultPages.js +10 -4
- package/dist/build/addKeys.js +85 -29
- package/dist/build/buildApi/buildApi.js +27 -8
- package/dist/build/buildApi/buildEndpoint.js +7 -1
- package/dist/build/buildApi/buildRoutine/buildControl.js +1 -1
- package/dist/build/buildApi/buildRoutine/buildRoutine.js +1 -1
- package/dist/build/buildApi/buildRoutine/buildStep.js +1 -1
- package/dist/build/buildApi/buildRoutine/controlTypes.js +34 -11
- package/dist/build/buildApi/buildRoutine/countControl.js +1 -1
- package/dist/build/buildApi/buildRoutine/countStepTypes.js +2 -2
- package/dist/build/buildApi/buildRoutine/setStepId.js +1 -1
- package/dist/build/buildApi/buildRoutine/validateStep.js +30 -9
- package/dist/build/buildApi/validateEndpoint.js +36 -7
- package/dist/build/buildApi/validateStepReferences.js +65 -0
- package/dist/build/buildApp.js +1 -1
- package/dist/build/buildAuth/buildApiAuth.js +7 -3
- package/dist/build/buildAuth/buildAuth.js +7 -4
- package/dist/build/buildAuth/buildAuthPlugins.js +42 -13
- package/dist/build/buildAuth/buildPageAuth.js +14 -3
- package/dist/build/buildAuth/getApiRoles.js +1 -1
- package/dist/build/buildAuth/getPageRoles.js +1 -1
- package/dist/build/buildAuth/getProtectedApi.js +1 -1
- package/dist/build/buildAuth/getProtectedPages.js +1 -1
- package/dist/build/buildAuth/validateAuthConfig.js +39 -5
- package/dist/build/buildAuth/validateMutualExclusivity.js +13 -5
- package/dist/build/buildConnections.js +23 -24
- package/dist/build/buildImports/buildIconImports.js +1 -1
- package/dist/build/buildImports/buildImports.js +1 -1
- package/dist/build/buildImports/buildImportsDev.js +1 -1
- package/dist/build/buildImports/buildImportsProd.js +1 -1
- package/dist/build/buildImports/buildStyleImports.js +1 -1
- package/dist/build/buildImports/defaultIconsDev.js +1 -1
- package/dist/build/buildImports/defaultIconsProd.js +1 -1
- package/dist/build/buildJs/generateJsFile.js +1 -1
- package/dist/build/buildJs/jsMapParser.js +1 -1
- package/dist/build/buildJs/writeJs.js +1 -1
- package/dist/build/buildLogger.js +41 -0
- package/dist/build/buildMenu.js +36 -12
- package/dist/build/buildPages/buildBlock/buildBlock.js +1 -1
- package/dist/build/buildPages/buildBlock/buildEvents.js +79 -9
- package/dist/build/buildPages/buildBlock/buildRequests.js +38 -8
- package/dist/build/buildPages/buildBlock/buildSubBlocks.js +6 -2
- package/dist/build/buildPages/buildBlock/countBlockOperators.js +1 -1
- package/dist/build/buildPages/buildBlock/countBlockTypes.js +2 -2
- package/dist/build/buildPages/buildBlock/moveSkeletonBlocksToArea.js +6 -2
- package/dist/build/buildPages/buildBlock/moveSubBlocksToArea.js +6 -2
- package/dist/build/buildPages/buildBlock/setBlockId.js +1 -1
- package/dist/build/buildPages/buildBlock/validateBlock.js +25 -7
- package/dist/build/buildPages/buildPage.js +35 -6
- package/dist/build/buildPages/validateLinkReferences.js +33 -0
- package/dist/build/buildPages/validatePayloadReferences.js +59 -0
- package/dist/build/buildPages/validateRequestReferences.js +33 -0
- package/dist/build/buildPages/validateServerStateReferences.js +41 -0
- package/dist/build/buildPages/validateStateReferences.js +79 -0
- package/dist/build/buildRefs/buildRefs.js +40 -7
- package/dist/build/buildRefs/evaluateStaticOperators.js +52 -0
- package/dist/build/buildRefs/getConfigFile.js +25 -6
- package/dist/build/buildRefs/getKey.js +1 -1
- package/dist/build/buildRefs/getRefContent.js +1 -1
- package/dist/build/buildRefs/getRefPath.js +1 -1
- package/dist/build/buildRefs/getUserJavascriptFunction.js +18 -4
- package/dist/build/buildRefs/makeRefDefinition.js +10 -5
- package/dist/build/buildRefs/parseNunjucks.js +1 -1
- package/dist/build/buildRefs/parseRefContent.js +108 -7
- package/dist/build/buildRefs/runRefResolver.js +11 -3
- package/dist/build/buildRefs/runTransformer.js +7 -3
- package/dist/build/buildRefs/walker.js +340 -0
- package/dist/build/buildTypes.js +22 -7
- package/dist/build/cleanBuildDirectory.js +1 -1
- package/dist/build/collectDynamicIdentifiers.js +35 -0
- package/dist/build/collectTypeNames.js +36 -0
- package/dist/build/copyPublicFolder.js +1 -1
- package/dist/build/{buildJs → full}/buildJs.js +3 -3
- package/dist/build/full/buildPages.js +87 -0
- package/dist/build/{buildPages → full}/buildTestPage.js +15 -3
- package/dist/build/{updateServerPackageJson.js → full/updateServerPackageJson.js} +1 -1
- package/dist/build/{writePages.js → full/writePages.js} +1 -1
- package/dist/build/{writeRequests.js → full/writeRequests.js} +11 -3
- package/dist/build/{writeTypes.js → full/writeTypes.js} +1 -1
- package/dist/build/jit/addInstalledTypes.js +51 -0
- package/dist/build/jit/buildJsShallow.js +41 -0
- package/dist/build/jit/buildPageJit.js +271 -0
- package/dist/build/jit/buildShallowPages.js +90 -0
- package/dist/build/jit/createPageRegistry.js +85 -0
- package/dist/build/jit/detectMissingPluginPackages.js +62 -0
- package/dist/build/jit/isPageContentPath.js +24 -0
- package/dist/build/jit/pageContentKeys.js +26 -0
- package/dist/build/jit/shallowBuild.js +242 -0
- package/dist/build/jit/updateServerPackageJsonJit.js +33 -0
- package/dist/build/jit/validatePageTypes.js +73 -0
- package/dist/build/jit/writePageJit.js +44 -0
- package/dist/build/jit/writePageRegistry.js +23 -0
- package/dist/build/jit/writeSourcelessPages.js +23 -0
- package/dist/build/testSchema.js +45 -7
- package/dist/build/validateConfig.js +2 -2
- package/dist/build/validateOperatorsDynamic.js +28 -0
- package/dist/build/writeApi.js +1 -1
- package/dist/build/writeApp.js +1 -1
- package/dist/build/writeAuth.js +1 -1
- package/dist/build/writeConfig.js +1 -1
- package/dist/build/writeConnections.js +1 -1
- package/dist/build/writeGlobal.js +1 -1
- package/dist/build/writeLogger.js +19 -0
- package/dist/build/writeMaps.js +1 -1
- package/dist/build/writeMenus.js +1 -1
- package/dist/build/writePluginImports/generateImportFile.js +1 -1
- package/dist/build/writePluginImports/writeActionImports.js +1 -1
- package/dist/build/writePluginImports/writeActionSchemaMap.js +42 -0
- package/dist/build/writePluginImports/writeAuthImports.js +1 -1
- package/dist/build/writePluginImports/writeBlockImports.js +1 -1
- package/dist/build/writePluginImports/writeBlockSchemaMap.js +42 -0
- package/dist/build/writePluginImports/writeConnectionImports.js +1 -1
- package/dist/build/writePluginImports/writeIconImports.js +1 -1
- package/dist/build/writePluginImports/writeOperatorImports.js +1 -1
- package/dist/build/writePluginImports/writeOperatorSchemaMap.js +49 -0
- package/dist/build/writePluginImports/writePluginImports.js +16 -1
- package/dist/build/writePluginImports/writeStyleImports.js +1 -1
- package/dist/createContext.js +16 -4
- package/dist/defaultTypesMap.js +479 -457
- package/dist/index.js +190 -127
- package/dist/indexDev.js +19 -0
- package/dist/lowdefySchema.js +588 -0
- package/dist/scripts/generateDefaultTypes.js +2 -1
- package/dist/scripts/run.js +1 -1
- package/dist/{test → test-utils}/buildRefs/testBuildRefsAsyncFunction.js +1 -1
- package/dist/{test → test-utils}/buildRefs/testBuildRefsErrorResolver.js +1 -1
- package/dist/{test → test-utils}/buildRefs/testBuildRefsNullResolver.js +1 -1
- package/dist/{test → test-utils}/buildRefs/testBuildRefsParsingResolver.js +1 -1
- package/dist/{test → test-utils}/buildRefs/testBuildRefsResolver.js +1 -1
- package/dist/{test → test-utils}/buildRefs/testBuildRefsTransform.js +1 -1
- package/dist/{test → test-utils}/buildRefs/testBuildRefsTransformIdentity.js +1 -1
- package/dist/test-utils/buildRefs/testJitPageResolver.js +24 -0
- package/dist/test-utils/createTestLogger.js +36 -0
- package/dist/test-utils/parseTestYaml.js +126 -0
- package/dist/test-utils/runBuild.js +228 -0
- package/dist/test-utils/runBuildForSnapshots.js +704 -0
- package/dist/{test → test-utils}/testContext.js +12 -2
- package/dist/utils/collectExceptions.js +34 -0
- package/dist/utils/countOperators.js +31 -12
- package/dist/utils/createBuildHandleError.js +38 -0
- package/dist/utils/createCheckDuplicateId.js +15 -9
- package/dist/utils/createCounter.js +16 -3
- package/dist/utils/createHandleWarning.js +41 -0
- package/dist/utils/createPluginTypesMap.js +1 -1
- package/dist/utils/extractOperatorKey.js +36 -0
- package/dist/utils/findConfigKey.js +37 -0
- package/dist/utils/findSimilarString.js +53 -0
- package/dist/utils/logCollectedErrors.js +30 -0
- package/dist/utils/makeId.js +16 -8
- package/dist/utils/preserveMetaProperties.js +27 -0
- package/dist/utils/readConfigFile.js +1 -1
- package/dist/utils/setNonEnumerableProperty.js +23 -0
- package/dist/utils/traverseConfig.js +43 -0
- package/dist/utils/tryBuildStep.js +46 -0
- package/dist/utils/writeBuildArtifact.js +1 -1
- package/package.json +46 -41
- package/dist/build/buildPages/buildPages.js +0 -31
- package/dist/build/buildRefs/evaluateBuildOperators.js +0 -34
- package/dist/build/buildRefs/getRefsFromFile.js +0 -37
- package/dist/build/buildRefs/populateRefs.js +0 -43
- package/dist/build/buildRefs/recursiveBuild.js +0 -85
- package/dist/utils/formatErrorMessage.js +0 -56
|
@@ -0,0 +1,340 @@
|
|
|
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
|
+
*/ import { get, type } from '@lowdefy/helpers';
|
|
16
|
+
import { ConfigError } from '@lowdefy/errors';
|
|
17
|
+
import { evaluateOperators } from '@lowdefy/operators';
|
|
18
|
+
import makeRefDefinition from './makeRefDefinition.js';
|
|
19
|
+
import getRefContent from './getRefContent.js';
|
|
20
|
+
import runTransformer from './runTransformer.js';
|
|
21
|
+
import getKey from './getKey.js';
|
|
22
|
+
import setNonEnumerableProperty from '../../utils/setNonEnumerableProperty.js';
|
|
23
|
+
import collectExceptions from '../../utils/collectExceptions.js';
|
|
24
|
+
let WalkContext = class WalkContext {
|
|
25
|
+
child(segment) {
|
|
26
|
+
return new WalkContext({
|
|
27
|
+
buildContext: this.buildContext,
|
|
28
|
+
refId: this.refId,
|
|
29
|
+
sourceRefId: this.sourceRefId,
|
|
30
|
+
vars: this.vars,
|
|
31
|
+
path: this.path ? `${this.path}.${segment}` : segment,
|
|
32
|
+
currentFile: this.currentFile,
|
|
33
|
+
refChain: this.refChain,
|
|
34
|
+
operators: this.operators,
|
|
35
|
+
env: this.env,
|
|
36
|
+
dynamicIdentifiers: this.dynamicIdentifiers,
|
|
37
|
+
shouldStop: this.shouldStop
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
forRef({ refId, vars, filePath }) {
|
|
41
|
+
const newChain = new Set(this.refChain);
|
|
42
|
+
if (filePath) {
|
|
43
|
+
newChain.add(filePath);
|
|
44
|
+
}
|
|
45
|
+
return new WalkContext({
|
|
46
|
+
buildContext: this.buildContext,
|
|
47
|
+
refId,
|
|
48
|
+
sourceRefId: this.refId,
|
|
49
|
+
vars: vars ?? {},
|
|
50
|
+
path: this.path,
|
|
51
|
+
currentFile: filePath ?? this.currentFile,
|
|
52
|
+
refChain: newChain,
|
|
53
|
+
operators: this.operators,
|
|
54
|
+
env: this.env,
|
|
55
|
+
dynamicIdentifiers: this.dynamicIdentifiers,
|
|
56
|
+
shouldStop: this.shouldStop
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
collectError(error) {
|
|
60
|
+
collectExceptions(this.buildContext, error);
|
|
61
|
+
}
|
|
62
|
+
get refMap() {
|
|
63
|
+
return this.buildContext.refMap;
|
|
64
|
+
}
|
|
65
|
+
get unresolvedRefVars() {
|
|
66
|
+
return this.buildContext.unresolvedRefVars;
|
|
67
|
+
}
|
|
68
|
+
constructor({ buildContext, refId, sourceRefId, vars, path, currentFile, refChain, operators, env, dynamicIdentifiers, shouldStop }){
|
|
69
|
+
this.buildContext = buildContext;
|
|
70
|
+
this.refId = refId;
|
|
71
|
+
this.sourceRefId = sourceRefId;
|
|
72
|
+
this.vars = vars;
|
|
73
|
+
this.path = path;
|
|
74
|
+
this.currentFile = currentFile;
|
|
75
|
+
this.refChain = refChain;
|
|
76
|
+
this.operators = operators;
|
|
77
|
+
this.env = env;
|
|
78
|
+
this.dynamicIdentifiers = dynamicIdentifiers;
|
|
79
|
+
this.shouldStop = shouldStop;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
// Detect _build.* operator objects: single non-tilde key starting with '_build.'
|
|
83
|
+
function isBuildOperator(node) {
|
|
84
|
+
const keys = Object.keys(node);
|
|
85
|
+
const nonTildeKeys = keys.filter((k)=>!k.startsWith('~'));
|
|
86
|
+
return nonTildeKeys.length === 1 && nonTildeKeys[0].startsWith('_build.');
|
|
87
|
+
}
|
|
88
|
+
// Set ~r as non-enumerable if not already present
|
|
89
|
+
function tagRef(node, refId) {
|
|
90
|
+
if (type.isObject(node) || type.isArray(node)) {
|
|
91
|
+
if (node['~r'] === undefined) {
|
|
92
|
+
setNonEnumerableProperty(node, '~r', refId);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Recursively set ~r on all objects/arrays that don't already have it
|
|
97
|
+
function tagRefDeep(node, refId) {
|
|
98
|
+
if (!type.isObject(node) && !type.isArray(node)) return;
|
|
99
|
+
if (node['~r'] !== undefined) return;
|
|
100
|
+
setNonEnumerableProperty(node, '~r', refId);
|
|
101
|
+
if (type.isArray(node)) {
|
|
102
|
+
for(let i = 0; i < node.length; i++){
|
|
103
|
+
tagRefDeep(node[i], refId);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
for (const key of Object.keys(node)){
|
|
107
|
+
tagRefDeep(node[key], refId);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Deep clone preserving ~r, ~l, ~k non-enumerable markers.
|
|
112
|
+
// Used before resolving ref def path/vars to prevent mutation of stored originals.
|
|
113
|
+
function cloneForResolve(value) {
|
|
114
|
+
if (!type.isObject(value) && !type.isArray(value)) return value;
|
|
115
|
+
if (type.isArray(value)) {
|
|
116
|
+
const clone = value.map((item)=>cloneForResolve(item));
|
|
117
|
+
if (value['~r'] !== undefined) setNonEnumerableProperty(clone, '~r', value['~r']);
|
|
118
|
+
if (value['~l'] !== undefined) setNonEnumerableProperty(clone, '~l', value['~l']);
|
|
119
|
+
if (value['~k'] !== undefined) setNonEnumerableProperty(clone, '~k', value['~k']);
|
|
120
|
+
if (value['~arr'] !== undefined) setNonEnumerableProperty(clone, '~arr', value['~arr']);
|
|
121
|
+
return clone;
|
|
122
|
+
}
|
|
123
|
+
const clone = {};
|
|
124
|
+
for (const key of Object.keys(value)){
|
|
125
|
+
clone[key] = cloneForResolve(value[key]);
|
|
126
|
+
}
|
|
127
|
+
if (value['~r'] !== undefined) setNonEnumerableProperty(clone, '~r', value['~r']);
|
|
128
|
+
if (value['~l'] !== undefined) setNonEnumerableProperty(clone, '~l', value['~l']);
|
|
129
|
+
if (value['~k'] !== undefined) setNonEnumerableProperty(clone, '~k', value['~k']);
|
|
130
|
+
return clone;
|
|
131
|
+
}
|
|
132
|
+
// Deep clone a var value, preserving markers and setting ~r provenance.
|
|
133
|
+
// When sourceRefId is null, preserves the template's existing ~r markers.
|
|
134
|
+
function cloneVarValue(value, sourceRefId) {
|
|
135
|
+
if (!type.isObject(value) && !type.isArray(value)) return value;
|
|
136
|
+
return cloneDeepWithProvenance(value, sourceRefId);
|
|
137
|
+
}
|
|
138
|
+
function cloneDeepWithProvenance(node, sourceRefId) {
|
|
139
|
+
if (!type.isObject(node) && !type.isArray(node)) return node;
|
|
140
|
+
if (type.isArray(node)) {
|
|
141
|
+
const clone = node.map((item)=>cloneDeepWithProvenance(item, sourceRefId));
|
|
142
|
+
if (node['~r'] !== undefined) {
|
|
143
|
+
setNonEnumerableProperty(clone, '~r', node['~r']);
|
|
144
|
+
} else if (sourceRefId) {
|
|
145
|
+
setNonEnumerableProperty(clone, '~r', sourceRefId);
|
|
146
|
+
}
|
|
147
|
+
if (node['~l'] !== undefined) setNonEnumerableProperty(clone, '~l', node['~l']);
|
|
148
|
+
if (node['~k'] !== undefined) setNonEnumerableProperty(clone, '~k', node['~k']);
|
|
149
|
+
if (node['~arr'] !== undefined) setNonEnumerableProperty(clone, '~arr', node['~arr']);
|
|
150
|
+
return clone;
|
|
151
|
+
}
|
|
152
|
+
const clone = {};
|
|
153
|
+
for (const key of Object.keys(node)){
|
|
154
|
+
clone[key] = cloneDeepWithProvenance(node[key], sourceRefId);
|
|
155
|
+
}
|
|
156
|
+
if (node['~r'] !== undefined) {
|
|
157
|
+
setNonEnumerableProperty(clone, '~r', node['~r']);
|
|
158
|
+
} else if (sourceRefId) {
|
|
159
|
+
setNonEnumerableProperty(clone, '~r', sourceRefId);
|
|
160
|
+
}
|
|
161
|
+
if (node['~l'] !== undefined) setNonEnumerableProperty(clone, '~l', node['~l']);
|
|
162
|
+
if (node['~k'] !== undefined) setNonEnumerableProperty(clone, '~k', node['~k']);
|
|
163
|
+
return clone;
|
|
164
|
+
}
|
|
165
|
+
// Evaluate a _build.* operator using evaluateOperators
|
|
166
|
+
function evaluateBuildOperator(node, ctx) {
|
|
167
|
+
const { output, errors } = evaluateOperators({
|
|
168
|
+
input: node,
|
|
169
|
+
operators: ctx.operators,
|
|
170
|
+
operatorPrefix: '_build.',
|
|
171
|
+
env: ctx.env,
|
|
172
|
+
dynamicIdentifiers: ctx.dynamicIdentifiers
|
|
173
|
+
});
|
|
174
|
+
if (errors.length > 0) {
|
|
175
|
+
errors.forEach((error)=>{
|
|
176
|
+
error.filePath = error.refId ? ctx.refMap[error.refId]?.path : ctx.currentFile;
|
|
177
|
+
ctx.collectError(error);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return output;
|
|
181
|
+
}
|
|
182
|
+
// Resolve a _var node
|
|
183
|
+
function resolveVar(node, ctx) {
|
|
184
|
+
const varDef = node._var;
|
|
185
|
+
// String form: { _var: "key" }
|
|
186
|
+
if (type.isString(varDef)) {
|
|
187
|
+
const value = get(ctx.vars, varDef, {
|
|
188
|
+
default: null
|
|
189
|
+
});
|
|
190
|
+
return cloneVarValue(value, ctx.sourceRefId);
|
|
191
|
+
}
|
|
192
|
+
// Object form: { _var: { key, default } }
|
|
193
|
+
if (type.isObject(varDef) && type.isString(varDef.key)) {
|
|
194
|
+
const varFromParent = get(ctx.vars, varDef.key);
|
|
195
|
+
// Var provided (even if null) → use parent's sourceRefId for location
|
|
196
|
+
if (!type.isUndefined(varFromParent)) {
|
|
197
|
+
return cloneVarValue(varFromParent, ctx.sourceRefId);
|
|
198
|
+
}
|
|
199
|
+
// Not provided → use default, preserve template's ~r
|
|
200
|
+
const defaultValue = type.isNone(varDef.default) ? null : varDef.default;
|
|
201
|
+
return cloneVarValue(defaultValue, null);
|
|
202
|
+
}
|
|
203
|
+
throw new ConfigError('_var operator takes a string or object with "key" field as arguments.', {
|
|
204
|
+
filePath: ctx.currentFile
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
// Resolve a _ref node (12-step ref handling)
|
|
208
|
+
async function resolveRef(node, ctx) {
|
|
209
|
+
// 1. Create ref definition
|
|
210
|
+
const lineNumber = node['~l'];
|
|
211
|
+
const refDef = makeRefDefinition(node._ref, ctx.refId, ctx.refMap, lineNumber);
|
|
212
|
+
// 2. Store unresolved vars before resolution mutates them, and clone so
|
|
213
|
+
// resolution operates on a copy (preserving original.vars for resolver refs).
|
|
214
|
+
const varKeys = Object.keys(refDef.vars);
|
|
215
|
+
if (varKeys.length > 0) {
|
|
216
|
+
ctx.unresolvedRefVars[refDef.id] = refDef.vars;
|
|
217
|
+
refDef.vars = cloneForResolve(refDef.vars);
|
|
218
|
+
}
|
|
219
|
+
// 3. Resolve dynamic path/vars/key
|
|
220
|
+
if (type.isObject(refDef.path)) {
|
|
221
|
+
refDef.path = await resolve(cloneForResolve(refDef.path), ctx);
|
|
222
|
+
}
|
|
223
|
+
for (const varKey of varKeys){
|
|
224
|
+
if (type.isObject(refDef.vars[varKey]) || type.isArray(refDef.vars[varKey])) {
|
|
225
|
+
refDef.vars[varKey] = await resolve(refDef.vars[varKey], ctx);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (type.isObject(refDef.key)) {
|
|
229
|
+
refDef.key = await resolve(cloneForResolve(refDef.key), ctx);
|
|
230
|
+
}
|
|
231
|
+
// 4. Update refMap with resolved path; store original for resolver refs
|
|
232
|
+
ctx.refMap[refDef.id].path = refDef.path;
|
|
233
|
+
if (!refDef.path) {
|
|
234
|
+
ctx.refMap[refDef.id].original = refDef.original;
|
|
235
|
+
}
|
|
236
|
+
// 5. Circular detection
|
|
237
|
+
if (refDef.path && ctx.refChain.has(refDef.path)) {
|
|
238
|
+
const chainDisplay = [
|
|
239
|
+
...ctx.refChain,
|
|
240
|
+
refDef.path
|
|
241
|
+
].join('\n -> ');
|
|
242
|
+
throw new ConfigError(`Circular reference detected. File "${refDef.path}" references itself through:\n -> ${chainDisplay}`, {
|
|
243
|
+
filePath: ctx.currentFile,
|
|
244
|
+
lineNumber: ctx.currentFile ? lineNumber : null
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
// Steps 6-12: File operations that can fail independently per ref.
|
|
248
|
+
// Errors are collected so the walker can continue processing sibling refs,
|
|
249
|
+
// allowing multiple errors to be reported at once.
|
|
250
|
+
try {
|
|
251
|
+
// 6. Load content
|
|
252
|
+
let content = await getRefContent({
|
|
253
|
+
context: ctx.buildContext,
|
|
254
|
+
refDef,
|
|
255
|
+
referencedFrom: ctx.currentFile
|
|
256
|
+
});
|
|
257
|
+
// 7. Create child context for the ref file
|
|
258
|
+
const childCtx = ctx.forRef({
|
|
259
|
+
refId: refDef.id,
|
|
260
|
+
vars: refDef.vars,
|
|
261
|
+
filePath: refDef.path
|
|
262
|
+
});
|
|
263
|
+
// 8. Walk the content
|
|
264
|
+
content = await resolve(content, childCtx);
|
|
265
|
+
// 9. Run transformer
|
|
266
|
+
content = await runTransformer({
|
|
267
|
+
context: ctx.buildContext,
|
|
268
|
+
input: content,
|
|
269
|
+
refDef
|
|
270
|
+
});
|
|
271
|
+
// 10. Extract key
|
|
272
|
+
content = getKey({
|
|
273
|
+
input: content,
|
|
274
|
+
refDef
|
|
275
|
+
});
|
|
276
|
+
// 11. Tag all nodes with ~r for provenance
|
|
277
|
+
tagRefDeep(content, refDef.id);
|
|
278
|
+
// 12. Propagate ~ignoreBuildChecks
|
|
279
|
+
if (refDef.ignoreBuildChecks !== undefined) {
|
|
280
|
+
if (type.isObject(content)) {
|
|
281
|
+
content['~ignoreBuildChecks'] = refDef.ignoreBuildChecks;
|
|
282
|
+
} else if (type.isArray(content)) {
|
|
283
|
+
content.forEach((item)=>{
|
|
284
|
+
if (type.isObject(item)) {
|
|
285
|
+
item['~ignoreBuildChecks'] = refDef.ignoreBuildChecks;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return content;
|
|
291
|
+
} catch (error) {
|
|
292
|
+
if (error instanceof ConfigError) {
|
|
293
|
+
ctx.collectError(error);
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// Core walk function — single-pass async tree walker
|
|
300
|
+
async function resolve(node, ctx) {
|
|
301
|
+
// 1. Primitives pass through
|
|
302
|
+
if (!type.isObject(node) && !type.isArray(node)) return node;
|
|
303
|
+
// 2. Object with _ref
|
|
304
|
+
if (type.isObject(node) && !type.isUndefined(node._ref)) {
|
|
305
|
+
return resolveRef(node, ctx);
|
|
306
|
+
}
|
|
307
|
+
// 4. Object with _var — resolve, then re-walk the result so any
|
|
308
|
+
// _ref or _build.* operators inside the default value get processed.
|
|
309
|
+
if (type.isObject(node) && !type.isUndefined(node._var)) {
|
|
310
|
+
const varResult = resolveVar(node, ctx);
|
|
311
|
+
return resolve(varResult, ctx);
|
|
312
|
+
}
|
|
313
|
+
// 5. Array — walk children in-place
|
|
314
|
+
if (type.isArray(node)) {
|
|
315
|
+
for(let i = 0; i < node.length; i++){
|
|
316
|
+
node[i] = await resolve(node[i], ctx.child(String(i)));
|
|
317
|
+
}
|
|
318
|
+
return node;
|
|
319
|
+
}
|
|
320
|
+
// 6. Object — walk children in-place
|
|
321
|
+
const keys = Object.keys(node);
|
|
322
|
+
for (const key of keys){
|
|
323
|
+
if (ctx.shouldStop) {
|
|
324
|
+
const childPath = ctx.path ? `${ctx.path}.${key}` : key;
|
|
325
|
+
if (ctx.shouldStop(childPath, ctx.refId)) {
|
|
326
|
+
delete node[key];
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
node[key] = await resolve(node[key], ctx.child(key));
|
|
331
|
+
}
|
|
332
|
+
// Check if this is a _build.* operator
|
|
333
|
+
if (isBuildOperator(node)) {
|
|
334
|
+
const result = evaluateBuildOperator(node, ctx);
|
|
335
|
+
tagRefDeep(result, ctx.refId);
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
return node;
|
|
339
|
+
}
|
|
340
|
+
export { resolve, WalkContext, cloneForResolve, tagRefDeep };
|
package/dist/build/buildTypes.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Copyright 2020-
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
3
|
|
|
4
4
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
you may not use this file except in compliance with the License.
|
|
@@ -14,21 +14,36 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import basicTypes from '@lowdefy/blocks-basic/types';
|
|
16
16
|
import loaderTypes from '@lowdefy/blocks-loaders/types';
|
|
17
|
+
import { ConfigError, ConfigWarning } from '@lowdefy/errors';
|
|
18
|
+
import findSimilarString from '../utils/findSimilarString.js';
|
|
17
19
|
function buildTypeClass(context, { counter, definitions, store, typeClass, warnIfMissing = false }) {
|
|
18
20
|
const counts = counter.getCounts();
|
|
21
|
+
const definedTypes = Object.keys(definitions);
|
|
19
22
|
Object.keys(counts).forEach((typeName)=>{
|
|
20
23
|
if (!definitions[typeName]) {
|
|
24
|
+
const configKey = counter.getLocation(typeName);
|
|
25
|
+
let message = `${typeClass} type "${typeName}" was used but is not defined.`;
|
|
26
|
+
const suggestion = findSimilarString({
|
|
27
|
+
input: typeName,
|
|
28
|
+
candidates: definedTypes
|
|
29
|
+
});
|
|
30
|
+
if (suggestion) {
|
|
31
|
+
message += ` Did you mean "${suggestion}"?`;
|
|
32
|
+
}
|
|
21
33
|
if (warnIfMissing) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
context.handleWarning(new ConfigWarning(message, {
|
|
35
|
+
configKey,
|
|
36
|
+
checkSlug: 'types'
|
|
37
|
+
}));
|
|
26
38
|
return;
|
|
27
39
|
}
|
|
28
|
-
throw new
|
|
40
|
+
throw new ConfigError(message, {
|
|
41
|
+
configKey,
|
|
42
|
+
checkSlug: 'types'
|
|
43
|
+
});
|
|
29
44
|
}
|
|
30
45
|
store[typeName] = {
|
|
31
|
-
originalTypeName: definitions[typeName].originalTypeName,
|
|
46
|
+
originalTypeName: definitions[typeName].originalTypeName ?? typeName,
|
|
32
47
|
package: definitions[typeName].package,
|
|
33
48
|
version: definitions[typeName].version,
|
|
34
49
|
count: counts[typeName]
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
*/ import { type } from '@lowdefy/helpers';
|
|
16
|
+
function collectDynamicIdentifiers({ operators }) {
|
|
17
|
+
const dynamicIdentifiers = new Set();
|
|
18
|
+
Object.entries(operators).forEach(([operatorName, operatorFn])=>{
|
|
19
|
+
if (!type.isFunction(operatorFn)) return;
|
|
20
|
+
if (operatorFn.dynamic === true) {
|
|
21
|
+
dynamicIdentifiers.add(operatorName);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// Check for method-level dynamic in meta
|
|
25
|
+
if (type.isObject(operatorFn.meta)) {
|
|
26
|
+
Object.entries(operatorFn.meta).forEach(([methodName, methodMeta])=>{
|
|
27
|
+
if (type.isObject(methodMeta) && methodMeta.dynamic === true) {
|
|
28
|
+
dynamicIdentifiers.add(`${operatorName}.${methodName}`);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return dynamicIdentifiers;
|
|
34
|
+
}
|
|
35
|
+
export default collectDynamicIdentifiers;
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
*/ import { type } from '@lowdefy/helpers';
|
|
16
|
+
function collectTypeNames({ typesMap }) {
|
|
17
|
+
const typeNames = new Set();
|
|
18
|
+
if (!type.isObject(typesMap)) {
|
|
19
|
+
return typeNames;
|
|
20
|
+
}
|
|
21
|
+
[
|
|
22
|
+
'blocks',
|
|
23
|
+
'requests',
|
|
24
|
+
'connections',
|
|
25
|
+
'actions',
|
|
26
|
+
'controls'
|
|
27
|
+
].forEach((category)=>{
|
|
28
|
+
if (type.isObject(typesMap[category])) {
|
|
29
|
+
Object.keys(typesMap[category]).forEach((typeName)=>{
|
|
30
|
+
typeNames.add(typeName);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return typeNames;
|
|
35
|
+
}
|
|
36
|
+
export default collectTypeNames;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Copyright 2020-
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
3
|
|
|
4
4
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
you may not use this file except in compliance with the License.
|
|
@@ -12,11 +12,11 @@
|
|
|
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 jsMapParser from '
|
|
15
|
+
*/ import jsMapParser from '../buildJs/jsMapParser.js';
|
|
16
16
|
function buildJs({ components, context }) {
|
|
17
17
|
components.pages = components.pages.map((page)=>{
|
|
18
18
|
const pageRequests = [
|
|
19
|
-
...page.requests
|
|
19
|
+
...page.requests ?? []
|
|
20
20
|
];
|
|
21
21
|
delete page.requests;
|
|
22
22
|
const cleanPage = jsMapParser({
|
|
@@ -0,0 +1,87 @@
|
|
|
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, shouldSuppressBuildCheck } from '@lowdefy/errors';
|
|
17
|
+
import buildPage from '../buildPages/buildPage.js';
|
|
18
|
+
import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
|
|
19
|
+
import validateLinkReferences from '../buildPages/validateLinkReferences.js';
|
|
20
|
+
import validatePayloadReferences from '../buildPages/validatePayloadReferences.js';
|
|
21
|
+
import validateServerStateReferences from '../buildPages/validateServerStateReferences.js';
|
|
22
|
+
import validateStateReferences from '../buildPages/validateStateReferences.js';
|
|
23
|
+
function buildPages({ components, context }) {
|
|
24
|
+
const pages = type.isArray(components.pages) ? components.pages : [];
|
|
25
|
+
const checkDuplicatePageId = createCheckDuplicateId({
|
|
26
|
+
message: 'Duplicate pageId "{{ id }}".'
|
|
27
|
+
});
|
|
28
|
+
// Initialize linkActionRefs to collect Link action references across all pages
|
|
29
|
+
context.linkActionRefs = [];
|
|
30
|
+
// Track which pages failed to build so we skip them in validation
|
|
31
|
+
const failedPageIndices = new Set();
|
|
32
|
+
// Wrap each page build to collect errors instead of stopping on first error
|
|
33
|
+
pages.forEach((page, index)=>{
|
|
34
|
+
try {
|
|
35
|
+
const result = buildPage({
|
|
36
|
+
page,
|
|
37
|
+
index,
|
|
38
|
+
context,
|
|
39
|
+
checkDuplicatePageId
|
|
40
|
+
});
|
|
41
|
+
// buildPage returns { failed: true } when validation fails
|
|
42
|
+
if (result?.failed) {
|
|
43
|
+
failedPageIndices.add(index);
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
// Skip suppressed ConfigErrors (via ~ignoreBuildChecks)
|
|
47
|
+
if (error instanceof ConfigError && shouldSuppressBuildCheck(error, context.keyMap)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Collect error object if context.errors exists, otherwise throw (for backward compat with tests)
|
|
51
|
+
if (context?.errors) {
|
|
52
|
+
context.errors.push(error);
|
|
53
|
+
failedPageIndices.add(index);
|
|
54
|
+
} else {
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
// Validate that all Link actions reference existing pages
|
|
60
|
+
// Include all pages — a link to a broken page is valid; the page error is already reported
|
|
61
|
+
const pageIds = pages.map((page)=>page.pageId);
|
|
62
|
+
validateLinkReferences({
|
|
63
|
+
linkActionRefs: context.linkActionRefs,
|
|
64
|
+
pageIds,
|
|
65
|
+
context
|
|
66
|
+
});
|
|
67
|
+
// Validate that _state references use defined block IDs
|
|
68
|
+
// and _payload references use defined payload keys
|
|
69
|
+
// Skip pages that failed to build
|
|
70
|
+
pages.forEach((page, index)=>{
|
|
71
|
+
if (failedPageIndices.has(index)) return;
|
|
72
|
+
validateStateReferences({
|
|
73
|
+
page,
|
|
74
|
+
context
|
|
75
|
+
});
|
|
76
|
+
validatePayloadReferences({
|
|
77
|
+
page,
|
|
78
|
+
context
|
|
79
|
+
});
|
|
80
|
+
validateServerStateReferences({
|
|
81
|
+
page,
|
|
82
|
+
context
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
return components;
|
|
86
|
+
}
|
|
87
|
+
export default buildPages;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Copyright 2020-
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
3
|
|
|
4
4
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
you may not use this file except in compliance with the License.
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import buildAuth from '../buildAuth/buildAuth.js';
|
|
17
17
|
import buildPages from './buildPages.js';
|
|
18
18
|
import createContext from '../../createContext.js';
|
|
19
|
-
function buildTestPage({ pageConfig }) {
|
|
19
|
+
function buildTestPage({ pageConfig, connectionIds = [] }) {
|
|
20
20
|
const context = createContext({
|
|
21
21
|
customTypesMap: {},
|
|
22
22
|
directories: {},
|
|
@@ -24,10 +24,22 @@ function buildTestPage({ pageConfig }) {
|
|
|
24
24
|
debug: ()=>{},
|
|
25
25
|
log: ()=>{},
|
|
26
26
|
warn: ()=>{},
|
|
27
|
-
error: ()=>{}
|
|
27
|
+
error: ()=>{},
|
|
28
|
+
ui: {
|
|
29
|
+
warn: ()=>{},
|
|
30
|
+
error: ()=>{}
|
|
31
|
+
}
|
|
28
32
|
},
|
|
29
33
|
stage: 'test'
|
|
30
34
|
});
|
|
35
|
+
// Add any connectionIds from test config to allow validation to pass
|
|
36
|
+
connectionIds.forEach((id)=>context.connectionIds.add(id));
|
|
37
|
+
// Also extract connectionIds from requests in the pageConfig
|
|
38
|
+
(pageConfig.requests || []).forEach((request)=>{
|
|
39
|
+
if (request.connectionId) {
|
|
40
|
+
context.connectionIds.add(request.connectionId);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
31
43
|
const components = {
|
|
32
44
|
pages: [
|
|
33
45
|
pageConfig
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Copyright 2020-
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
3
|
|
|
4
4
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
you may not use this file except in compliance with the License.
|
|
@@ -12,9 +12,17 @@
|
|
|
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 { serializer } from '@lowdefy/helpers';
|
|
15
|
+
*/ import { serializer, type } from '@lowdefy/helpers';
|
|
16
|
+
import { ConfigError } from '@lowdefy/errors';
|
|
16
17
|
async function writeRequestsOnPage({ page, context }) {
|
|
17
|
-
|
|
18
|
+
const requests = page.requests ?? [];
|
|
19
|
+
if (!type.isArray(requests)) {
|
|
20
|
+
throw new ConfigError('Page requests must be an array.', {
|
|
21
|
+
received: requests,
|
|
22
|
+
configKey: page['~k']
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return Promise.all(requests.map(async (request)=>{
|
|
18
26
|
await context.writeBuildArtifact(`pages/${page.pageId}/requests/${request.requestId}.json`, serializer.serializeToString(request ?? {}));
|
|
19
27
|
delete request.properties;
|
|
20
28
|
delete request.type;
|