@lowdefy/build 0.0.0-experimental-20260114142524 → 0.0.0-experimental-20260122121633
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/populateRefs.js +43 -5
- 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 +403 -403
- 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,6 +13,40 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { get, serializer, type } from '@lowdefy/helpers';
|
|
16
|
+
/**
|
|
17
|
+
* Copies a _var value while preserving source location markers.
|
|
18
|
+
*
|
|
19
|
+
* When _var copies a value from vars into a template, we need to preserve
|
|
20
|
+
* the ~r (ref ID) and ~l (line number) from WHERE THE VALUE IS DEFINED
|
|
21
|
+
* (the source file), not where _var is used (the template).
|
|
22
|
+
*
|
|
23
|
+
* The issue is that serializer.copy loses non-enumerable properties, and
|
|
24
|
+
* recursiveBuild.js then sets ~r to the template file for objects without ~r.
|
|
25
|
+
*
|
|
26
|
+
* This function preserves the source location by making ~r enumerable on
|
|
27
|
+
* the copied value, so it survives through subsequent serializer.copy calls.
|
|
28
|
+
*
|
|
29
|
+
* @param {*} value - The value to copy from vars
|
|
30
|
+
* @param {string} sourceRefId - The ref ID of the source file where the var is defined
|
|
31
|
+
* @returns {*} The copied value with preserved source location markers
|
|
32
|
+
*/ function copyVarValue(value, sourceRefId) {
|
|
33
|
+
if (!type.isObject(value) && !type.isArray(value)) {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
// Copy the value, preserving ~l and setting ~r to the source file
|
|
37
|
+
return serializer.copy(value, {
|
|
38
|
+
reviver: (_, v)=>{
|
|
39
|
+
if (type.isObject(v)) {
|
|
40
|
+
// Preserve the source file's ref ID by setting it explicitly
|
|
41
|
+
// This prevents recursiveBuild from overwriting it with the template's ref ID
|
|
42
|
+
if (sourceRefId && v['~r'] === undefined) {
|
|
43
|
+
v['~r'] = sourceRefId;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return v;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
16
50
|
function refReviver(key, value) {
|
|
17
51
|
if (type.isObject(value)) {
|
|
18
52
|
if (!type.isUndefined(value._ref)) {
|
|
@@ -20,14 +54,16 @@ function refReviver(key, value) {
|
|
|
20
54
|
}
|
|
21
55
|
if (value._var) {
|
|
22
56
|
if (type.isString(value._var)) {
|
|
23
|
-
|
|
57
|
+
const varValue = get(this.vars, value._var, {
|
|
24
58
|
default: null
|
|
25
|
-
})
|
|
59
|
+
});
|
|
60
|
+
return copyVarValue(varValue, this.sourceRefId);
|
|
26
61
|
}
|
|
27
62
|
if (type.isObject(value._var) && type.isString(value._var.key)) {
|
|
28
|
-
|
|
63
|
+
const varValue = get(this.vars, value._var.key, {
|
|
29
64
|
default: type.isNone(value._var.default) ? null : value._var.default
|
|
30
|
-
})
|
|
65
|
+
});
|
|
66
|
+
return copyVarValue(varValue, this.sourceRefId);
|
|
31
67
|
}
|
|
32
68
|
throw new Error(`"_var" operator takes a string or object with "key" field as arguments. Received "${JSON.stringify(value)}"`);
|
|
33
69
|
}
|
|
@@ -36,10 +72,12 @@ function refReviver(key, value) {
|
|
|
36
72
|
}
|
|
37
73
|
function populateRefs({ parsedFiles, refDef, toPopulate }) {
|
|
38
74
|
// Use serializer.copy to preserve non-enumerable properties like ~r, ~k, ~l
|
|
75
|
+
// sourceRefId is the PARENT file's ref ID where vars are defined (not the template's ID)
|
|
39
76
|
return serializer.copy(toPopulate, {
|
|
40
77
|
reviver: refReviver.bind({
|
|
41
78
|
parsedFiles,
|
|
42
|
-
vars: refDef.vars
|
|
79
|
+
vars: refDef.vars,
|
|
80
|
+
sourceRefId: refDef.parent
|
|
43
81
|
})
|
|
44
82
|
});
|
|
45
83
|
}
|
|
@@ -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) {
|