@lowdefy/build 4.5.2 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/dist/build/addDefaultPages/404.js +1 -1
  2. package/dist/build/addDefaultPages/addDefaultPages.js +10 -4
  3. package/dist/build/addKeys.js +85 -29
  4. package/dist/build/buildApi/buildApi.js +27 -8
  5. package/dist/build/buildApi/buildEndpoint.js +7 -1
  6. package/dist/build/buildApi/buildRoutine/buildControl.js +1 -1
  7. package/dist/build/buildApi/buildRoutine/buildRoutine.js +1 -1
  8. package/dist/build/buildApi/buildRoutine/buildStep.js +1 -1
  9. package/dist/build/buildApi/buildRoutine/controlTypes.js +34 -11
  10. package/dist/build/buildApi/buildRoutine/countControl.js +1 -1
  11. package/dist/build/buildApi/buildRoutine/countStepTypes.js +2 -2
  12. package/dist/build/buildApi/buildRoutine/setStepId.js +1 -1
  13. package/dist/build/buildApi/buildRoutine/validateStep.js +30 -9
  14. package/dist/build/buildApi/validateEndpoint.js +36 -7
  15. package/dist/build/buildApi/validateStepReferences.js +65 -0
  16. package/dist/build/buildApp.js +1 -1
  17. package/dist/build/buildAuth/buildApiAuth.js +7 -3
  18. package/dist/build/buildAuth/buildAuth.js +7 -4
  19. package/dist/build/buildAuth/buildAuthPlugins.js +42 -13
  20. package/dist/build/buildAuth/buildPageAuth.js +14 -3
  21. package/dist/build/buildAuth/getApiRoles.js +1 -1
  22. package/dist/build/buildAuth/getPageRoles.js +1 -1
  23. package/dist/build/buildAuth/getProtectedApi.js +1 -1
  24. package/dist/build/buildAuth/getProtectedPages.js +1 -1
  25. package/dist/build/buildAuth/validateAuthConfig.js +39 -5
  26. package/dist/build/buildAuth/validateMutualExclusivity.js +13 -5
  27. package/dist/build/buildConnections.js +23 -24
  28. package/dist/build/buildImports/buildIconImports.js +1 -1
  29. package/dist/build/buildImports/buildImports.js +1 -1
  30. package/dist/build/buildImports/buildImportsDev.js +1 -1
  31. package/dist/build/buildImports/buildImportsProd.js +1 -1
  32. package/dist/build/buildImports/buildStyleImports.js +1 -1
  33. package/dist/build/buildImports/defaultIconsDev.js +1 -1
  34. package/dist/build/buildImports/defaultIconsProd.js +1 -1
  35. package/dist/build/buildJs/generateJsFile.js +1 -1
  36. package/dist/build/buildJs/jsMapParser.js +1 -1
  37. package/dist/build/buildJs/writeJs.js +1 -1
  38. package/dist/build/buildLogger.js +41 -0
  39. package/dist/build/buildMenu.js +36 -12
  40. package/dist/build/buildPages/buildBlock/buildBlock.js +1 -1
  41. package/dist/build/buildPages/buildBlock/buildEvents.js +79 -9
  42. package/dist/build/buildPages/buildBlock/buildRequests.js +38 -8
  43. package/dist/build/buildPages/buildBlock/buildSubBlocks.js +6 -2
  44. package/dist/build/buildPages/buildBlock/countBlockOperators.js +1 -1
  45. package/dist/build/buildPages/buildBlock/countBlockTypes.js +2 -2
  46. package/dist/build/buildPages/buildBlock/moveSkeletonBlocksToArea.js +6 -2
  47. package/dist/build/buildPages/buildBlock/moveSubBlocksToArea.js +6 -2
  48. package/dist/build/buildPages/buildBlock/setBlockId.js +1 -1
  49. package/dist/build/buildPages/buildBlock/validateBlock.js +25 -7
  50. package/dist/build/buildPages/buildPage.js +35 -6
  51. package/dist/build/buildPages/validateLinkReferences.js +33 -0
  52. package/dist/build/buildPages/validatePayloadReferences.js +59 -0
  53. package/dist/build/buildPages/validateRequestReferences.js +33 -0
  54. package/dist/build/buildPages/validateServerStateReferences.js +41 -0
  55. package/dist/build/buildPages/validateStateReferences.js +79 -0
  56. package/dist/build/buildRefs/buildRefs.js +20 -3
  57. package/dist/build/buildRefs/createRefReviver.js +28 -0
  58. package/dist/build/buildRefs/evaluateBuildOperators.js +26 -7
  59. package/dist/build/buildRefs/evaluateStaticOperators.js +56 -0
  60. package/dist/build/buildRefs/getConfigFile.js +25 -6
  61. package/dist/build/buildRefs/getKey.js +1 -1
  62. package/dist/build/buildRefs/getRefContent.js +1 -1
  63. package/dist/build/buildRefs/getRefPath.js +1 -1
  64. package/dist/build/buildRefs/getRefsFromFile.js +9 -4
  65. package/dist/build/buildRefs/getUserJavascriptFunction.js +18 -4
  66. package/dist/build/buildRefs/makeRefDefinition.js +10 -5
  67. package/dist/build/buildRefs/parseNunjucks.js +1 -1
  68. package/dist/build/buildRefs/parseRefContent.js +100 -6
  69. package/dist/build/buildRefs/populateRefs.js +75 -13
  70. package/dist/build/buildRefs/recursiveBuild.js +66 -18
  71. package/dist/build/buildRefs/runRefResolver.js +11 -3
  72. package/dist/build/buildRefs/runTransformer.js +7 -3
  73. package/dist/build/buildTypes.js +22 -7
  74. package/dist/build/cleanBuildDirectory.js +1 -1
  75. package/dist/build/collectDynamicIdentifiers.js +35 -0
  76. package/dist/build/collectTypeNames.js +36 -0
  77. package/dist/build/copyPublicFolder.js +1 -1
  78. package/dist/build/{buildJs → full}/buildJs.js +3 -3
  79. package/dist/build/full/buildPages.js +87 -0
  80. package/dist/build/{buildPages → full}/buildTestPage.js +15 -3
  81. package/dist/build/{updateServerPackageJson.js → full/updateServerPackageJson.js} +1 -1
  82. package/dist/build/{writePages.js → full/writePages.js} +1 -1
  83. package/dist/build/{writeRequests.js → full/writeRequests.js} +11 -3
  84. package/dist/build/{writeTypes.js → full/writeTypes.js} +1 -1
  85. package/dist/build/jit/addInstalledTypes.js +51 -0
  86. package/dist/build/jit/buildJsShallow.js +41 -0
  87. package/dist/build/jit/buildPageJit.js +252 -0
  88. package/dist/build/jit/buildShallowPages.js +90 -0
  89. package/dist/build/jit/createPageRegistry.js +80 -0
  90. package/dist/build/jit/detectMissingPluginPackages.js +62 -0
  91. package/dist/build/jit/getRefPositions.js +38 -0
  92. package/dist/build/jit/isPageContentPath.js +24 -0
  93. package/dist/build/jit/pageContentKeys.js +26 -0
  94. package/dist/build/jit/shallowBuild.js +245 -0
  95. package/dist/build/jit/stripPageContent.js +23 -0
  96. package/dist/build/jit/updateServerPackageJsonJit.js +33 -0
  97. package/dist/build/jit/validatePageTypes.js +73 -0
  98. package/dist/build/jit/writePageJit.js +44 -0
  99. package/dist/build/jit/writePageRegistry.js +23 -0
  100. package/dist/build/jit/writeSourcelessPages.js +23 -0
  101. package/dist/build/testSchema.js +45 -7
  102. package/dist/build/validateConfig.js +2 -2
  103. package/dist/build/validateOperatorsDynamic.js +28 -0
  104. package/dist/build/writeApi.js +1 -1
  105. package/dist/build/writeApp.js +1 -1
  106. package/dist/build/writeAuth.js +1 -1
  107. package/dist/build/writeConfig.js +1 -1
  108. package/dist/build/writeConnections.js +1 -1
  109. package/dist/build/writeGlobal.js +1 -1
  110. package/dist/build/writeLogger.js +19 -0
  111. package/dist/build/writeMaps.js +1 -1
  112. package/dist/build/writeMenus.js +1 -1
  113. package/dist/build/writePluginImports/generateImportFile.js +1 -1
  114. package/dist/build/writePluginImports/writeActionImports.js +1 -1
  115. package/dist/build/writePluginImports/writeActionSchemaMap.js +42 -0
  116. package/dist/build/writePluginImports/writeAuthImports.js +1 -1
  117. package/dist/build/writePluginImports/writeBlockImports.js +1 -1
  118. package/dist/build/writePluginImports/writeBlockSchemaMap.js +42 -0
  119. package/dist/build/writePluginImports/writeConnectionImports.js +1 -1
  120. package/dist/build/writePluginImports/writeIconImports.js +1 -1
  121. package/dist/build/writePluginImports/writeOperatorImports.js +1 -1
  122. package/dist/build/writePluginImports/writeOperatorSchemaMap.js +49 -0
  123. package/dist/build/writePluginImports/writePluginImports.js +16 -1
  124. package/dist/build/writePluginImports/writeStyleImports.js +1 -1
  125. package/dist/createContext.js +16 -4
  126. package/dist/defaultTypesMap.js +479 -457
  127. package/dist/index.js +188 -127
  128. package/dist/indexDev.js +18 -0
  129. package/dist/lowdefySchema.js +589 -0
  130. package/dist/scripts/generateDefaultTypes.js +2 -1
  131. package/dist/scripts/run.js +1 -1
  132. package/dist/{test → test-utils}/buildRefs/testBuildRefsAsyncFunction.js +1 -1
  133. package/dist/{test → test-utils}/buildRefs/testBuildRefsErrorResolver.js +1 -1
  134. package/dist/{test → test-utils}/buildRefs/testBuildRefsNullResolver.js +1 -1
  135. package/dist/{test → test-utils}/buildRefs/testBuildRefsParsingResolver.js +1 -1
  136. package/dist/{test → test-utils}/buildRefs/testBuildRefsResolver.js +1 -1
  137. package/dist/{test → test-utils}/buildRefs/testBuildRefsTransform.js +1 -1
  138. package/dist/{test → test-utils}/buildRefs/testBuildRefsTransformIdentity.js +1 -1
  139. package/dist/test-utils/buildRefs/testJitPageResolver.js +24 -0
  140. package/dist/test-utils/createTestLogger.js +36 -0
  141. package/dist/test-utils/parseTestYaml.js +126 -0
  142. package/dist/test-utils/runBuild.js +228 -0
  143. package/dist/test-utils/runBuildForSnapshots.js +704 -0
  144. package/dist/{test → test-utils}/testContext.js +12 -2
  145. package/dist/utils/collectExceptions.js +34 -0
  146. package/dist/utils/countOperators.js +31 -12
  147. package/dist/utils/createBuildHandleError.js +38 -0
  148. package/dist/utils/createCheckDuplicateId.js +15 -9
  149. package/dist/utils/createCounter.js +16 -3
  150. package/dist/utils/createHandleWarning.js +41 -0
  151. package/dist/utils/createPluginTypesMap.js +1 -1
  152. package/dist/utils/extractOperatorKey.js +36 -0
  153. package/dist/utils/findConfigKey.js +37 -0
  154. package/dist/utils/findSimilarString.js +53 -0
  155. package/dist/utils/logCollectedErrors.js +30 -0
  156. package/dist/utils/makeId.js +13 -8
  157. package/dist/utils/preserveMetaProperties.js +27 -0
  158. package/dist/utils/readConfigFile.js +1 -1
  159. package/dist/utils/setNonEnumerableProperty.js +23 -0
  160. package/dist/utils/traverseConfig.js +43 -0
  161. package/dist/utils/tryBuildStep.js +46 -0
  162. package/dist/utils/writeBuildArtifact.js +1 -1
  163. package/package.json +46 -41
  164. package/dist/build/buildPages/buildPages.js +0 -31
  165. 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-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -15,14 +15,31 @@
