@lowdefy/build 4.6.0 → 4.7.1
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/buildImports/buildIconImports.js +1 -31
- package/dist/build/buildImports/iconPackages.js +49 -0
- package/dist/build/buildRefs/buildRefs.js +35 -19
- package/dist/build/buildRefs/evaluateStaticOperators.js +5 -9
- package/dist/build/buildRefs/makeRefDefinition.js +4 -2
- package/dist/build/buildRefs/parseRefContent.js +8 -1
- package/dist/build/buildRefs/walker.js +348 -0
- package/dist/build/jit/buildPageJit.js +77 -30
- package/dist/build/jit/createPageRegistry.js +6 -1
- package/dist/build/jit/detectMissingIcons.js +36 -0
- package/dist/build/jit/extractIconData.js +54 -0
- package/dist/build/jit/shallowBuild.js +5 -4
- package/dist/build/jit/updateIconImportsJit.js +45 -0
- package/dist/build/jit/{stripPageContent.js → writeIconsDynamic.js} +5 -8
- package/dist/defaultTypesMap.js +347 -338
- package/dist/index.js +8 -6
- package/dist/indexDev.js +1 -0
- package/dist/lowdefySchema.js +0 -1
- package/dist/utils/makeId.js +3 -0
- package/package.json +42 -42
- package/dist/build/buildRefs/createRefReviver.js +0 -28
- package/dist/build/buildRefs/evaluateBuildOperators.js +0 -53
- package/dist/build/buildRefs/getRefsFromFile.js +0 -42
- package/dist/build/buildRefs/populateRefs.js +0 -105
- package/dist/build/buildRefs/recursiveBuild.js +0 -133
- package/dist/build/jit/getRefPositions.js +0 -38
|
@@ -12,36 +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
|
-
*/
|
|
16
|
-
'react-icons/ai': /"(Ai[A-Z0-9]\w*)"/gm,
|
|
17
|
-
'react-icons/bi': /"(Bi[A-Z0-9]\w*)"/gm,
|
|
18
|
-
'react-icons/bs': /"(Bs[A-Z0-9]\w*)"/gm,
|
|
19
|
-
'react-icons/cg': /"(Cg[A-Z0-9]\w*)"/gm,
|
|
20
|
-
'react-icons/ci': /"(Ci[A-Z0-9]\w*)"/gm,
|
|
21
|
-
'react-icons/di': /"(Di[A-Z0-9]\w*)"/gm,
|
|
22
|
-
'react-icons/fa': /"(Fa[A-Z0-9]\w*)"/gm,
|
|
23
|
-
'react-icons/fc': /"(Fc[A-Z0-9]\w*)"/gm,
|
|
24
|
-
'react-icons/fi': /"(Fi[A-Z0-9]\w*)"/gm,
|
|
25
|
-
'react-icons/gi': /"(Gi[A-Z0-9]\w*)"/gm,
|
|
26
|
-
'react-icons/go': /"(Go[A-Z0-9]\w*)"/gm,
|
|
27
|
-
'react-icons/gr': /"(Gr[A-Z0-9]\w*)"/gm,
|
|
28
|
-
'react-icons/hi': /"(Hi[A-Z0-9]\w*)"/gm,
|
|
29
|
-
'react-icons/im': /"(Im[A-Z0-9]\w*)"/gm,
|
|
30
|
-
'react-icons/io': /"(IoIos[A-Z0-9]\w*)"/gm,
|
|
31
|
-
'react-icons/io5': /"(Io[A-Z0-9]\w*)"/gm,
|
|
32
|
-
'react-icons/lu': /"(Lu[A-Z0-9]\w*)"/gm,
|
|
33
|
-
'react-icons/md': /"(Md[A-Z0-9]\w*)"/gm,
|
|
34
|
-
'react-icons/pi': /"(Pi[A-Z0-9]\w*)"/gm,
|
|
35
|
-
'react-icons/ri': /"(Ri[A-Z0-9]\w*)"/gm,
|
|
36
|
-
'react-icons/rx': /"(Rx[A-Z0-9]\w*)"/gm,
|
|
37
|
-
'react-icons/si': /"(Si[A-Z0-9]\w*)"/gm,
|
|
38
|
-
'react-icons/sl': /"(Sl[A-Z0-9]\w*)"/gm,
|
|
39
|
-
'react-icons/tb': /"(Tb[A-Z0-9]\w*)"/gm,
|
|
40
|
-
'react-icons/tfi': /"(Tfi[A-Z0-9]\w*)"/gm,
|
|
41
|
-
'react-icons/ti': /"(Ti[A-Z0-9]\w*)"/gm,
|
|
42
|
-
'react-icons/vsc': /"(Vsc[A-Z0-9]\w*)"/gm,
|
|
43
|
-
'react-icons/wi': /"(Wi[A-Z0-9]\w*)"/gm
|
|
44
|
-
};
|
|
15
|
+
*/ import iconPackages from './iconPackages.js';
|
|
45
16
|
function getConfigIcons({ components, icons, regex }) {
|
|
46
17
|
[
|
|
47
18
|
...JSON.stringify(components.global || {}).matchAll(regex)
|
|
@@ -65,7 +36,6 @@ function getBlockDefaultIcons({ blocks, context, icons, regex }) {
|
|
|
65
36
|
function buildIconImports({ blocks, components, context, defaults = {} }) {
|
|
66
37
|
const iconImports = [];
|
|
67
38
|
Object.entries(iconPackages).forEach(([iconPackage, regex])=>{
|
|
68
|
-
defaults;
|
|
69
39
|
const icons = new Set(defaults[iconPackage]);
|
|
70
40
|
getConfigIcons({
|
|
71
41
|
components,
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
*/ // Icon name regex patterns keyed by react-icons package.
|
|
16
|
+
// Note: 'react-icons/io' and 'react-icons/io5' overlap — IoIos* icons match
|
|
17
|
+
// both packages. This is intentional and matches the existing buildIconImports
|
|
18
|
+
// behavior where both packages receive the IoIos* icons.
|
|
19
|
+
const iconPackages = {
|
|
20
|
+
'react-icons/ai': /"(Ai[A-Z0-9]\w*)"/gm,
|
|
21
|
+
'react-icons/bi': /"(Bi[A-Z0-9]\w*)"/gm,
|
|
22
|
+
'react-icons/bs': /"(Bs[A-Z0-9]\w*)"/gm,
|
|
23
|
+
'react-icons/cg': /"(Cg[A-Z0-9]\w*)"/gm,
|
|
24
|
+
'react-icons/ci': /"(Ci[A-Z0-9]\w*)"/gm,
|
|
25
|
+
'react-icons/di': /"(Di[A-Z0-9]\w*)"/gm,
|
|
26
|
+
'react-icons/fa': /"(Fa[A-Z0-9]\w*)"/gm,
|
|
27
|
+
'react-icons/fc': /"(Fc[A-Z0-9]\w*)"/gm,
|
|
28
|
+
'react-icons/fi': /"(Fi[A-Z0-9]\w*)"/gm,
|
|
29
|
+
'react-icons/gi': /"(Gi[A-Z0-9]\w*)"/gm,
|
|
30
|
+
'react-icons/go': /"(Go[A-Z0-9]\w*)"/gm,
|
|
31
|
+
'react-icons/gr': /"(Gr[A-Z0-9]\w*)"/gm,
|
|
32
|
+
'react-icons/hi': /"(Hi[A-Z0-9]\w*)"/gm,
|
|
33
|
+
'react-icons/im': /"(Im[A-Z0-9]\w*)"/gm,
|
|
34
|
+
'react-icons/io': /"(IoIos[A-Z0-9]\w*)"/gm,
|
|
35
|
+
'react-icons/io5': /"(Io[A-Z0-9]\w*)"/gm,
|
|
36
|
+
'react-icons/lu': /"(Lu[A-Z0-9]\w*)"/gm,
|
|
37
|
+
'react-icons/md': /"(Md[A-Z0-9]\w*)"/gm,
|
|
38
|
+
'react-icons/pi': /"(Pi[A-Z0-9]\w*)"/gm,
|
|
39
|
+
'react-icons/ri': /"(Ri[A-Z0-9]\w*)"/gm,
|
|
40
|
+
'react-icons/rx': /"(Rx[A-Z0-9]\w*)"/gm,
|
|
41
|
+
'react-icons/si': /"(Si[A-Z0-9]\w*)"/gm,
|
|
42
|
+
'react-icons/sl': /"(Sl[A-Z0-9]\w*)"/gm,
|
|
43
|
+
'react-icons/tb': /"(Tb[A-Z0-9]\w*)"/gm,
|
|
44
|
+
'react-icons/tfi': /"(Tfi[A-Z0-9]\w*)"/gm,
|
|
45
|
+
'react-icons/ti': /"(Ti[A-Z0-9]\w*)"/gm,
|
|
46
|
+
'react-icons/vsc': /"(Vsc[A-Z0-9]\w*)"/gm,
|
|
47
|
+
'react-icons/wi': /"(Wi[A-Z0-9]\w*)"/gm
|
|
48
|
+
};
|
|
49
|
+
export default iconPackages;
|
|
@@ -12,33 +12,49 @@
|
|
|
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 evaluateBuildOperators from './evaluateBuildOperators.js';
|
|
18
19
|
import evaluateStaticOperators from './evaluateStaticOperators.js';
|
|
19
|
-
import
|
|
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
|
+
});
|
|
20
30
|
async function buildRefs({ context, shallowOptions }) {
|
|
31
|
+
context.unresolvedRefVars = context.unresolvedRefVars ?? {};
|
|
21
32
|
const refDef = makeRefDefinition('lowdefy.yaml', null, context.refMap);
|
|
22
|
-
|
|
23
|
-
context,
|
|
24
|
-
refDef,
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
27
50
|
});
|
|
28
|
-
|
|
29
|
-
// Pass typeNames so page objects act as type boundaries, preventing ~dyn markers
|
|
30
|
-
// from ~shallow content (blocks, events) from bubbling up and blocking evaluation
|
|
31
|
-
// of _build.array at the pages level.
|
|
32
|
-
const typeNames = collectTypeNames({
|
|
33
|
-
typesMap: context.typesMap
|
|
34
|
-
});
|
|
35
|
-
components = await evaluateBuildOperators({
|
|
51
|
+
const content = await getRefContent({
|
|
36
52
|
context,
|
|
37
|
-
input: components,
|
|
38
53
|
refDef,
|
|
39
|
-
|
|
54
|
+
referencedFrom: null
|
|
40
55
|
});
|
|
41
|
-
|
|
56
|
+
let components = await resolve(content, ctx);
|
|
57
|
+
// Evaluate static operators (_sum, _if, etc.) that don't depend on runtime data
|
|
42
58
|
components = evaluateStaticOperators({
|
|
43
59
|
context,
|
|
44
60
|
input: components,
|
|
@@ -12,13 +12,12 @@
|
|
|
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 { evaluateOperators } from '@lowdefy/operators';
|
|
16
16
|
import operators from '@lowdefy/operators-js/operators/build';
|
|
17
17
|
import collectDynamicIdentifiers from '../collectDynamicIdentifiers.js';
|
|
18
18
|
import collectTypeNames from '../collectTypeNames.js';
|
|
19
19
|
import validateOperatorsDynamic from '../validateOperatorsDynamic.js';
|
|
20
20
|
import collectExceptions from '../../utils/collectExceptions.js';
|
|
21
|
-
// Validate and collect dynamic identifiers once at module load
|
|
22
21
|
validateOperatorsDynamic({
|
|
23
22
|
operators
|
|
24
23
|
});
|
|
@@ -26,20 +25,17 @@ const dynamicIdentifiers = collectDynamicIdentifiers({
|
|
|
26
25
|
operators
|
|
27
26
|
});
|
|
28
27
|
function evaluateStaticOperators({ context, input, refDef }) {
|
|
29
|
-
// Collect type names from context.typesMap for type boundary detection
|
|
30
28
|
const typeNames = collectTypeNames({
|
|
31
29
|
typesMap: context.typesMap
|
|
32
30
|
});
|
|
33
|
-
const
|
|
34
|
-
|
|
31
|
+
const { output, errors } = evaluateOperators({
|
|
32
|
+
input,
|
|
35
33
|
operators,
|
|
34
|
+
operatorPrefix: '_',
|
|
35
|
+
env: process.env,
|
|
36
36
|
dynamicIdentifiers,
|
|
37
37
|
typeNames
|
|
38
38
|
});
|
|
39
|
-
const { output, errors } = operatorsParser.parse({
|
|
40
|
-
input,
|
|
41
|
-
operatorPrefix: '_'
|
|
42
|
-
});
|
|
43
39
|
if (errors.length > 0) {
|
|
44
40
|
errors.forEach((error)=>{
|
|
45
41
|
// Resolve source file path for error location.
|
|
@@ -15,8 +15,10 @@
|
|
|
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, lineNumber) {
|
|
19
|
-
|
|
18
|
+
function makeRefDefinition(refDefinition, parent, refMap, lineNumber, walkerPath) {
|
|
19
|
+
// Use walker tree path when available for deterministic IDs under parallel
|
|
20
|
+
// resolution. Falls back to counter for root ref and JIT-created refs.
|
|
21
|
+
const id = walkerPath != null ? walkerPath : makeId.next();
|
|
20
22
|
const refDef = {
|
|
21
23
|
parent,
|
|
22
24
|
lineNumber
|
|
@@ -92,7 +92,8 @@ function parseRefContent({ content, refDef }) {
|
|
|
92
92
|
const { path, vars } = refDef;
|
|
93
93
|
if (type.isString(path)) {
|
|
94
94
|
let ext = getFileExtension(path);
|
|
95
|
-
|
|
95
|
+
const isNjk = ext === 'njk';
|
|
96
|
+
if (isNjk) {
|
|
96
97
|
try {
|
|
97
98
|
content = parseNunjucks(content, vars);
|
|
98
99
|
} catch (error) {
|
|
@@ -107,6 +108,12 @@ function parseRefContent({ content, refDef }) {
|
|
|
107
108
|
try {
|
|
108
109
|
content = parseYamlWithLineNumbers(content);
|
|
109
110
|
} catch (error) {
|
|
111
|
+
if (isNjk) {
|
|
112
|
+
throw new ConfigError(`Nunjucks template "${path}" produced invalid YAML.`, {
|
|
113
|
+
cause: error,
|
|
114
|
+
filePath: path
|
|
115
|
+
});
|
|
116
|
+
}
|
|
110
117
|
const lineMatch = error.message.match(/at line (\d+)/);
|
|
111
118
|
throw new ConfigError(`YAML parse error in "${path}".`, {
|
|
112
119
|
cause: error,
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import { get, type } from '@lowdefy/helpers';
|
|
16
|
+
import { ConfigError } from '@lowdefy/errors';
|
|
17
|
+
import { evaluateOperators } from '@lowdefy/operators';
|
|
18
|
+
import makeRefDefinition from './makeRefDefinition.js';
|
|
19
|
+
import getRefContent from './getRefContent.js';
|
|
20
|
+
import runTransformer from './runTransformer.js';
|
|
21
|
+
import getKey from './getKey.js';
|
|
22
|
+
import setNonEnumerableProperty from '../../utils/setNonEnumerableProperty.js';
|
|
23
|
+
import collectExceptions from '../../utils/collectExceptions.js';
|
|
24
|
+
let WalkContext = class WalkContext {
|
|
25
|
+
child(segment) {
|
|
26
|
+
return new WalkContext({
|
|
27
|
+
buildContext: this.buildContext,
|
|
28
|
+
refId: this.refId,
|
|
29
|
+
sourceRefId: this.sourceRefId,
|
|
30
|
+
vars: this.vars,
|
|
31
|
+
path: this.path ? `${this.path}.${segment}` : segment,
|
|
32
|
+
currentFile: this.currentFile,
|
|
33
|
+
refChain: this.refChain,
|
|
34
|
+
operators: this.operators,
|
|
35
|
+
env: this.env,
|
|
36
|
+
dynamicIdentifiers: this.dynamicIdentifiers,
|
|
37
|
+
shouldStop: this.shouldStop
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
forRef({ refId, vars, filePath }) {
|
|
41
|
+
const newChain = new Set(this.refChain);
|
|
42
|
+
if (filePath) {
|
|
43
|
+
newChain.add(filePath);
|
|
44
|
+
}
|
|
45
|
+
return new WalkContext({
|
|
46
|
+
buildContext: this.buildContext,
|
|
47
|
+
refId,
|
|
48
|
+
sourceRefId: this.refId,
|
|
49
|
+
vars: vars ?? {},
|
|
50
|
+
path: this.path,
|
|
51
|
+
currentFile: filePath ?? this.currentFile,
|
|
52
|
+
refChain: newChain,
|
|
53
|
+
operators: this.operators,
|
|
54
|
+
env: this.env,
|
|
55
|
+
dynamicIdentifiers: this.dynamicIdentifiers,
|
|
56
|
+
shouldStop: this.shouldStop
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
collectError(error) {
|
|
60
|
+
collectExceptions(this.buildContext, error);
|
|
61
|
+
}
|
|
62
|
+
get refMap() {
|
|
63
|
+
return this.buildContext.refMap;
|
|
64
|
+
}
|
|
65
|
+
get unresolvedRefVars() {
|
|
66
|
+
return this.buildContext.unresolvedRefVars;
|
|
67
|
+
}
|
|
68
|
+
constructor({ buildContext, refId, sourceRefId, vars, path, currentFile, refChain, operators, env, dynamicIdentifiers, shouldStop }){
|
|
69
|
+
this.buildContext = buildContext;
|
|
70
|
+
this.refId = refId;
|
|
71
|
+
this.sourceRefId = sourceRefId;
|
|
72
|
+
this.vars = vars;
|
|
73
|
+
this.path = path;
|
|
74
|
+
this.currentFile = currentFile;
|
|
75
|
+
this.refChain = refChain;
|
|
76
|
+
this.operators = operators;
|
|
77
|
+
this.env = env;
|
|
78
|
+
this.dynamicIdentifiers = dynamicIdentifiers;
|
|
79
|
+
this.shouldStop = shouldStop;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
// Detect _build.* operator objects: single non-tilde key starting with '_build.'
|
|
83
|
+
function isBuildOperator(node) {
|
|
84
|
+
const keys = Object.keys(node);
|
|
85
|
+
const nonTildeKeys = keys.filter((k)=>!k.startsWith('~'));
|
|
86
|
+
return nonTildeKeys.length === 1 && nonTildeKeys[0].startsWith('_build.');
|
|
87
|
+
}
|
|
88
|
+
// Set ~r as non-enumerable if not already present
|
|
89
|
+
function tagRef(node, refId) {
|
|
90
|
+
if (type.isObject(node) || type.isArray(node)) {
|
|
91
|
+
if (node['~r'] === undefined) {
|
|
92
|
+
setNonEnumerableProperty(node, '~r', refId);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Recursively set ~r on all objects/arrays that don't already have it
|
|
97
|
+
function tagRefDeep(node, refId) {
|
|
98
|
+
if (!type.isObject(node) && !type.isArray(node)) return;
|
|
99
|
+
if (node['~r'] !== undefined) return;
|
|
100
|
+
setNonEnumerableProperty(node, '~r', refId);
|
|
101
|
+
if (type.isArray(node)) {
|
|
102
|
+
for(let i = 0; i < node.length; i++){
|
|
103
|
+
tagRefDeep(node[i], refId);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
for (const key of Object.keys(node)){
|
|
107
|
+
tagRefDeep(node[key], refId);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Deep clone preserving ~r, ~l, ~k non-enumerable markers.
|
|
112
|
+
// Used before resolving ref def path/vars to prevent mutation of stored originals.
|
|
113
|
+
function cloneForResolve(value) {
|
|
114
|
+
if (!type.isObject(value) && !type.isArray(value)) return value;
|
|
115
|
+
if (type.isArray(value)) {
|
|
116
|
+
const clone = value.map((item)=>cloneForResolve(item));
|
|
117
|
+
if (value['~r'] !== undefined) setNonEnumerableProperty(clone, '~r', value['~r']);
|
|
118
|
+
if (value['~l'] !== undefined) setNonEnumerableProperty(clone, '~l', value['~l']);
|
|
119
|
+
if (value['~k'] !== undefined) setNonEnumerableProperty(clone, '~k', value['~k']);
|
|
120
|
+
if (value['~arr'] !== undefined) setNonEnumerableProperty(clone, '~arr', value['~arr']);
|
|
121
|
+
return clone;
|
|
122
|
+
}
|
|
123
|
+
const clone = {};
|
|
124
|
+
for (const key of Object.keys(value)){
|
|
125
|
+
clone[key] = cloneForResolve(value[key]);
|
|
126
|
+
}
|
|
127
|
+
if (value['~r'] !== undefined) setNonEnumerableProperty(clone, '~r', value['~r']);
|
|
128
|
+
if (value['~l'] !== undefined) setNonEnumerableProperty(clone, '~l', value['~l']);
|
|
129
|
+
if (value['~k'] !== undefined) setNonEnumerableProperty(clone, '~k', value['~k']);
|
|
130
|
+
return clone;
|
|
131
|
+
}
|
|
132
|
+
// Deep clone a var value, preserving markers and setting ~r provenance.
|
|
133
|
+
// When sourceRefId is null, preserves the template's existing ~r markers.
|
|
134
|
+
function cloneVarValue(value, sourceRefId) {
|
|
135
|
+
if (!type.isObject(value) && !type.isArray(value)) return value;
|
|
136
|
+
return cloneDeepWithProvenance(value, sourceRefId);
|
|
137
|
+
}
|
|
138
|
+
function cloneDeepWithProvenance(node, sourceRefId) {
|
|
139
|
+
if (!type.isObject(node) && !type.isArray(node)) return node;
|
|
140
|
+
if (type.isArray(node)) {
|
|
141
|
+
const clone = node.map((item)=>cloneDeepWithProvenance(item, sourceRefId));
|
|
142
|
+
if (node['~r'] !== undefined) {
|
|
143
|
+
setNonEnumerableProperty(clone, '~r', node['~r']);
|
|
144
|
+
} else if (sourceRefId) {
|
|
145
|
+
setNonEnumerableProperty(clone, '~r', sourceRefId);
|
|
146
|
+
}
|
|
147
|
+
if (node['~l'] !== undefined) setNonEnumerableProperty(clone, '~l', node['~l']);
|
|
148
|
+
if (node['~k'] !== undefined) setNonEnumerableProperty(clone, '~k', node['~k']);
|
|
149
|
+
if (node['~arr'] !== undefined) setNonEnumerableProperty(clone, '~arr', node['~arr']);
|
|
150
|
+
return clone;
|
|
151
|
+
}
|
|
152
|
+
const clone = {};
|
|
153
|
+
for (const key of Object.keys(node)){
|
|
154
|
+
clone[key] = cloneDeepWithProvenance(node[key], sourceRefId);
|
|
155
|
+
}
|
|
156
|
+
if (node['~r'] !== undefined) {
|
|
157
|
+
setNonEnumerableProperty(clone, '~r', node['~r']);
|
|
158
|
+
} else if (sourceRefId) {
|
|
159
|
+
setNonEnumerableProperty(clone, '~r', sourceRefId);
|
|
160
|
+
}
|
|
161
|
+
if (node['~l'] !== undefined) setNonEnumerableProperty(clone, '~l', node['~l']);
|
|
162
|
+
if (node['~k'] !== undefined) setNonEnumerableProperty(clone, '~k', node['~k']);
|
|
163
|
+
return clone;
|
|
164
|
+
}
|
|
165
|
+
// Evaluate a _build.* operator using evaluateOperators
|
|
166
|
+
function evaluateBuildOperator(node, ctx) {
|
|
167
|
+
const { output, errors } = evaluateOperators({
|
|
168
|
+
input: node,
|
|
169
|
+
operators: ctx.operators,
|
|
170
|
+
operatorPrefix: '_build.',
|
|
171
|
+
env: ctx.env,
|
|
172
|
+
dynamicIdentifiers: ctx.dynamicIdentifiers
|
|
173
|
+
});
|
|
174
|
+
if (errors.length > 0) {
|
|
175
|
+
errors.forEach((error)=>{
|
|
176
|
+
error.filePath = error.refId ? ctx.refMap[error.refId]?.path : ctx.currentFile;
|
|
177
|
+
ctx.collectError(error);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return output;
|
|
181
|
+
}
|
|
182
|
+
// Resolve a _var node
|
|
183
|
+
function resolveVar(node, ctx) {
|
|
184
|
+
const varDef = node._var;
|
|
185
|
+
// String form: { _var: "key" }
|
|
186
|
+
if (type.isString(varDef)) {
|
|
187
|
+
const value = get(ctx.vars, varDef, {
|
|
188
|
+
default: null
|
|
189
|
+
});
|
|
190
|
+
return cloneVarValue(value, ctx.sourceRefId);
|
|
191
|
+
}
|
|
192
|
+
// Object form: { _var: { key, default } }
|
|
193
|
+
if (type.isObject(varDef) && type.isString(varDef.key)) {
|
|
194
|
+
const varFromParent = get(ctx.vars, varDef.key);
|
|
195
|
+
// Var provided (even if null) → use parent's sourceRefId for location
|
|
196
|
+
if (!type.isUndefined(varFromParent)) {
|
|
197
|
+
return cloneVarValue(varFromParent, ctx.sourceRefId);
|
|
198
|
+
}
|
|
199
|
+
// Not provided → use default, preserve template's ~r
|
|
200
|
+
const defaultValue = type.isNone(varDef.default) ? null : varDef.default;
|
|
201
|
+
return cloneVarValue(defaultValue, null);
|
|
202
|
+
}
|
|
203
|
+
throw new ConfigError('_var operator takes a string or object with "key" field as arguments.', {
|
|
204
|
+
filePath: ctx.currentFile
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
// Resolve a _ref node (12-step ref handling)
|
|
208
|
+
async function resolveRef(node, ctx) {
|
|
209
|
+
// 1. Create ref definition
|
|
210
|
+
const lineNumber = node['~l'];
|
|
211
|
+
const refDef = makeRefDefinition(node._ref, ctx.refId, ctx.refMap, lineNumber, ctx.path);
|
|
212
|
+
// 2. Store unresolved vars before resolution mutates them, and clone so
|
|
213
|
+
// resolution operates on a copy (preserving original.vars for resolver refs).
|
|
214
|
+
const varKeys = Object.keys(refDef.vars);
|
|
215
|
+
if (varKeys.length > 0) {
|
|
216
|
+
ctx.unresolvedRefVars[refDef.id] = refDef.vars;
|
|
217
|
+
refDef.vars = cloneForResolve(refDef.vars);
|
|
218
|
+
}
|
|
219
|
+
// 3. Resolve dynamic path/vars/key
|
|
220
|
+
if (type.isObject(refDef.path)) {
|
|
221
|
+
refDef.path = await resolve(cloneForResolve(refDef.path), ctx);
|
|
222
|
+
}
|
|
223
|
+
await Promise.all(varKeys.map(async (varKey)=>{
|
|
224
|
+
if (type.isObject(refDef.vars[varKey]) || type.isArray(refDef.vars[varKey])) {
|
|
225
|
+
refDef.vars[varKey] = await resolve(refDef.vars[varKey], ctx);
|
|
226
|
+
}
|
|
227
|
+
}));
|
|
228
|
+
if (type.isObject(refDef.key)) {
|
|
229
|
+
refDef.key = await resolve(cloneForResolve(refDef.key), ctx);
|
|
230
|
+
}
|
|
231
|
+
// 4. Update refMap with resolved path; store original for resolver refs
|
|
232
|
+
ctx.refMap[refDef.id].path = refDef.path;
|
|
233
|
+
if (!refDef.path) {
|
|
234
|
+
ctx.refMap[refDef.id].original = refDef.original;
|
|
235
|
+
}
|
|
236
|
+
// 5. Circular detection
|
|
237
|
+
if (refDef.path && ctx.refChain.has(refDef.path)) {
|
|
238
|
+
const chainDisplay = [
|
|
239
|
+
...ctx.refChain,
|
|
240
|
+
refDef.path
|
|
241
|
+
].join('\n -> ');
|
|
242
|
+
throw new ConfigError(`Circular reference detected. File "${refDef.path}" references itself through:\n -> ${chainDisplay}`, {
|
|
243
|
+
filePath: ctx.currentFile,
|
|
244
|
+
lineNumber: ctx.currentFile ? lineNumber : null
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
// Steps 6-12: File operations that can fail independently per ref.
|
|
248
|
+
// Errors are collected so the walker can continue processing sibling refs,
|
|
249
|
+
// allowing multiple errors to be reported at once.
|
|
250
|
+
try {
|
|
251
|
+
// 6. Load content
|
|
252
|
+
let content = await getRefContent({
|
|
253
|
+
context: ctx.buildContext,
|
|
254
|
+
refDef,
|
|
255
|
+
referencedFrom: ctx.currentFile
|
|
256
|
+
});
|
|
257
|
+
// 7. Create child context for the ref file
|
|
258
|
+
const childCtx = ctx.forRef({
|
|
259
|
+
refId: refDef.id,
|
|
260
|
+
vars: refDef.vars,
|
|
261
|
+
filePath: refDef.path
|
|
262
|
+
});
|
|
263
|
+
// 8. Walk the content
|
|
264
|
+
content = await resolve(content, childCtx);
|
|
265
|
+
// 9. Run transformer
|
|
266
|
+
content = await runTransformer({
|
|
267
|
+
context: ctx.buildContext,
|
|
268
|
+
input: content,
|
|
269
|
+
refDef
|
|
270
|
+
});
|
|
271
|
+
// 10. Extract key
|
|
272
|
+
content = getKey({
|
|
273
|
+
input: content,
|
|
274
|
+
refDef
|
|
275
|
+
});
|
|
276
|
+
// 11. Tag all nodes with ~r for provenance
|
|
277
|
+
tagRefDeep(content, refDef.id);
|
|
278
|
+
// 12. Propagate ~ignoreBuildChecks
|
|
279
|
+
if (refDef.ignoreBuildChecks !== undefined) {
|
|
280
|
+
if (type.isObject(content)) {
|
|
281
|
+
content['~ignoreBuildChecks'] = refDef.ignoreBuildChecks;
|
|
282
|
+
} else if (type.isArray(content)) {
|
|
283
|
+
content.forEach((item)=>{
|
|
284
|
+
if (type.isObject(item)) {
|
|
285
|
+
item['~ignoreBuildChecks'] = refDef.ignoreBuildChecks;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return content;
|
|
291
|
+
} catch (error) {
|
|
292
|
+
if (error instanceof ConfigError) {
|
|
293
|
+
ctx.collectError(error);
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// Core walk function — single-pass async tree walker
|
|
300
|
+
async function resolve(node, ctx) {
|
|
301
|
+
// 1. Primitives pass through
|
|
302
|
+
if (!type.isObject(node) && !type.isArray(node)) return node;
|
|
303
|
+
// 2. Object with _ref
|
|
304
|
+
if (type.isObject(node) && !type.isUndefined(node._ref)) {
|
|
305
|
+
return resolveRef(node, ctx);
|
|
306
|
+
}
|
|
307
|
+
// 4. Object with _var — resolve, then re-walk the result so any
|
|
308
|
+
// _ref or _build.* operators inside the default value get processed.
|
|
309
|
+
if (type.isObject(node) && !type.isUndefined(node._var)) {
|
|
310
|
+
try {
|
|
311
|
+
const varResult = resolveVar(node, ctx);
|
|
312
|
+
return await resolve(varResult, ctx);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
if (error instanceof ConfigError) {
|
|
315
|
+
ctx.collectError(error);
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// 5. Array — walk children in parallel
|
|
322
|
+
if (type.isArray(node)) {
|
|
323
|
+
await Promise.all(node.map(async (item, i)=>{
|
|
324
|
+
node[i] = await resolve(item, ctx.child(String(i)));
|
|
325
|
+
}));
|
|
326
|
+
return node;
|
|
327
|
+
}
|
|
328
|
+
// 6. Object — walk children in parallel
|
|
329
|
+
const keys = Object.keys(node);
|
|
330
|
+
await Promise.all(keys.map(async (key)=>{
|
|
331
|
+
if (ctx.shouldStop) {
|
|
332
|
+
const childPath = ctx.path ? `${ctx.path}.${key}` : key;
|
|
333
|
+
if (ctx.shouldStop(childPath, ctx.refId)) {
|
|
334
|
+
delete node[key];
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
node[key] = await resolve(node[key], ctx.child(key));
|
|
339
|
+
}));
|
|
340
|
+
// Check if this is a _build.* operator
|
|
341
|
+
if (isBuildOperator(node)) {
|
|
342
|
+
const result = evaluateBuildOperator(node, ctx);
|
|
343
|
+
tagRefDeep(result, ctx.refId);
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
return node;
|
|
347
|
+
}
|
|
348
|
+
export { resolve, WalkContext, cloneForResolve, tagRefDeep };
|