@lowdefy/build 4.5.2 → 4.6.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 +20 -3
- package/dist/build/buildRefs/createRefReviver.js +28 -0
- package/dist/build/buildRefs/evaluateBuildOperators.js +26 -7
- package/dist/build/buildRefs/evaluateStaticOperators.js +56 -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/getRefsFromFile.js +9 -4
- 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 +100 -6
- package/dist/build/buildRefs/populateRefs.js +75 -13
- package/dist/build/buildRefs/recursiveBuild.js +66 -18
- package/dist/build/buildRefs/runRefResolver.js +11 -3
- package/dist/build/buildRefs/runTransformer.js +7 -3
- 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 +252 -0
- package/dist/build/jit/buildShallowPages.js +90 -0
- package/dist/build/jit/createPageRegistry.js +80 -0
- package/dist/build/jit/detectMissingPluginPackages.js +62 -0
- package/dist/build/jit/getRefPositions.js +38 -0
- package/dist/build/jit/isPageContentPath.js +24 -0
- package/dist/build/jit/pageContentKeys.js +26 -0
- package/dist/build/jit/shallowBuild.js +245 -0
- package/dist/build/jit/stripPageContent.js +23 -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 +188 -127
- package/dist/indexDev.js +18 -0
- package/dist/lowdefySchema.js +589 -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 +13 -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/utils/formatErrorMessage.js +0 -56
|
@@ -0,0 +1,79 @@
|
|
|
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 { ConfigWarning } from '@lowdefy/errors';
|
|
16
|
+
import { type } from '@lowdefy/helpers';
|
|
17
|
+
import extractOperatorKey from '../../utils/extractOperatorKey.js';
|
|
18
|
+
import traverseConfig from '../../utils/traverseConfig.js';
|
|
19
|
+
function validateStateReferences({ page, context }) {
|
|
20
|
+
// Single traversal collects blockIds, _state references, and SetState keys
|
|
21
|
+
// More memory-efficient than stringify+regex for massive pages
|
|
22
|
+
const blockIds = new Set();
|
|
23
|
+
const setStateKeys = new Set();
|
|
24
|
+
const stateRefs = new Map(); // topLevelKey -> configKey (first occurrence)
|
|
25
|
+
// Collect ~k values inside request.properties subtrees so we can skip them
|
|
26
|
+
// when collecting _state refs — those are already handled by validateServerStateReferences
|
|
27
|
+
const requestPropertyKeys = new Set();
|
|
28
|
+
(page.requests ?? []).forEach((request)=>{
|
|
29
|
+
if (request.properties) {
|
|
30
|
+
traverseConfig({
|
|
31
|
+
config: request.properties,
|
|
32
|
+
visitor: (obj)=>{
|
|
33
|
+
if (obj['~k']) requestPropertyKeys.add(obj['~k']);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
traverseConfig({
|
|
39
|
+
config: page,
|
|
40
|
+
visitor: (obj)=>{
|
|
41
|
+
// Collect blockId if present, including the top-level key for dot-notation ids
|
|
42
|
+
if (type.isString(obj.blockId)) {
|
|
43
|
+
blockIds.add(obj.blockId);
|
|
44
|
+
const topLevel = obj.blockId.split(/[.\[]/)[0];
|
|
45
|
+
if (topLevel !== obj.blockId) {
|
|
46
|
+
blockIds.add(topLevel);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Collect SetState action params to track state keys being initialized
|
|
50
|
+
if (obj.type === 'SetState' && obj.params) {
|
|
51
|
+
Object.keys(obj.params).forEach((key)=>{
|
|
52
|
+
const topLevelKey = key.split(/[.\[]/)[0];
|
|
53
|
+
setStateKeys.add(topLevelKey);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// Collect _state reference if present (skip request.properties — handled separately)
|
|
57
|
+
if (obj._state !== undefined && !requestPropertyKeys.has(obj['~k'])) {
|
|
58
|
+
const topLevelKey = extractOperatorKey({
|
|
59
|
+
operatorValue: obj._state
|
|
60
|
+
});
|
|
61
|
+
if (topLevelKey && !stateRefs.has(topLevelKey)) {
|
|
62
|
+
stateRefs.set(topLevelKey, obj['~k']);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
// Filter to only undefined references and warn
|
|
68
|
+
stateRefs.forEach((configKey, topLevelKey)=>{
|
|
69
|
+
// Skip if state key is from an input block or SetState action
|
|
70
|
+
if (blockIds.has(topLevelKey) || setStateKeys.has(topLevelKey)) return;
|
|
71
|
+
const message = `_state references "${topLevelKey}" on page "${page.pageId}", ` + `but no input block with id "${topLevelKey}" exists on this page. ` + `State keys are created from input block ids. ` + `Check for typos, add an input block with this id, or initialize the state with SetState.`;
|
|
72
|
+
context.handleWarning(new ConfigWarning(message, {
|
|
73
|
+
configKey,
|
|
74
|
+
prodError: true,
|
|
75
|
+
checkSlug: 'state-refs'
|
|
76
|
+
}));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
export default validateStateReferences;
|
|
@@ -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.
|
|
@@ -15,14 +15,31 @@
|
|
|
15
15
|
*/ import recursiveBuild from './recursiveBuild.js';
|
|
16
16
|
import makeRefDefinition from './makeRefDefinition.js';
|
|
17
17
|
import evaluateBuildOperators from './evaluateBuildOperators.js';
|
|
18
|
-
|
|
18
|
+
import evaluateStaticOperators from './evaluateStaticOperators.js';
|
|
19
|
+
import collectTypeNames from '../collectTypeNames.js';
|
|
20
|
+
async function buildRefs({ context, shallowOptions }) {
|
|
19
21
|
const refDef = makeRefDefinition('lowdefy.yaml', null, context.refMap);
|
|
20
22
|
let components = await recursiveBuild({
|
|
21
23
|
context,
|
|
22
24
|
refDef,
|
|
23
|
-
count: 0
|
|
25
|
+
count: 0,
|
|
26
|
+
shallowOptions
|
|
27
|
+
});
|
|
28
|
+
// First: evaluate _build.* operators (e.g., _build.env)
|
|
29
|
+
// Pass typeNames so page objects act as type boundaries, preventing ~dyn markers
|
|
30
|
+
// from ~shallow content (blocks, events) from bubbling up and blocking evaluation
|
|
31
|
+
// of _build.array at the pages level.
|
|
32
|
+
const typeNames = collectTypeNames({
|
|
33
|
+
typesMap: context.typesMap
|
|
24
34
|
});
|
|
25
35
|
components = await evaluateBuildOperators({
|
|
36
|
+
context,
|
|
37
|
+
input: components,
|
|
38
|
+
refDef,
|
|
39
|
+
typeNames
|
|
40
|
+
});
|
|
41
|
+
// Second: evaluate static operators (_sum, _if, etc.) that don't depend on runtime data
|
|
42
|
+
components = evaluateStaticOperators({
|
|
26
43
|
context,
|
|
27
44
|
input: components,
|
|
28
45
|
refDef
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
import setNonEnumerableProperty from '../../utils/setNonEnumerableProperty.js';
|
|
17
|
+
// Returns a serializer.copy reviver that sets ~r on all objects
|
|
18
|
+
// that don't already have it, preserving original file references.
|
|
19
|
+
function createRefReviver(refId) {
|
|
20
|
+
return (_, value)=>{
|
|
21
|
+
if (!type.isObject(value) && !type.isArray(value)) return value;
|
|
22
|
+
if (value['~r'] === undefined) {
|
|
23
|
+
setNonEnumerableProperty(value, '~r', refId);
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export default createRefReviver;
|
|
@@ -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,20 +14,39 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { BuildParser } from '@lowdefy/operators';
|
|
16
16
|
import operators from '@lowdefy/operators-js/operators/build';
|
|
17
|
-
|
|
17
|
+
import collectDynamicIdentifiers from '../collectDynamicIdentifiers.js';
|
|
18
|
+
import validateOperatorsDynamic from '../validateOperatorsDynamic.js';
|
|
19
|
+
import collectExceptions from '../../utils/collectExceptions.js';
|
|
20
|
+
// Validate and collect dynamic identifiers once at module load
|
|
21
|
+
validateOperatorsDynamic({
|
|
22
|
+
operators
|
|
23
|
+
});
|
|
24
|
+
const dynamicIdentifiers = collectDynamicIdentifiers({
|
|
25
|
+
operators
|
|
26
|
+
});
|
|
27
|
+
function evaluateBuildOperators({ context, input, refDef, typeNames }) {
|
|
18
28
|
const operatorsParser = new BuildParser({
|
|
19
29
|
env: process.env,
|
|
20
|
-
operators
|
|
30
|
+
operators,
|
|
31
|
+
dynamicIdentifiers,
|
|
32
|
+
...typeNames ? {
|
|
33
|
+
typeNames
|
|
34
|
+
} : {}
|
|
21
35
|
});
|
|
22
36
|
const { output, errors } = operatorsParser.parse({
|
|
23
37
|
input,
|
|
24
|
-
location: refDef.path ?? refDef.resolver,
|
|
25
38
|
operatorPrefix: '_build.'
|
|
26
39
|
});
|
|
27
40
|
if (errors.length > 0) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
41
|
+
errors.forEach((error)=>{
|
|
42
|
+
// Resolve source file path for error location.
|
|
43
|
+
// Two call sites: (1) recursiveBuild per-file where refDef.path is correct
|
|
44
|
+
// but ~r isn't set yet (createRefReviver runs after), and (2) buildRefs
|
|
45
|
+
// top-level where refDef is root lowdefy.yaml but ~r identifies the real
|
|
46
|
+
// source file via refMap. The fallback to refDef.path handles case (1).
|
|
47
|
+
error.filePath = error.refId ? context.refMap[error.refId]?.path : refDef.path;
|
|
48
|
+
collectExceptions(context, error);
|
|
49
|
+
});
|
|
31
50
|
}
|
|
32
51
|
return output;
|
|
33
52
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import { BuildParser } from '@lowdefy/operators';
|
|
16
|
+
import operators from '@lowdefy/operators-js/operators/build';
|
|
17
|
+
import collectDynamicIdentifiers from '../collectDynamicIdentifiers.js';
|
|
18
|
+
import collectTypeNames from '../collectTypeNames.js';
|
|
19
|
+
import validateOperatorsDynamic from '../validateOperatorsDynamic.js';
|
|
20
|
+
import collectExceptions from '../../utils/collectExceptions.js';
|
|
21
|
+
// Validate and collect dynamic identifiers once at module load
|
|
22
|
+
validateOperatorsDynamic({
|
|
23
|
+
operators
|
|
24
|
+
});
|
|
25
|
+
const dynamicIdentifiers = collectDynamicIdentifiers({
|
|
26
|
+
operators
|
|
27
|
+
});
|
|
28
|
+
function evaluateStaticOperators({ context, input, refDef }) {
|
|
29
|
+
// Collect type names from context.typesMap for type boundary detection
|
|
30
|
+
const typeNames = collectTypeNames({
|
|
31
|
+
typesMap: context.typesMap
|
|
32
|
+
});
|
|
33
|
+
const operatorsParser = new BuildParser({
|
|
34
|
+
env: process.env,
|
|
35
|
+
operators,
|
|
36
|
+
dynamicIdentifiers,
|
|
37
|
+
typeNames
|
|
38
|
+
});
|
|
39
|
+
const { output, errors } = operatorsParser.parse({
|
|
40
|
+
input,
|
|
41
|
+
operatorPrefix: '_'
|
|
42
|
+
});
|
|
43
|
+
if (errors.length > 0) {
|
|
44
|
+
errors.forEach((error)=>{
|
|
45
|
+
// Resolve source file path for error location.
|
|
46
|
+
// Only called from buildRefs top-level where refDef is root lowdefy.yaml,
|
|
47
|
+
// but ~r on each operator object identifies the real source file via refMap.
|
|
48
|
+
// Falls back to refDef.path if ~r is missing (shouldn't happen at this stage
|
|
49
|
+
// since all objects have ~r after recursiveBuild completes).
|
|
50
|
+
error.filePath = error.refId ? context.refMap[error.refId]?.path : refDef.path;
|
|
51
|
+
collectExceptions(context, error);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return output;
|
|
55
|
+
}
|
|
56
|
+
export default evaluateStaticOperators;
|
|
@@ -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,16 +12,35 @@
|
|
|
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
|
|
15
|
+
*/ import path from 'path';
|
|
16
|
+
import { type } from '@lowdefy/helpers';
|
|
17
|
+
import { ConfigError } from '@lowdefy/errors';
|
|
16
18
|
async function getConfigFile({ context, refDef, referencedFrom }) {
|
|
17
19
|
if (!type.isString(refDef.path)) {
|
|
18
|
-
throw new
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
throw new ConfigError('Invalid _ref definition.', {
|
|
21
|
+
received: {
|
|
22
|
+
_ref: refDef.original
|
|
23
|
+
},
|
|
24
|
+
filePath: referencedFrom ?? null,
|
|
25
|
+
lineNumber: referencedFrom ? refDef.lineNumber : null
|
|
26
|
+
});
|
|
21
27
|
}
|
|
22
28
|
const content = await context.readConfigFile(refDef.path);
|
|
23
29
|
if (content === null) {
|
|
24
|
-
|
|
30
|
+
const absolutePath = path.resolve(context.directories.config, refDef.path);
|
|
31
|
+
let message = `Referenced file does not exist: "${refDef.path}". Resolved to: ${absolutePath}`;
|
|
32
|
+
// Help with common mistakes
|
|
33
|
+
if (refDef.path.startsWith('../')) {
|
|
34
|
+
const suggestedPath = refDef.path.replace(/^(\.\.\/)+/, '');
|
|
35
|
+
message += ` Tip: Paths in _ref are resolved from config root. Did you mean "${suggestedPath}"?`;
|
|
36
|
+
} else if (refDef.path.startsWith('./')) {
|
|
37
|
+
const suggestedPath = refDef.path.substring(2);
|
|
38
|
+
message += ` Tip: Remove "./" prefix - paths are resolved from config root. Did you mean "${suggestedPath}"?`;
|
|
39
|
+
}
|
|
40
|
+
throw new ConfigError(message, {
|
|
41
|
+
filePath: referencedFrom ?? null,
|
|
42
|
+
lineNumber: referencedFrom ? refDef.lineNumber : null
|
|
43
|
+
});
|
|
25
44
|
}
|
|
26
45
|
return content;
|
|
27
46
|
}
|
|
@@ -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,14 +12,16 @@
|
|
|
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';
|
|
15
|
+
*/ import { serializer, type } from '@lowdefy/helpers';
|
|
16
16
|
import makeRefDefinition from './makeRefDefinition.js';
|
|
17
17
|
function getRefsFromFile(fileContent, parentRefDefId, refMap) {
|
|
18
18
|
const foundRefs = [];
|
|
19
19
|
const reviver = (key, value)=>{
|
|
20
20
|
if (type.isObject(value)) {
|
|
21
21
|
if (!type.isUndefined(value._ref)) {
|
|
22
|
-
|
|
22
|
+
// Capture line number from the object containing the _ref
|
|
23
|
+
const lineNumber = value['~l'];
|
|
24
|
+
const def = makeRefDefinition(value._ref, parentRefDefId, refMap, lineNumber);
|
|
23
25
|
foundRefs.push(def);
|
|
24
26
|
return {
|
|
25
27
|
_ref: def
|
|
@@ -28,7 +30,10 @@ function getRefsFromFile(fileContent, parentRefDefId, refMap) {
|
|
|
28
30
|
}
|
|
29
31
|
return value;
|
|
30
32
|
};
|
|
31
|
-
|
|
33
|
+
// Use serializer.copy to preserve non-enumerable properties like ~l
|
|
34
|
+
const fileContentBuiltRefs = serializer.copy(fileContent, {
|
|
35
|
+
reviver
|
|
36
|
+
});
|
|
32
37
|
return {
|
|
33
38
|
foundRefs,
|
|
34
39
|
fileContentBuiltRefs
|
|
@@ -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,12 +14,26 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import path from 'path';
|
|
16
16
|
import { pathToFileURL } from 'url';
|
|
17
|
+
import { ConfigError } from '@lowdefy/errors';
|
|
18
|
+
// Create a native import() that survives webpack bundling. When this module is
|
|
19
|
+
// bundled by Next.js webpack for server-dev API routes, webpack transforms
|
|
20
|
+
// import() into __webpack_require__() which can't handle file:// URLs for
|
|
21
|
+
// loading user-provided resolver and transformer JS files from the config
|
|
22
|
+
// directory. The Function constructor creates the import call at runtime,
|
|
23
|
+
// bypassing webpack's static analysis.
|
|
24
|
+
const nativeImport = new Function('specifier', 'return import(specifier)');
|
|
17
25
|
async function getUserJavascriptFunction({ context, filePath }) {
|
|
18
26
|
try {
|
|
19
|
-
|
|
27
|
+
const fileUrl = pathToFileURL(path.join(context.directories.config, filePath));
|
|
28
|
+
// Bust Node.js module cache so edits to resolver/transformer JS files are
|
|
29
|
+
// picked up during dev rebuilds. Each import gets a unique URL.
|
|
30
|
+
fileUrl.searchParams.set('t', Date.now());
|
|
31
|
+
return (await nativeImport(fileUrl.href)).default;
|
|
20
32
|
} catch (error) {
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
throw new ConfigError(`Error importing ${filePath}.`, {
|
|
34
|
+
cause: error,
|
|
35
|
+
filePath
|
|
36
|
+
});
|
|
23
37
|
}
|
|
24
38
|
}
|
|
25
39
|
export default getUserJavascriptFunction;
|
|
@@ -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.
|
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
*/ import { get } from '@lowdefy/helpers';
|
|
16
16
|
import getRefPath from './getRefPath.js';
|
|
17
17
|
import makeId from '../../utils/makeId.js';
|
|
18
|
-
function makeRefDefinition(refDefinition, parent, refMap) {
|
|
19
|
-
const id = makeId();
|
|
18
|
+
function makeRefDefinition(refDefinition, parent, refMap, lineNumber) {
|
|
19
|
+
const id = makeId.next();
|
|
20
20
|
const refDef = {
|
|
21
|
-
parent
|
|
21
|
+
parent,
|
|
22
|
+
lineNumber
|
|
22
23
|
};
|
|
23
24
|
refMap[id] = refDef;
|
|
25
|
+
const ignoreBuildChecks = get(refDefinition, '~ignoreBuildChecks');
|
|
24
26
|
return {
|
|
25
27
|
...refDef,
|
|
26
28
|
id,
|
|
@@ -31,7 +33,10 @@ function makeRefDefinition(refDefinition, parent, refMap) {
|
|
|
31
33
|
transformer: get(refDefinition, 'transformer'),
|
|
32
34
|
vars: get(refDefinition, 'vars', {
|
|
33
35
|
default: {}
|
|
34
|
-
})
|
|
36
|
+
}),
|
|
37
|
+
...ignoreBuildChecks !== undefined && {
|
|
38
|
+
ignoreBuildChecks
|
|
39
|
+
}
|
|
35
40
|
};
|
|
36
41
|
}
|
|
37
42
|
export default makeRefDefinition;
|
|
@@ -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,24 +12,118 @@
|
|
|
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
|
-
*/ /* eslint-disable no-param-reassign */ import {
|
|
15
|
+
*/ /* eslint-disable no-param-reassign */ import { ConfigError } from '@lowdefy/errors';
|
|
16
|
+
import { type } from '@lowdefy/helpers';
|
|
16
17
|
import { getFileExtension, getFileSubExtension } from '@lowdefy/node-utils';
|
|
17
18
|
import JSON5 from 'json5';
|
|
18
|
-
import YAML from 'yaml';
|
|
19
|
+
import YAML, { isMap, isSeq, isPair, isScalar } from 'yaml';
|
|
19
20
|
import parseNunjucks from './parseNunjucks.js';
|
|
21
|
+
import setNonEnumerableProperty from '../../utils/setNonEnumerableProperty.js';
|
|
22
|
+
function getLineNumber(content, offset) {
|
|
23
|
+
if (offset == null || offset < 0) return null;
|
|
24
|
+
return content.substring(0, offset).split('\n').length;
|
|
25
|
+
}
|
|
26
|
+
function addLineNumbers(node, content, result) {
|
|
27
|
+
if (isMap(node)) {
|
|
28
|
+
const obj = result || {};
|
|
29
|
+
if (node.range) {
|
|
30
|
+
setNonEnumerableProperty(obj, '~l', getLineNumber(content, node.range[0]));
|
|
31
|
+
}
|
|
32
|
+
for (const pair of node.items){
|
|
33
|
+
if (isPair(pair) && isScalar(pair.key)) {
|
|
34
|
+
const key = pair.key.value;
|
|
35
|
+
const value = pair.value;
|
|
36
|
+
// Use key's line number for the value's ~l (more useful for error messages)
|
|
37
|
+
const keyLineNumber = pair.key.range ? getLineNumber(content, pair.key.range[0]) : null;
|
|
38
|
+
if (isMap(value)) {
|
|
39
|
+
const mapResult = addLineNumbers(value, content, {});
|
|
40
|
+
// Override ~l with key's line number if available
|
|
41
|
+
if (keyLineNumber) {
|
|
42
|
+
setNonEnumerableProperty(mapResult, '~l', keyLineNumber);
|
|
43
|
+
}
|
|
44
|
+
obj[key] = mapResult;
|
|
45
|
+
} else if (isSeq(value)) {
|
|
46
|
+
const arrResult = addLineNumbers(value, content, []);
|
|
47
|
+
// Override ~l with key's line number if available
|
|
48
|
+
if (keyLineNumber) {
|
|
49
|
+
setNonEnumerableProperty(arrResult, '~l', keyLineNumber);
|
|
50
|
+
}
|
|
51
|
+
obj[key] = arrResult;
|
|
52
|
+
} else if (isScalar(value)) {
|
|
53
|
+
obj[key] = value.value;
|
|
54
|
+
} else {
|
|
55
|
+
obj[key] = value?.toJSON?.() ?? value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return obj;
|
|
60
|
+
}
|
|
61
|
+
if (isSeq(node)) {
|
|
62
|
+
const arr = result || [];
|
|
63
|
+
if (node.range) {
|
|
64
|
+
setNonEnumerableProperty(arr, '~l', getLineNumber(content, node.range[0]));
|
|
65
|
+
}
|
|
66
|
+
for (const item of node.items){
|
|
67
|
+
if (isMap(item)) {
|
|
68
|
+
arr.push(addLineNumbers(item, content, {}));
|
|
69
|
+
} else if (isSeq(item)) {
|
|
70
|
+
arr.push(addLineNumbers(item, content, []));
|
|
71
|
+
} else if (isScalar(item)) {
|
|
72
|
+
arr.push(item.value);
|
|
73
|
+
} else {
|
|
74
|
+
arr.push(item?.toJSON?.() ?? item);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return arr;
|
|
78
|
+
}
|
|
79
|
+
if (isScalar(node)) {
|
|
80
|
+
return node.value;
|
|
81
|
+
}
|
|
82
|
+
return node?.toJSON?.() ?? node;
|
|
83
|
+
}
|
|
84
|
+
function parseYamlWithLineNumbers(content) {
|
|
85
|
+
const doc = YAML.parseDocument(content);
|
|
86
|
+
if (doc.errors && doc.errors.length > 0) {
|
|
87
|
+
throw new Error(doc.errors[0].message);
|
|
88
|
+
}
|
|
89
|
+
return addLineNumbers(doc.contents, content);
|
|
90
|
+
}
|
|
20
91
|
function parseRefContent({ content, refDef }) {
|
|
21
92
|
const { path, vars } = refDef;
|
|
22
93
|
if (type.isString(path)) {
|
|
23
94
|
let ext = getFileExtension(path);
|
|
24
95
|
if (ext === 'njk') {
|
|
25
|
-
|
|
96
|
+
try {
|
|
97
|
+
content = parseNunjucks(content, vars);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
throw new ConfigError(`Nunjucks error in "${path}".`, {
|
|
100
|
+
cause: error,
|
|
101
|
+
filePath: path
|
|
102
|
+
});
|
|
103
|
+
}
|
|
26
104
|
ext = getFileSubExtension(path);
|
|
27
105
|
}
|
|
28
106
|
if (ext === 'yaml' || ext === 'yml') {
|
|
29
|
-
|
|
107
|
+
try {
|
|
108
|
+
content = parseYamlWithLineNumbers(content);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
const lineMatch = error.message.match(/at line (\d+)/);
|
|
111
|
+
throw new ConfigError(`YAML parse error in "${path}".`, {
|
|
112
|
+
cause: error,
|
|
113
|
+
filePath: path,
|
|
114
|
+
lineNumber: lineMatch ? lineMatch[1] : null
|
|
115
|
+
});
|
|
116
|
+
}
|
|
30
117
|
}
|
|
31
118
|
if (ext === 'json') {
|
|
32
|
-
|
|
119
|
+
try {
|
|
120
|
+
content = JSON5.parse(content);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw new ConfigError(`JSON parse error in "${path}".`, {
|
|
123
|
+
cause: error,
|
|
124
|
+
filePath: path
|
|
125
|
+
});
|
|
126
|
+
}
|
|
33
127
|
}
|
|
34
128
|
}
|
|
35
129
|
return content;
|