15
15
  */ import recursiveBuild from './recursiveBuild.js';
16
16
  import makeRefDefinition from './makeRefDefinition.js';
17
17
  import evaluateBuildOperators from './evaluateBuildOperators.js';
18
- async function buildRefs({ context }) {
18
+ import evaluateStaticOperators from './evaluateStaticOperators.js';
19
+ import collectTypeNames from '../collectTypeNames.js';
20
+ async function buildRefs({ context, shallowOptions }) {
19
21
  const refDef = makeRefDefinition('lowdefy.yaml', null, context.refMap);
20
22
  let components = await recursiveBuild({
21
23
  context,
22
24
  refDef,
23
- count: 0
25
+ count: 0,
26
+ shallowOptions
27
+ });
28
+ // First: evaluate _build.* operators (e.g., _build.env)
29
+ // Pass typeNames so page objects act as type boundaries, preventing ~dyn markers
30
+ // from ~shallow content (blocks, events) from bubbling up and blocking evaluation
31
+ // of _build.array at the pages level.
32
+ const typeNames = collectTypeNames({
33
+ typesMap: context.typesMap
24
34
  });
25
35
  components = await evaluateBuildOperators({
36
+ context,
37
+ input: components,
38
+ refDef,
39
+ typeNames
40
+ });
41
+ // Second: evaluate static operators (_sum, _if, etc.) that don't depend on runtime data
42
+ components = evaluateStaticOperators({
26
43
  context,
27
44
  input: components,
28
45
  refDef
@@ -0,0 +1,28 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { type } from '@lowdefy/helpers';
16
+ import setNonEnumerableProperty from '../../utils/setNonEnumerableProperty.js';
17
+ // Returns a serializer.copy reviver that sets ~r on all objects
18
+ // that don't already have it, preserving original file references.
19
+ function createRefReviver(refId) {
20
+ return (_, value)=>{
21
+ if (!type.isObject(value) && !type.isArray(value)) return value;
22
+ if (value['~r'] === undefined) {
23
+ setNonEnumerableProperty(value, '~r', refId);
24
+ }
25
+ return value;
26
+ };
27
+ }
28
+ export default createRefReviver;
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -14,20 +14,39 @@
14
14
  limitations under the License.
15
15
  */ import { BuildParser } from '@lowdefy/operators';
16
16
  import operators from '@lowdefy/operators-js/operators/build';
17
- async function evaluateBuildOperators({ context, input, refDef }) {
17
+ import collectDynamicIdentifiers from '../collectDynamicIdentifiers.js';
18
+ import validateOperatorsDynamic from '../validateOperatorsDynamic.js';
19
+ import collectExceptions from '../../utils/collectExceptions.js';
20
+ // Validate and collect dynamic identifiers once at module load
21
+ validateOperatorsDynamic({
22
+ operators
23
+ });
24
+ const dynamicIdentifiers = collectDynamicIdentifiers({
25
+ operators
26
+ });
27
+ function evaluateBuildOperators({ context, input, refDef, typeNames }) {
18
28
  const operatorsParser = new BuildParser({
19
29
  env: process.env,
20
- operators
30
+ operators,
31
+ dynamicIdentifiers,
32
+ ...typeNames ? {
33
+ typeNames
34
+ } : {}
21
35
  });
22
36
  const { output, errors } = operatorsParser.parse({
23
37
  input,
24
- location: refDef.path ?? refDef.resolver,
25
38
  operatorPrefix: '_build.'
26
39
  });
27
40
  if (errors.length > 0) {
28
- await context.logger.warn('Build operator errors.');
29
- const promises = errors.map((error)=>context.logger.warn(error.message));
30
- await promises;
41
+ errors.forEach((error)=>{
42
+ // Resolve source file path for error location.
43
+ // Two call sites: (1) recursiveBuild per-file where refDef.path is correct
44
+ // but ~r isn't set yet (createRefReviver runs after), and (2) buildRefs
45
+ // top-level where refDef is root lowdefy.yaml but ~r identifies the real
46
+ // source file via refMap. The fallback to refDef.path handles case (1).
47
+ error.filePath = error.refId ? context.refMap[error.refId]?.path : refDef.path;
48
+ collectExceptions(context, error);
49
+ });
31
50
  }
32
51
  return output;
33
52
  }
@@ -0,0 +1,56 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { BuildParser } from '@lowdefy/operators';
16
+ import operators from '@lowdefy/operators-js/operators/build';
17
+ import collectDynamicIdentifiers from '../collectDynamicIdentifiers.js';
18
+ import collectTypeNames from '../collectTypeNames.js';
19
+ import validateOperatorsDynamic from '../validateOperatorsDynamic.js';
20
+ import collectExceptions from '../../utils/collectExceptions.js';
21
+ // Validate and collect dynamic identifiers once at module load
22
+ validateOperatorsDynamic({
23
+ operators
24
+ });
25
+ const dynamicIdentifiers = collectDynamicIdentifiers({
26
+ operators
27
+ });
28
+ function evaluateStaticOperators({ context, input, refDef }) {
29
+ // Collect type names from context.typesMap for type boundary detection
30
+ const typeNames = collectTypeNames({
31
+ typesMap: context.typesMap
32
+ });
33
+ const operatorsParser = new BuildParser({
34
+ env: process.env,
35
+ operators,
36
+ dynamicIdentifiers,
37
+ typeNames
38
+ });
39
+ const { output, errors } = operatorsParser.parse({
40
+ input,
41
+ operatorPrefix: '_'
42
+ });
43
+ if (errors.length > 0) {
44
+ errors.forEach((error)=>{
45
+ // Resolve source file path for error location.
46
+ // Only called from buildRefs top-level where refDef is root lowdefy.yaml,
47
+ // but ~r on each operator object identifies the real source file via refMap.
48
+ // Falls back to refDef.path if ~r is missing (shouldn't happen at this stage
49
+ // since all objects have ~r after recursiveBuild completes).
50
+ error.filePath = error.refId ? context.refMap[error.refId]?.path : refDef.path;
51
+ collectExceptions(context, error);
52
+ });
53
+ }
54
+ return output;
55
+ }
56
+ export default evaluateStaticOperators;
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
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 { type } from '@lowdefy/helpers';
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 Error(`Invalid _ref definition ${JSON.stringify({
19
- _ref: refDef.original
20
- })} in file ${referencedFrom}`);
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
- throw new Error(`Tried to reference file "${refDef.path}" from "${referencedFrom}", but file does not exist.`);
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-2024 Lowdefy, Inc
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.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
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.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
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.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -12,14 +12,16 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ import { type } from '@lowdefy/helpers';
15
+ */ import { serializer, type } from '@lowdefy/helpers';
16
16
  import makeRefDefinition from './makeRefDefinition.js';
17
17
  function getRefsFromFile(fileContent, parentRefDefId, refMap) {
18
18
  const foundRefs = [];
19
19
  const reviver = (key, value)=>{
20
20
  if (type.isObject(value)) {
21
21
  if (!type.isUndefined(value._ref)) {
22
- const def = makeRefDefinition(value._ref, parentRefDefId, refMap);
22
+ // Capture line number from the object containing the _ref
23
+ const lineNumber = value['~l'];
24
+ const def = makeRefDefinition(value._ref, parentRefDefId, refMap, lineNumber);
23
25
  foundRefs.push(def);
24
26
  return {
25
27
  _ref: def
@@ -28,7 +30,10 @@ function getRefsFromFile(fileContent, parentRefDefId, refMap) {
28
30
  }
29
31
  return value;
30
32
  };
31
- const fileContentBuiltRefs = JSON.parse(JSON.stringify(fileContent), reviver);
33
+ // Use serializer.copy to preserve non-enumerable properties like ~l
34
+ const fileContentBuiltRefs = serializer.copy(fileContent, {
35
+ reviver
36
+ });
32
37
  return {
33
38
  foundRefs,
34
39
  fileContentBuiltRefs
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
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
- return (await import(pathToFileURL(path.join(context.directories.config, filePath)))).default;
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
- context.logger.error(`Error importing ${filePath}.`);
22
- throw Error(error);
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-2024 Lowdefy, Inc
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-2024 Lowdefy, Inc
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.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -12,24 +12,118 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ /* eslint-disable no-param-reassign */ import { type } from '@lowdefy/helpers';
15
+ */ /* eslint-disable no-param-reassign */ import { ConfigError } from '@lowdefy/errors';
16
+ import { type } from '@lowdefy/helpers';
16
17
  import { getFileExtension, getFileSubExtension } from '@lowdefy/node-utils';
17
18
  import JSON5 from 'json5';
18
- import YAML from 'yaml';
19
+ import YAML, { isMap, isSeq, isPair, isScalar } from 'yaml';
19
20
  import parseNunjucks from './parseNunjucks.js';
21
+ import setNonEnumerableProperty from '../../utils/setNonEnumerableProperty.js';
22
+ function getLineNumber(content, offset) {
23
+ if (offset == null || offset < 0) return null;
24
+ return content.substring(0, offset).split('\n').length;
25
+ }
26
+ function addLineNumbers(node, content, result) {
27
+ if (isMap(node)) {
28
+ const obj = result || {};
29
+ if (node.range) {
30
+ setNonEnumerableProperty(obj, '~l', getLineNumber(content, node.range[0]));
31
+ }
32
+ for (const pair of node.items){
33
+ if (isPair(pair) && isScalar(pair.key)) {
34
+ const key = pair.key.value;
35
+ const value = pair.value;
36
+ // Use key's line number for the value's ~l (more useful for error messages)
37
+ const keyLineNumber = pair.key.range ? getLineNumber(content, pair.key.range[0]) : null;
38
+ if (isMap(value)) {
39
+ const mapResult = addLineNumbers(value, content, {});
40
+ // Override ~l with key's line number if available
41
+ if (keyLineNumber) {
42
+ setNonEnumerableProperty(mapResult, '~l', keyLineNumber);
43
+ }
44
+ obj[key] = mapResult;
45
+ } else if (isSeq(value)) {
46
+ const arrResult = addLineNumbers(value, content, []);
47
+ // Override ~l with key's line number if available
48
+ if (keyLineNumber) {
49
+ setNonEnumerableProperty(arrResult, '~l', keyLineNumber);
50
+ }
51
+ obj[key] = arrResult;
52
+ } else if (isScalar(value)) {
53
+ obj[key] = value.value;
54
+ } else {
55
+ obj[key] = value?.toJSON?.() ?? value;
56
+ }
57
+ }
58
+ }
59
+ return obj;
60
+ }
61
+ if (isSeq(node)) {
62
+ const arr = result || [];
63
+ if (node.range) {
64
+ setNonEnumerableProperty(arr, '~l', getLineNumber(content, node.range[0]));
65
+ }
66
+ for (const item of node.items){
67
+ if (isMap(item)) {
68
+ arr.push(addLineNumbers(item, content, {}));
69
+ } else if (isSeq(item)) {
70
+ arr.push(addLineNumbers(item, content, []));
71
+ } else if (isScalar(item)) {
72
+ arr.push(item.value);
73
+ } else {
74
+ arr.push(item?.toJSON?.() ?? item);
75
+ }
76
+ }
77
+ return arr;
78
+ }
79
+ if (isScalar(node)) {
80
+ return node.value;
81
+ }
82
+ return node?.toJSON?.() ?? node;
83
+ }
84
+ function parseYamlWithLineNumbers(content) {
85
+ const doc = YAML.parseDocument(content);
86
+ if (doc.errors && doc.errors.length > 0) {
87
+ throw new Error(doc.errors[0].message);
88
+ }
89
+ return addLineNumbers(doc.contents, content);
90
+ }
20
91
  function parseRefContent({ content, refDef }) {
21
92
  const { path, vars } = refDef;
22
93
  if (type.isString(path)) {
23
94
  let ext = getFileExtension(path);
24
95
  if (ext === 'njk') {
25
- content = parseNunjucks(content, vars);
96
+ try {
97
+ content = parseNunjucks(content, vars);
98
+ } catch (error) {
99
+ throw new ConfigError(`Nunjucks error in "${path}".`, {
100
+ cause: error,
101
+ filePath: path
102
+ });
103
+ }
26
104
  ext = getFileSubExtension(path);
27
105
  }
28
106
  if (ext === 'yaml' || ext === 'yml') {
29
- content = YAML.parse(content);
107
+ try {
108
+ content = parseYamlWithLineNumbers(content);
109
+ } catch (error) {
110
+ const lineMatch = error.message.match(/at line (\d+)/);
111
+ throw new ConfigError(`YAML parse error in "${path}".`, {
112
+ cause: error,
113
+ filePath: path,
114
+ lineNumber: lineMatch ? lineMatch[1] : null
115
+ });
116
+ }
30
117
  }
31
118
  if (ext === 'json') {
32
- content = JSON5.parse(content);
119
+ try {
120
+ content = JSON5.parse(content);
121
+ } catch (error) {
122
+ throw new ConfigError(`JSON parse error in "${path}".`, {
123
+ cause: error,
124
+ filePath: path
125
+ });
126
+ }
33
127
  }
34
128
  }
35
129
  return content;