@lowdefy/build 0.0.0-experimental-20260114142524 → 0.0.0-experimental-20260122074446
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/addKeys.js +31 -18
- package/dist/build/buildApi/validateStepReferences.js +3 -4
- package/dist/build/buildAuth/buildAuth.js +2 -1
- package/dist/build/buildAuth/buildAuthPlugins.js +13 -13
- package/dist/build/buildAuth/validateAuthConfig.js +40 -8
- package/dist/build/buildAuth/validateMutualExclusivity.js +7 -7
- package/dist/build/buildConnections.js +20 -39
- package/dist/build/buildMenu.js +9 -14
- package/dist/build/buildPages/buildBlock/buildEvents.js +13 -13
- package/dist/build/buildPages/buildBlock/buildRequests.js +15 -15
- package/dist/build/buildPages/buildBlock/validateBlock.js +13 -13
- package/dist/build/buildPages/buildPage.js +5 -5
- package/dist/build/buildPages/validateLinkReferences.js +8 -9
- package/dist/build/buildPages/validatePayloadReferences.js +3 -4
- package/dist/build/buildPages/validateRequestReferences.js +7 -8
- package/dist/build/buildPages/validateStateReferences.js +3 -4
- package/dist/build/buildRefs/buildRefs.js +8 -0
- package/dist/build/buildRefs/evaluateBuildOperators.js +11 -1
- package/dist/build/buildRefs/evaluateStaticOperators.js +54 -0
- package/dist/build/buildRefs/getConfigFile.js +18 -13
- package/dist/build/buildRefs/getRefContent.js +15 -5
- package/dist/build/buildRefs/makeRefDefinition.js +1 -1
- package/dist/build/buildRefs/parseRefContent.js +32 -2
- package/dist/build/buildRefs/recursiveBuild.js +9 -7
- package/dist/build/buildRefs/runRefResolver.js +13 -2
- package/dist/build/buildTypes.js +9 -0
- package/dist/build/collectDynamicIdentifiers.js +35 -0
- package/dist/{utils/formatConfigError.js → build/collectTypeNames.js} +20 -8
- package/dist/build/formatBuildError.js +1 -1
- package/dist/build/testSchema.js +45 -6
- package/dist/build/validateOperatorsDynamic.js +28 -0
- package/dist/build/writeRequests.js +3 -3
- package/dist/createContext.js +42 -1
- package/dist/defaultTypesMap.js +447 -447
- package/dist/index.js +43 -4
- package/dist/lowdefySchema.js +60 -0
- package/dist/test-utils/parseTestYaml.js +110 -0
- package/dist/test-utils/runBuild.js +270 -0
- package/dist/test-utils/runBuildForSnapshots.js +698 -0
- package/dist/{test → test-utils}/testContext.js +15 -1
- package/dist/utils/collectConfigError.js +6 -6
- package/dist/utils/countOperators.js +5 -3
- package/dist/utils/createCheckDuplicateId.js +1 -1
- package/dist/utils/findConfigKey.js +37 -0
- package/dist/utils/makeId.js +12 -7
- package/dist/utils/tryBuildStep.js +12 -5
- package/package.json +39 -39
- package/dist/utils/formatConfigMessage.js +0 -33
- package/dist/utils/formatConfigWarning.js +0 -24
- package/dist/utils/formatErrorMessage.js +0 -56
- /package/dist/{test → test-utils}/buildRefs/testBuildRefsAsyncFunction.js +0 -0
- /package/dist/{test → test-utils}/buildRefs/testBuildRefsErrorResolver.js +0 -0
- /package/dist/{test → test-utils}/buildRefs/testBuildRefsNullResolver.js +0 -0
- /package/dist/{test → test-utils}/buildRefs/testBuildRefsParsingResolver.js +0 -0
- /package/dist/{test → test-utils}/buildRefs/testBuildRefsResolver.js +0 -0
- /package/dist/{test → test-utils}/buildRefs/testBuildRefsTransform.js +0 -0
- /package/dist/{test → test-utils}/buildRefs/testBuildRefsTransformIdentity.js +0 -0
|
@@ -13,26 +13,26 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { type } from '@lowdefy/helpers';
|
|
16
|
+
import { ConfigError } from '@lowdefy/node-utils';
|
|
16
17
|
import buildBlock from './buildBlock/buildBlock.js';
|
|
17
18
|
import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
|
|
18
19
|
import createCounter from '../../utils/createCounter.js';
|
|
19
|
-
import formatConfigError from '../../utils/formatConfigError.js';
|
|
20
20
|
import validateRequestReferences from './validateRequestReferences.js';
|
|
21
21
|
function buildPage({ page, index, context, checkDuplicatePageId }) {
|
|
22
22
|
const configKey = page['~k'];
|
|
23
23
|
if (type.isUndefined(page.id)) {
|
|
24
|
-
throw new
|
|
24
|
+
throw new ConfigError({
|
|
25
25
|
message: `Page id missing at page ${index}.`,
|
|
26
26
|
configKey,
|
|
27
27
|
context
|
|
28
|
-
})
|
|
28
|
+
});
|
|
29
29
|
}
|
|
30
30
|
if (!type.isString(page.id)) {
|
|
31
|
-
throw new
|
|
31
|
+
throw new ConfigError({
|
|
32
32
|
message: `Page id is not a string at page ${index}. Received ${JSON.stringify(page.id)}.`,
|
|
33
33
|
configKey,
|
|
34
34
|
context
|
|
35
|
-
})
|
|
35
|
+
});
|
|
36
36
|
}
|
|
37
37
|
checkDuplicatePageId({
|
|
38
38
|
id: page.id,
|
|
@@ -12,21 +12,20 @@
|
|
|
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
|
-
*/
|
|
16
|
-
function validateLinkReferences({ linkActionRefs, pageIds, context }) {
|
|
15
|
+
*/ function validateLinkReferences({ linkActionRefs, pageIds, context }) {
|
|
17
16
|
const pageIdSet = new Set(pageIds);
|
|
18
17
|
linkActionRefs.forEach(({ pageId, action, sourcePageId })=>{
|
|
18
|
+
// Only skip validation if skip is explicitly true
|
|
19
|
+
// Pages must exist in app even if Link is conditional
|
|
20
|
+
if (action.skip === true) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
19
23
|
if (!pageIdSet.has(pageId)) {
|
|
20
|
-
|
|
24
|
+
context.logger.configWarning({
|
|
21
25
|
message: `Page "${pageId}" not found. Link on page "${sourcePageId}" references non-existent page.`,
|
|
22
26
|
configKey: action['~k'],
|
|
23
|
-
|
|
27
|
+
prodError: true
|
|
24
28
|
});
|
|
25
|
-
if (context.stage === 'dev' || context.stage === 'test') {
|
|
26
|
-
context.logger.warn(errorMessage);
|
|
27
|
-
} else {
|
|
28
|
-
throw new Error(errorMessage);
|
|
29
|
-
}
|
|
30
29
|
}
|
|
31
30
|
});
|
|
32
31
|
}
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import extractOperatorKey from '../../utils/extractOperatorKey.js';
|
|
16
|
-
import formatConfigWarning from '../../utils/formatConfigWarning.js';
|
|
17
16
|
import traverseConfig from '../../utils/traverseConfig.js';
|
|
18
17
|
function validatePayloadReferences({ page, context }) {
|
|
19
18
|
const requests = page.requests || [];
|
|
@@ -41,11 +40,11 @@ function validatePayloadReferences({ page, context }) {
|
|
|
41
40
|
payloadRefs.forEach((configKey, topLevelKey)=>{
|
|
42
41
|
if (payloadKeys.has(topLevelKey)) return;
|
|
43
42
|
const message = `_payload references "${topLevelKey}" in request "${request.requestId}" on page "${page.pageId}", ` + `but no key "${topLevelKey}" exists in the request payload definition. ` + `Payload keys are defined in the request's "payload" property. ` + `Check for typos or add the key to the payload definition.`;
|
|
44
|
-
context.logger.
|
|
43
|
+
context.logger.configWarning({
|
|
45
44
|
message,
|
|
46
45
|
configKey,
|
|
47
|
-
|
|
48
|
-
})
|
|
46
|
+
prodError: true
|
|
47
|
+
});
|
|
49
48
|
});
|
|
50
49
|
});
|
|
51
50
|
}
|
|
@@ -12,21 +12,20 @@
|
|
|
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 { type } from '@lowdefy/helpers';
|
|
16
16
|
function validateRequestReferences({ requestActionRefs, requests, pageId, context }) {
|
|
17
17
|
const requestIds = new Set(requests.map((req)=>req.requestId));
|
|
18
18
|
requestActionRefs.forEach(({ requestId, action })=>{
|
|
19
|
+
// Skip validation if action has skip condition (true or operator object)
|
|
20
|
+
if (action.skip === true || type.isObject(action.skip)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
19
23
|
if (!requestIds.has(requestId)) {
|
|
20
|
-
|
|
24
|
+
context.logger.configWarning({
|
|
21
25
|
message: `Request "${requestId}" not defined on page "${pageId}".`,
|
|
22
26
|
configKey: action['~k'],
|
|
23
|
-
|
|
27
|
+
prodError: true
|
|
24
28
|
});
|
|
25
|
-
if (context.stage === 'dev' || context.stage === 'test') {
|
|
26
|
-
context.logger.warn(errorMessage);
|
|
27
|
-
} else {
|
|
28
|
-
throw new Error(errorMessage);
|
|
29
|
-
}
|
|
30
29
|
}
|
|
31
30
|
});
|
|
32
31
|
}
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import extractOperatorKey from '../../utils/extractOperatorKey.js';
|
|
16
|
-
import formatConfigWarning from '../../utils/formatConfigWarning.js';
|
|
17
16
|
import traverseConfig from '../../utils/traverseConfig.js';
|
|
18
17
|
function validateStateReferences({ page, context }) {
|
|
19
18
|
// Single traversal collects blockIds, _state references, and SetState keys
|
|
@@ -51,11 +50,11 @@ function validateStateReferences({ page, context }) {
|
|
|
51
50
|
// Skip if state key is from an input block or SetState action
|
|
52
51
|
if (blockIds.has(topLevelKey) || setStateKeys.has(topLevelKey)) return;
|
|
53
52
|
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.`;
|
|
54
|
-
context.logger.
|
|
53
|
+
context.logger.configWarning({
|
|
55
54
|
message,
|
|
56
55
|
configKey,
|
|
57
|
-
|
|
58
|
-
})
|
|
56
|
+
prodError: true
|
|
57
|
+
});
|
|
59
58
|
});
|
|
60
59
|
}
|
|
61
60
|
export default validateStateReferences;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/ import recursiveBuild from './recursiveBuild.js';
|
|
16
16
|
import makeRefDefinition from './makeRefDefinition.js';
|
|
17
17
|
import evaluateBuildOperators from './evaluateBuildOperators.js';
|
|
18
|
+
import evaluateStaticOperators from './evaluateStaticOperators.js';
|
|
18
19
|
async function buildRefs({ context }) {
|
|
19
20
|
const refDef = makeRefDefinition('lowdefy.yaml', null, context.refMap);
|
|
20
21
|
let components = await recursiveBuild({
|
|
@@ -22,11 +23,18 @@ async function buildRefs({ context }) {
|
|
|
22
23
|
refDef,
|
|
23
24
|
count: 0
|
|
24
25
|
});
|
|
26
|
+
// First: evaluate _build.* operators (e.g., _build.env)
|
|
25
27
|
components = await evaluateBuildOperators({
|
|
26
28
|
context,
|
|
27
29
|
input: components,
|
|
28
30
|
refDef
|
|
29
31
|
});
|
|
32
|
+
// Second: evaluate static operators (_sum, _if, etc.) that don't depend on runtime data
|
|
33
|
+
components = evaluateStaticOperators({
|
|
34
|
+
context,
|
|
35
|
+
input: components,
|
|
36
|
+
refDef
|
|
37
|
+
});
|
|
30
38
|
return components ?? {};
|
|
31
39
|
}
|
|
32
40
|
export default buildRefs;
|
|
@@ -14,10 +14,20 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { BuildParser } from '@lowdefy/operators';
|
|
16
16
|
import operators from '@lowdefy/operators-js/operators/build';
|
|
17
|
+
import collectDynamicIdentifiers from '../collectDynamicIdentifiers.js';
|
|
18
|
+
import validateOperatorsDynamic from '../validateOperatorsDynamic.js';
|
|
19
|
+
// Validate and collect dynamic identifiers once at module load
|
|
20
|
+
validateOperatorsDynamic({
|
|
21
|
+
operators
|
|
22
|
+
});
|
|
23
|
+
const dynamicIdentifiers = collectDynamicIdentifiers({
|
|
24
|
+
operators
|
|
25
|
+
});
|
|
17
26
|
async function evaluateBuildOperators({ context, input, refDef }) {
|
|
18
27
|
const operatorsParser = new BuildParser({
|
|
19
28
|
env: process.env,
|
|
20
|
-
operators
|
|
29
|
+
operators,
|
|
30
|
+
dynamicIdentifiers
|
|
21
31
|
});
|
|
22
32
|
const { output, errors } = operatorsParser.parse({
|
|
23
33
|
input,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 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
|
+
// Validate and collect dynamic identifiers once at module load
|
|
21
|
+
validateOperatorsDynamic({
|
|
22
|
+
operators
|
|
23
|
+
});
|
|
24
|
+
const dynamicIdentifiers = collectDynamicIdentifiers({
|
|
25
|
+
operators
|
|
26
|
+
});
|
|
27
|
+
function evaluateStaticOperators({ context, input, refDef }) {
|
|
28
|
+
// Collect type names from context.typesMap for type boundary detection
|
|
29
|
+
const typeNames = collectTypeNames({
|
|
30
|
+
typesMap: context.typesMap
|
|
31
|
+
});
|
|
32
|
+
const operatorsParser = new BuildParser({
|
|
33
|
+
env: process.env,
|
|
34
|
+
operators,
|
|
35
|
+
dynamicIdentifiers,
|
|
36
|
+
typeNames
|
|
37
|
+
});
|
|
38
|
+
const location = refDef.path ?? refDef.resolver;
|
|
39
|
+
const { output, errors } = operatorsParser.parse({
|
|
40
|
+
input,
|
|
41
|
+
location,
|
|
42
|
+
operatorPrefix: '_'
|
|
43
|
+
});
|
|
44
|
+
if (errors.length > 0) {
|
|
45
|
+
errors.forEach((error)=>{
|
|
46
|
+
context.logger.configWarning({
|
|
47
|
+
message: error.message,
|
|
48
|
+
operatorLocation: error.operatorLocation
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return output;
|
|
53
|
+
}
|
|
54
|
+
export default evaluateStaticOperators;
|
|
@@ -14,31 +14,36 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import path from 'path';
|
|
16
16
|
import { type } from '@lowdefy/helpers';
|
|
17
|
+
import { ConfigError } from '@lowdefy/node-utils';
|
|
17
18
|
async function getConfigFile({ context, refDef, referencedFrom }) {
|
|
18
19
|
if (!type.isString(refDef.path)) {
|
|
19
|
-
throw new
|
|
20
|
-
_ref:
|
|
21
|
-
|
|
20
|
+
throw new ConfigError({
|
|
21
|
+
message: `Invalid _ref definition: ${JSON.stringify({
|
|
22
|
+
_ref: refDef.original
|
|
23
|
+
})}`,
|
|
24
|
+
filePath: referencedFrom,
|
|
25
|
+
lineNumber: refDef.lineNumber,
|
|
26
|
+
configDirectory: context.directories.config
|
|
27
|
+
});
|
|
22
28
|
}
|
|
23
29
|
const content = await context.readConfigFile(refDef.path);
|
|
24
30
|
if (content === null) {
|
|
25
|
-
// Build helpful error message with resolved path information
|
|
26
|
-
const lineInfo = refDef.lineNumber ? `:${refDef.lineNumber}` : '';
|
|
27
31
|
const absolutePath = path.resolve(context.directories.config, refDef.path);
|
|
28
|
-
let message = `
|
|
29
|
-
message += ` Referenced from: ${referencedFrom}${lineInfo}\n`;
|
|
30
|
-
message += ` Resolved to: ${absolutePath}\n`;
|
|
32
|
+
let message = `Referenced file does not exist: "${refDef.path}". Resolved to: ${absolutePath}`;
|
|
31
33
|
// Help with common mistakes
|
|
32
34
|
if (refDef.path.startsWith('../')) {
|
|
33
35
|
const suggestedPath = refDef.path.replace(/^(\.\.\/)+/, '');
|
|
34
|
-
message +=
|
|
35
|
-
message += `\n Did you mean: "${suggestedPath}"?`;
|
|
36
|
+
message += ` Tip: Paths in _ref are resolved from config root. Did you mean "${suggestedPath}"?`;
|
|
36
37
|
} else if (refDef.path.startsWith('./')) {
|
|
37
38
|
const suggestedPath = refDef.path.substring(2);
|
|
38
|
-
message +=
|
|
39
|
-
message += `\n Did you mean: "${suggestedPath}"?`;
|
|
39
|
+
message += ` Tip: Remove "./" prefix - paths are resolved from config root. Did you mean "${suggestedPath}"?`;
|
|
40
40
|
}
|
|
41
|
-
throw new
|
|
41
|
+
throw new ConfigError({
|
|
42
|
+
message,
|
|
43
|
+
filePath: referencedFrom,
|
|
44
|
+
lineNumber: refDef.lineNumber,
|
|
45
|
+
configDirectory: context.directories.config
|
|
46
|
+
});
|
|
42
47
|
}
|
|
43
48
|
return content;
|
|
44
49
|
}
|
|
@@ -12,7 +12,8 @@
|
|
|
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 { ConfigError } from '@lowdefy/node-utils';
|
|
16
|
+
import getConfigFile from './getConfigFile.js';
|
|
16
17
|
import parseRefContent from './parseRefContent.js';
|
|
17
18
|
import runRefResolver from './runRefResolver.js';
|
|
18
19
|
async function getRefContent({ context, refDef, referencedFrom }) {
|
|
@@ -36,9 +37,18 @@ async function getRefContent({ context, refDef, referencedFrom }) {
|
|
|
36
37
|
referencedFrom
|
|
37
38
|
});
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
try {
|
|
41
|
+
return parseRefContent({
|
|
42
|
+
content,
|
|
43
|
+
refDef
|
|
44
|
+
});
|
|
45
|
+
} catch (error) {
|
|
46
|
+
// Re-throw parse errors as ConfigError with location info
|
|
47
|
+
throw new ConfigError({
|
|
48
|
+
message: `Error parsing "${refDef.path}": ${error.message}`,
|
|
49
|
+
filePath: refDef.path,
|
|
50
|
+
configDirectory: context.directories.config
|
|
51
|
+
});
|
|
52
|
+
}
|
|
43
53
|
}
|
|
44
54
|
export default getRefContent;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import getRefPath from './getRefPath.js';
|
|
17
17
|
import makeId from '../../utils/makeId.js';
|
|
18
18
|
function makeRefDefinition(refDefinition, parent, refMap, lineNumber) {
|
|
19
|
-
const id = makeId();
|
|
19
|
+
const id = makeId.next();
|
|
20
20
|
const refDef = {
|
|
21
21
|
parent,
|
|
22
22
|
lineNumber
|
|
@@ -36,10 +36,32 @@ function addLineNumbers(node, content, result) {
|
|
|
36
36
|
if (isPair(pair) && isScalar(pair.key)) {
|
|
37
37
|
const key = pair.key.value;
|
|
38
38
|
const value = pair.value;
|
|
39
|
+
// Use key's line number for the value's ~l (more useful for error messages)
|
|
40
|
+
const keyLineNumber = pair.key.range ? getLineNumber(content, pair.key.range[0]) : null;
|
|
39
41
|
if (isMap(value)) {
|
|
40
|
-
|
|
42
|
+
const mapResult = addLineNumbers(value, content, {});
|
|
43
|
+
// Override ~l with key's line number if available
|
|
44
|
+
if (keyLineNumber) {
|
|
45
|
+
Object.defineProperty(mapResult, '~l', {
|
|
46
|
+
value: keyLineNumber,
|
|
47
|
+
enumerable: false,
|
|
48
|
+
writable: true,
|
|
49
|
+
configurable: true
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
obj[key] = mapResult;
|
|
41
53
|
} else if (isSeq(value)) {
|
|
42
|
-
|
|
54
|
+
const arrResult = addLineNumbers(value, content, []);
|
|
55
|
+
// Override ~l with key's line number if available
|
|
56
|
+
if (keyLineNumber) {
|
|
57
|
+
Object.defineProperty(arrResult, '~l', {
|
|
58
|
+
value: keyLineNumber,
|
|
59
|
+
enumerable: false,
|
|
60
|
+
writable: true,
|
|
61
|
+
configurable: true
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
obj[key] = arrResult;
|
|
43
65
|
} else if (isScalar(value)) {
|
|
44
66
|
obj[key] = value.value;
|
|
45
67
|
} else {
|
|
@@ -51,6 +73,14 @@ function addLineNumbers(node, content, result) {
|
|
|
51
73
|
}
|
|
52
74
|
if (isSeq(node)) {
|
|
53
75
|
const arr = result || [];
|
|
76
|
+
if (node.range) {
|
|
77
|
+
Object.defineProperty(arr, '~l', {
|
|
78
|
+
value: getLineNumber(content, node.range[0]),
|
|
79
|
+
enumerable: false,
|
|
80
|
+
writable: true,
|
|
81
|
+
configurable: true
|
|
82
|
+
});
|
|
83
|
+
}
|
|
54
84
|
for (const item of node.items){
|
|
55
85
|
if (isMap(item)) {
|
|
56
86
|
arr.push(addLineNumbers(item, content, {}));
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { serializer, type } from '@lowdefy/helpers';
|
|
16
|
+
import { ConfigError } from '@lowdefy/node-utils';
|
|
16
17
|
import evaluateBuildOperators from './evaluateBuildOperators.js';
|
|
17
|
-
import formatConfigError from '../../utils/formatConfigError.js';
|
|
18
18
|
import getKey from './getKey.js';
|
|
19
19
|
import getRefContent from './getRefContent.js';
|
|
20
20
|
import getRefsFromFile from './getRefsFromFile.js';
|
|
@@ -30,20 +30,22 @@ async function recursiveBuild({ context, refDef, count, referencedFrom, refChain
|
|
|
30
30
|
...refChainList,
|
|
31
31
|
currentPath
|
|
32
32
|
].join('\n -> ');
|
|
33
|
-
throw new
|
|
34
|
-
message: `Circular reference detected
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
throw new ConfigError({
|
|
34
|
+
message: `Circular reference detected. File "${currentPath}" references itself through:\n -> ${chainDisplay}`,
|
|
35
|
+
filePath: referencedFrom,
|
|
36
|
+
lineNumber: refDef.lineNumber,
|
|
37
|
+
configDirectory: context.directories.config
|
|
38
|
+
});
|
|
37
39
|
}
|
|
38
40
|
refChainSet.add(currentPath);
|
|
39
41
|
refChainList.push(currentPath);
|
|
40
42
|
}
|
|
41
43
|
// Keep count as a fallback safety limit
|
|
42
44
|
if (count > 10000) {
|
|
43
|
-
throw new
|
|
45
|
+
throw new ConfigError({
|
|
44
46
|
message: `Maximum recursion depth of references exceeded (10000 levels). This likely indicates a circular reference.`,
|
|
45
47
|
context
|
|
46
|
-
})
|
|
48
|
+
});
|
|
47
49
|
}
|
|
48
50
|
let fileContent = await getRefContent({
|
|
49
51
|
context,
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { type } from '@lowdefy/helpers';
|
|
16
|
+
import { ConfigError } from '@lowdefy/node-utils';
|
|
16
17
|
import getUserJavascriptFunction from './getUserJavascriptFunction.js';
|
|
17
18
|
async function runRefResolver({ context, refDef, referencedFrom }) {
|
|
18
19
|
const resolverFn = await getUserJavascriptFunction({
|
|
@@ -23,10 +24,20 @@ async function runRefResolver({ context, refDef, referencedFrom }) {
|
|
|
23
24
|
try {
|
|
24
25
|
content = await resolverFn(refDef.path, refDef.vars, context);
|
|
25
26
|
} catch (error) {
|
|
26
|
-
throw new
|
|
27
|
+
throw new ConfigError({
|
|
28
|
+
message: `Error calling resolver "${refDef.resolver}": ${error.message}`,
|
|
29
|
+
filePath: referencedFrom,
|
|
30
|
+
lineNumber: refDef.lineNumber,
|
|
31
|
+
configDirectory: context.directories.config
|
|
32
|
+
});
|
|
27
33
|
}
|
|
28
34
|
if (type.isNone(content)) {
|
|
29
|
-
throw new
|
|
35
|
+
throw new ConfigError({
|
|
36
|
+
message: `Resolver "${refDef.resolver}" returned "${content}".`,
|
|
37
|
+
filePath: referencedFrom,
|
|
38
|
+
lineNumber: refDef.lineNumber,
|
|
39
|
+
configDirectory: context.directories.config
|
|
40
|
+
});
|
|
30
41
|
}
|
|
31
42
|
return content;
|
|
32
43
|
}
|
package/dist/build/buildTypes.js
CHANGED
|
@@ -16,11 +16,20 @@
|
|
|
16
16
|
import loaderTypes from '@lowdefy/blocks-loaders/types';
|
|
17
17
|
import findSimilarString from '../utils/findSimilarString.js';
|
|
18
18
|
import formatBuildError from './formatBuildError.js';
|
|
19
|
+
// Check if a configKey has ~ignoreBuildCheck set
|
|
20
|
+
function hasIgnoreBuildCheck(keyMap, configKey) {
|
|
21
|
+
return keyMap[configKey]?.['~ignoreBuildCheck'] === true;
|
|
22
|
+
}
|
|
19
23
|
function buildTypeClass(context, { counter, definitions, store, typeClass, warnIfMissing = false }) {
|
|
20
24
|
const counts = counter.getCounts();
|
|
21
25
|
const definedTypes = Object.keys(definitions);
|
|
22
26
|
Object.keys(counts).forEach((typeName)=>{
|
|
23
27
|
if (!definitions[typeName]) {
|
|
28
|
+
// Check if this type usage has ~ignoreBuildCheck flag
|
|
29
|
+
const configKey = counter.getLocation(typeName);
|
|
30
|
+
if (configKey && hasIgnoreBuildCheck(context.keyMap, configKey)) {
|
|
31
|
+
return; // Skip warning/error for this type
|
|
32
|
+
}
|
|
24
33
|
let message = `${typeClass} type "${typeName}" was used but is not defined.`;
|
|
25
34
|
const suggestion = findSimilarString({
|
|
26
35
|
input: typeName,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 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;
|
|
@@ -12,13 +12,25 @@
|
|
|
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
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
+
}
|
|
22
33
|
});
|
|
34
|
+
return typeNames;
|
|
23
35
|
}
|
|
24
|
-
export default
|
|
36
|
+
export default collectTypeNames;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
|
-
*/ import { resolveConfigLocation } from '@lowdefy/
|
|
15
|
+
*/ import { resolveConfigLocation } from '@lowdefy/node-utils';
|
|
16
16
|
function formatBuildError({ context, counter, typeName, message }) {
|
|
17
17
|
const configKey = counter.getLocation(typeName);
|
|
18
18
|
if (!configKey) {
|
package/dist/build/testSchema.js
CHANGED
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { validate } from '@lowdefy/ajv';
|
|
16
|
+
import { ConfigError } from '@lowdefy/node-utils';
|
|
17
|
+
import findConfigKey from '../utils/findConfigKey.js';
|
|
16
18
|
import lowdefySchema from '../lowdefySchema.js';
|
|
17
|
-
import formatErrorMessage from '../utils/formatErrorMessage.js';
|
|
18
19
|
function testSchema({ components, context }) {
|
|
19
20
|
const { valid, errors } = validate({
|
|
20
21
|
schema: lowdefySchema,
|
|
@@ -22,11 +23,49 @@ function testSchema({ components, context }) {
|
|
|
22
23
|
returnErrors: true
|
|
23
24
|
});
|
|
24
25
|
if (!valid) {
|
|
25
|
-
|
|
26
|
-
errors
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
// Filter out anyOf/oneOf cascade errors - these are always accompanied by
|
|
27
|
+
// more specific validation errors and just add noise
|
|
28
|
+
let filteredErrors = errors.filter((error)=>error.keyword !== 'anyOf' && error.keyword !== 'oneOf');
|
|
29
|
+
// Hierarchical deduplication: if an error exists at a child path,
|
|
30
|
+
// filter out errors at parent paths (prefer more specific errors)
|
|
31
|
+
filteredErrors = filteredErrors.filter((error)=>{
|
|
32
|
+
const hasChildError = filteredErrors.some((other)=>other !== error && other.instancePath.startsWith(error.instancePath + '/'));
|
|
33
|
+
return !hasChildError;
|
|
34
|
+
});
|
|
35
|
+
// Same-path deduplication: only show first error per unique path
|
|
36
|
+
// (multiple errors at same path are usually cascade errors from schema branches)
|
|
37
|
+
const seenPaths = new Set();
|
|
38
|
+
filteredErrors = filteredErrors.filter((error)=>{
|
|
39
|
+
if (seenPaths.has(error.instancePath)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
seenPaths.add(error.instancePath);
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
filteredErrors.forEach((error)=>{
|
|
46
|
+
const instancePath = error.instancePath.split('/').slice(1).filter(Boolean);
|
|
47
|
+
const configKey = findConfigKey({
|
|
48
|
+
components,
|
|
49
|
+
instancePath
|
|
50
|
+
});
|
|
51
|
+
let message = error.message;
|
|
52
|
+
if (error.params?.additionalProperty) {
|
|
53
|
+
message = `${message} - "${error.params.additionalProperty}"`;
|
|
54
|
+
}
|
|
55
|
+
const configError = new ConfigError({
|
|
56
|
+
message,
|
|
57
|
+
configKey,
|
|
58
|
+
context
|
|
59
|
+
});
|
|
60
|
+
if (!configError.suppressed) {
|
|
61
|
+
if (!context.errors) {
|
|
62
|
+
// If no error collection array, throw immediately (fallback for tests)
|
|
63
|
+
throw new Error(configError.message);
|
|
64
|
+
}
|
|
65
|
+
// Collect error - logging happens at checkpoints in index.js
|
|
66
|
+
context.errors.push(configError.message);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
30
69
|
}
|
|
31
70
|
}
|
|
32
71
|
export default testSchema;
|