@lowdefy/build 4.5.2 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/addDefaultPages/404.js +1 -1
- package/dist/build/addDefaultPages/addDefaultPages.js +10 -4
- package/dist/build/addKeys.js +85 -29
- package/dist/build/buildApi/buildApi.js +27 -8
- package/dist/build/buildApi/buildEndpoint.js +7 -1
- package/dist/build/buildApi/buildRoutine/buildControl.js +1 -1
- package/dist/build/buildApi/buildRoutine/buildRoutine.js +1 -1
- package/dist/build/buildApi/buildRoutine/buildStep.js +1 -1
- package/dist/build/buildApi/buildRoutine/controlTypes.js +34 -11
- package/dist/build/buildApi/buildRoutine/countControl.js +1 -1
- package/dist/build/buildApi/buildRoutine/countStepTypes.js +2 -2
- package/dist/build/buildApi/buildRoutine/setStepId.js +1 -1
- package/dist/build/buildApi/buildRoutine/validateStep.js +30 -9
- package/dist/build/buildApi/validateEndpoint.js +36 -7
- package/dist/build/buildApi/validateStepReferences.js +65 -0
- package/dist/build/buildApp.js +1 -1
- package/dist/build/buildAuth/buildApiAuth.js +7 -3
- package/dist/build/buildAuth/buildAuth.js +7 -4
- package/dist/build/buildAuth/buildAuthPlugins.js +42 -13
- package/dist/build/buildAuth/buildPageAuth.js +14 -3
- package/dist/build/buildAuth/getApiRoles.js +1 -1
- package/dist/build/buildAuth/getPageRoles.js +1 -1
- package/dist/build/buildAuth/getProtectedApi.js +1 -1
- package/dist/build/buildAuth/getProtectedPages.js +1 -1
- package/dist/build/buildAuth/validateAuthConfig.js +39 -5
- package/dist/build/buildAuth/validateMutualExclusivity.js +13 -5
- package/dist/build/buildConnections.js +23 -24
- package/dist/build/buildImports/buildIconImports.js +1 -1
- package/dist/build/buildImports/buildImports.js +1 -1
- package/dist/build/buildImports/buildImportsDev.js +1 -1
- package/dist/build/buildImports/buildImportsProd.js +1 -1
- package/dist/build/buildImports/buildStyleImports.js +1 -1
- package/dist/build/buildImports/defaultIconsDev.js +1 -1
- package/dist/build/buildImports/defaultIconsProd.js +1 -1
- package/dist/build/buildJs/generateJsFile.js +1 -1
- package/dist/build/buildJs/jsMapParser.js +1 -1
- package/dist/build/buildJs/writeJs.js +1 -1
- package/dist/build/buildLogger.js +41 -0
- package/dist/build/buildMenu.js +36 -12
- package/dist/build/buildPages/buildBlock/buildBlock.js +1 -1
- package/dist/build/buildPages/buildBlock/buildEvents.js +79 -9
- package/dist/build/buildPages/buildBlock/buildRequests.js +38 -8
- package/dist/build/buildPages/buildBlock/buildSubBlocks.js +6 -2
- package/dist/build/buildPages/buildBlock/countBlockOperators.js +1 -1
- package/dist/build/buildPages/buildBlock/countBlockTypes.js +2 -2
- package/dist/build/buildPages/buildBlock/moveSkeletonBlocksToArea.js +6 -2
- package/dist/build/buildPages/buildBlock/moveSubBlocksToArea.js +6 -2
- package/dist/build/buildPages/buildBlock/setBlockId.js +1 -1
- package/dist/build/buildPages/buildBlock/validateBlock.js +25 -7
- package/dist/build/buildPages/buildPage.js +35 -6
- package/dist/build/buildPages/validateLinkReferences.js +33 -0
- package/dist/build/buildPages/validatePayloadReferences.js +59 -0
- package/dist/build/buildPages/validateRequestReferences.js +33 -0
- package/dist/build/buildPages/validateServerStateReferences.js +41 -0
- package/dist/build/buildPages/validateStateReferences.js +79 -0
- package/dist/build/buildRefs/buildRefs.js +40 -7
- package/dist/build/buildRefs/evaluateStaticOperators.js +52 -0
- package/dist/build/buildRefs/getConfigFile.js +25 -6
- package/dist/build/buildRefs/getKey.js +1 -1
- package/dist/build/buildRefs/getRefContent.js +1 -1
- package/dist/build/buildRefs/getRefPath.js +1 -1
- package/dist/build/buildRefs/getUserJavascriptFunction.js +18 -4
- package/dist/build/buildRefs/makeRefDefinition.js +10 -5
- package/dist/build/buildRefs/parseNunjucks.js +1 -1
- package/dist/build/buildRefs/parseRefContent.js +108 -7
- package/dist/build/buildRefs/runRefResolver.js +11 -3
- package/dist/build/buildRefs/runTransformer.js +7 -3
- package/dist/build/buildRefs/walker.js +340 -0
- package/dist/build/buildTypes.js +22 -7
- package/dist/build/cleanBuildDirectory.js +1 -1
- package/dist/build/collectDynamicIdentifiers.js +35 -0
- package/dist/build/collectTypeNames.js +36 -0
- package/dist/build/copyPublicFolder.js +1 -1
- package/dist/build/{buildJs → full}/buildJs.js +3 -3
- package/dist/build/full/buildPages.js +87 -0
- package/dist/build/{buildPages → full}/buildTestPage.js +15 -3
- package/dist/build/{updateServerPackageJson.js → full/updateServerPackageJson.js} +1 -1
- package/dist/build/{writePages.js → full/writePages.js} +1 -1
- package/dist/build/{writeRequests.js → full/writeRequests.js} +11 -3
- package/dist/build/{writeTypes.js → full/writeTypes.js} +1 -1
- package/dist/build/jit/addInstalledTypes.js +51 -0
- package/dist/build/jit/buildJsShallow.js +41 -0
- package/dist/build/jit/buildPageJit.js +271 -0
- package/dist/build/jit/buildShallowPages.js +90 -0
- package/dist/build/jit/createPageRegistry.js +85 -0
- package/dist/build/jit/detectMissingPluginPackages.js +62 -0
- package/dist/build/jit/isPageContentPath.js +24 -0
- package/dist/build/jit/pageContentKeys.js +26 -0
- package/dist/build/jit/shallowBuild.js +242 -0
- package/dist/build/jit/updateServerPackageJsonJit.js +33 -0
- package/dist/build/jit/validatePageTypes.js +73 -0
- package/dist/build/jit/writePageJit.js +44 -0
- package/dist/build/jit/writePageRegistry.js +23 -0
- package/dist/build/jit/writeSourcelessPages.js +23 -0
- package/dist/build/testSchema.js +45 -7
- package/dist/build/validateConfig.js +2 -2
- package/dist/build/validateOperatorsDynamic.js +28 -0
- package/dist/build/writeApi.js +1 -1
- package/dist/build/writeApp.js +1 -1
- package/dist/build/writeAuth.js +1 -1
- package/dist/build/writeConfig.js +1 -1
- package/dist/build/writeConnections.js +1 -1
- package/dist/build/writeGlobal.js +1 -1
- package/dist/build/writeLogger.js +19 -0
- package/dist/build/writeMaps.js +1 -1
- package/dist/build/writeMenus.js +1 -1
- package/dist/build/writePluginImports/generateImportFile.js +1 -1
- package/dist/build/writePluginImports/writeActionImports.js +1 -1
- package/dist/build/writePluginImports/writeActionSchemaMap.js +42 -0
- package/dist/build/writePluginImports/writeAuthImports.js +1 -1
- package/dist/build/writePluginImports/writeBlockImports.js +1 -1
- package/dist/build/writePluginImports/writeBlockSchemaMap.js +42 -0
- package/dist/build/writePluginImports/writeConnectionImports.js +1 -1
- package/dist/build/writePluginImports/writeIconImports.js +1 -1
- package/dist/build/writePluginImports/writeOperatorImports.js +1 -1
- package/dist/build/writePluginImports/writeOperatorSchemaMap.js +49 -0
- package/dist/build/writePluginImports/writePluginImports.js +16 -1
- package/dist/build/writePluginImports/writeStyleImports.js +1 -1
- package/dist/createContext.js +16 -4
- package/dist/defaultTypesMap.js +479 -457
- package/dist/index.js +190 -127
- package/dist/indexDev.js +19 -0
- package/dist/lowdefySchema.js +588 -0
- package/dist/scripts/generateDefaultTypes.js +2 -1
- package/dist/scripts/run.js +1 -1
- package/dist/{test → test-utils}/buildRefs/testBuildRefsAsyncFunction.js +1 -1
- package/dist/{test → test-utils}/buildRefs/testBuildRefsErrorResolver.js +1 -1
- package/dist/{test → test-utils}/buildRefs/testBuildRefsNullResolver.js +1 -1
- package/dist/{test → test-utils}/buildRefs/testBuildRefsParsingResolver.js +1 -1
- package/dist/{test → test-utils}/buildRefs/testBuildRefsResolver.js +1 -1
- package/dist/{test → test-utils}/buildRefs/testBuildRefsTransform.js +1 -1
- package/dist/{test → test-utils}/buildRefs/testBuildRefsTransformIdentity.js +1 -1
- package/dist/test-utils/buildRefs/testJitPageResolver.js +24 -0
- package/dist/test-utils/createTestLogger.js +36 -0
- package/dist/test-utils/parseTestYaml.js +126 -0
- package/dist/test-utils/runBuild.js +228 -0
- package/dist/test-utils/runBuildForSnapshots.js +704 -0
- package/dist/{test → test-utils}/testContext.js +12 -2
- package/dist/utils/collectExceptions.js +34 -0
- package/dist/utils/countOperators.js +31 -12
- package/dist/utils/createBuildHandleError.js +38 -0
- package/dist/utils/createCheckDuplicateId.js +15 -9
- package/dist/utils/createCounter.js +16 -3
- package/dist/utils/createHandleWarning.js +41 -0
- package/dist/utils/createPluginTypesMap.js +1 -1
- package/dist/utils/extractOperatorKey.js +36 -0
- package/dist/utils/findConfigKey.js +37 -0
- package/dist/utils/findSimilarString.js +53 -0
- package/dist/utils/logCollectedErrors.js +30 -0
- package/dist/utils/makeId.js +16 -8
- package/dist/utils/preserveMetaProperties.js +27 -0
- package/dist/utils/readConfigFile.js +1 -1
- package/dist/utils/setNonEnumerableProperty.js +23 -0
- package/dist/utils/traverseConfig.js +43 -0
- package/dist/utils/tryBuildStep.js +46 -0
- package/dist/utils/writeBuildArtifact.js +1 -1
- package/package.json +46 -41
- package/dist/build/buildPages/buildPages.js +0 -31
- package/dist/build/buildRefs/evaluateBuildOperators.js +0 -34
- package/dist/build/buildRefs/getRefsFromFile.js +0 -37
- package/dist/build/buildRefs/populateRefs.js +0 -43
- package/dist/build/buildRefs/recursiveBuild.js +0 -85
- package/dist/utils/formatErrorMessage.js +0 -56
|
@@ -0,0 +1,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.
|
|
@@ -12,17 +12,50 @@
|
|
|
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 operators from '@lowdefy/operators-js/operators/build';
|
|
16
|
+
import { resolve, WalkContext } from './walker.js';
|
|
17
|
+
import getRefContent from './getRefContent.js';
|
|
16
18
|
import makeRefDefinition from './makeRefDefinition.js';
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
+
import evaluateStaticOperators from './evaluateStaticOperators.js';
|
|
20
|
+
import collectDynamicIdentifiers from '../collectDynamicIdentifiers.js';
|
|
21
|
+
import validateOperatorsDynamic from '../validateOperatorsDynamic.js';
|
|
22
|
+
import isPageContentPath from '../jit/isPageContentPath.js';
|
|
23
|
+
// Validate and collect dynamic identifiers once at module load
|
|
24
|
+
validateOperatorsDynamic({
|
|
25
|
+
operators
|
|
26
|
+
});
|
|
27
|
+
const dynamicIdentifiers = collectDynamicIdentifiers({
|
|
28
|
+
operators
|
|
29
|
+
});
|
|
30
|
+
async function buildRefs({ context, shallowOptions }) {
|
|
31
|
+
context.unresolvedRefVars = context.unresolvedRefVars ?? {};
|
|
19
32
|
const refDef = makeRefDefinition('lowdefy.yaml', null, context.refMap);
|
|
20
|
-
|
|
33
|
+
const ctx = new WalkContext({
|
|
34
|
+
buildContext: context,
|
|
35
|
+
refId: refDef.id,
|
|
36
|
+
sourceRefId: null,
|
|
37
|
+
vars: {},
|
|
38
|
+
path: '',
|
|
39
|
+
currentFile: refDef.path,
|
|
40
|
+
refChain: new Set(refDef.path ? [
|
|
41
|
+
refDef.path
|
|
42
|
+
] : []),
|
|
43
|
+
operators,
|
|
44
|
+
env: process.env,
|
|
45
|
+
dynamicIdentifiers,
|
|
46
|
+
shouldStop: shallowOptions ? // JIT can re-resolve them from source files. Inline pages (defined
|
|
47
|
+
// directly in lowdefy.yaml) live in the root ref and have no separate
|
|
48
|
+
// source file — their content must be preserved for buildShallowPages.
|
|
49
|
+
(path, refId)=>isPageContentPath(path) && refId !== refDef.id : null
|
|
50
|
+
});
|
|
51
|
+
const content = await getRefContent({
|
|
21
52
|
context,
|
|
22
53
|
refDef,
|
|
23
|
-
|
|
54
|
+
referencedFrom: null
|
|
24
55
|
});
|
|
25
|
-
components = await
|
|
56
|
+
let components = await resolve(content, ctx);
|
|
57
|
+
// Evaluate static operators (_sum, _if, etc.) that don't depend on runtime data
|
|
58
|
+
components = evaluateStaticOperators({
|
|
26
59
|
context,
|
|
27
60
|
input: components,
|
|
28
61
|
refDef
|
|
@@ -0,0 +1,52 @@
|
|
|
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 { evaluateOperators } 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
|
+
validateOperatorsDynamic({
|
|
22
|
+
operators
|
|
23
|
+
});
|
|
24
|
+
const dynamicIdentifiers = collectDynamicIdentifiers({
|
|
25
|
+
operators
|
|
26
|
+
});
|
|
27
|
+
function evaluateStaticOperators({ context, input, refDef }) {
|
|
28
|
+
const typeNames = collectTypeNames({
|
|
29
|
+
typesMap: context.typesMap
|
|
30
|
+
});
|
|
31
|
+
const { output, errors } = evaluateOperators({
|
|
32
|
+
input,
|
|
33
|
+
operators,
|
|
34
|
+
operatorPrefix: '_',
|
|
35
|
+
env: process.env,
|
|
36
|
+
dynamicIdentifiers,
|
|
37
|
+
typeNames
|
|
38
|
+
});
|
|
39
|
+
if (errors.length > 0) {
|
|
40
|
+
errors.forEach((error)=>{
|
|
41
|
+
// Resolve source file path for error location.
|
|
42
|
+
// Only called from buildRefs top-level where refDef is root lowdefy.yaml,
|
|
43
|
+
// but ~r on each operator object identifies the real source file via refMap.
|
|
44
|
+
// Falls back to refDef.path if ~r is missing (shouldn't happen at this stage
|
|
45
|
+
// since all objects have ~r after recursiveBuild completes).
|
|
46
|
+
error.filePath = error.refId ? context.refMap[error.refId]?.path : refDef.path;
|
|
47
|
+
collectExceptions(context, error);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return output;
|
|
51
|
+
}
|
|
52
|
+
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.
|
|
@@ -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,125 @@
|
|
|
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
|
-
|
|
25
|
-
|
|
95
|
+
const isNjk = ext === 'njk';
|
|
96
|
+
if (isNjk) {
|
|
97
|
+
try {
|
|
98
|
+
content = parseNunjucks(content, vars);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
throw new ConfigError(`Nunjucks error in "${path}".`, {
|
|
101
|
+
cause: error,
|
|
102
|
+
filePath: path
|
|
103
|
+
});
|
|
104
|
+
}
|
|
26
105
|
ext = getFileSubExtension(path);
|
|
27
106
|
}
|
|
28
107
|
if (ext === 'yaml' || ext === 'yml') {
|
|
29
|
-
|
|
108
|
+
try {
|
|
109
|
+
content = parseYamlWithLineNumbers(content);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (isNjk) {
|
|
112
|
+
throw new ConfigError(`Nunjucks template "${path}" produced invalid YAML.`, {
|
|
113
|
+
cause: error,
|
|
114
|
+
filePath: path
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const lineMatch = error.message.match(/at line (\d+)/);
|
|
118
|
+
throw new ConfigError(`YAML parse error in "${path}".`, {
|
|
119
|
+
cause: error,
|
|
120
|
+
filePath: path,
|
|
121
|
+
lineNumber: lineMatch ? lineMatch[1] : null
|
|
122
|
+
});
|
|
123
|
+
}
|
|
30
124
|
}
|
|
31
125
|
if (ext === 'json') {
|
|
32
|
-
|
|
126
|
+
try {
|
|
127
|
+
content = JSON5.parse(content);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
throw new ConfigError(`JSON parse error in "${path}".`, {
|
|
130
|
+
cause: error,
|
|
131
|
+
filePath: path
|
|
132
|
+
});
|
|
133
|
+
}
|
|
33
134
|
}
|
|
34
135
|
}
|
|
35
136
|
return content;
|
|
@@ -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.
|
|
@@ -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/errors';
|
|
16
17
|
import getUserJavascriptFunction from './getUserJavascriptFunction.js';
|
|
17
18
|
async function runRefResolver({ context, refDef, referencedFrom }) {
|
|
18
19
|
const resolverFn = await getUserJavascriptFunction({
|
|
@@ -23,10 +24,17 @@ 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(`Error calling resolver "${refDef.resolver}".`, {
|
|
28
|
+
cause: error,
|
|
29
|
+
filePath: referencedFrom,
|
|
30
|
+
lineNumber: refDef.lineNumber
|
|
31
|
+
});
|
|
27
32
|
}
|
|
28
33
|
if (type.isNone(content)) {
|
|
29
|
-
throw new
|
|
34
|
+
throw new ConfigError(`Resolver "${refDef.resolver}" returned "${content}".`, {
|
|
35
|
+
filePath: referencedFrom,
|
|
36
|
+
lineNumber: refDef.lineNumber
|
|
37
|
+
});
|
|
30
38
|
}
|
|
31
39
|
return content;
|
|
32
40
|
}
|
|
@@ -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,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/errors';
|
|
16
|
+
import getUserJavascriptFunction from './getUserJavascriptFunction.js';
|
|
16
17
|
async function runTransformer({ context, input, refDef }) {
|
|
17
18
|
if (refDef.transformer) {
|
|
18
19
|
const transformerFn = await getUserJavascriptFunction({
|
|
@@ -22,7 +23,10 @@ async function runTransformer({ context, input, refDef }) {
|
|
|
22
23
|
try {
|
|
23
24
|
return transformerFn(input, refDef.vars);
|
|
24
25
|
} catch (error) {
|
|
25
|
-
throw
|
|
26
|
+
throw new ConfigError(`Error calling transformer "${refDef.transformer}" from "${refDef.path}".`, {
|
|
27
|
+
cause: error,
|
|
28
|
+
filePath: refDef.transformer
|
|
29
|
+
});
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
32
|
return input;
|