@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.
Files changed (163) 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 +40 -7
  57. package/dist/build/buildRefs/evaluateStaticOperators.js +52 -0
  58. package/dist/build/buildRefs/getConfigFile.js +25 -6
  59. package/dist/build/buildRefs/getKey.js +1 -1
  60. package/dist/build/buildRefs/getRefContent.js +1 -1
  61. package/dist/build/buildRefs/getRefPath.js +1 -1
  62. package/dist/build/buildRefs/getUserJavascriptFunction.js +18 -4
  63. package/dist/build/buildRefs/makeRefDefinition.js +10 -5
  64. package/dist/build/buildRefs/parseNunjucks.js +1 -1
  65. package/dist/build/buildRefs/parseRefContent.js +108 -7
  66. package/dist/build/buildRefs/runRefResolver.js +11 -3
  67. package/dist/build/buildRefs/runTransformer.js +7 -3
  68. package/dist/build/buildRefs/walker.js +340 -0
  69. package/dist/build/buildTypes.js +22 -7
  70. package/dist/build/cleanBuildDirectory.js +1 -1
  71. package/dist/build/collectDynamicIdentifiers.js +35 -0
  72. package/dist/build/collectTypeNames.js +36 -0
  73. package/dist/build/copyPublicFolder.js +1 -1
  74. package/dist/build/{buildJs → full}/buildJs.js +3 -3
  75. package/dist/build/full/buildPages.js +87 -0
  76. package/dist/build/{buildPages → full}/buildTestPage.js +15 -3
  77. package/dist/build/{updateServerPackageJson.js → full/updateServerPackageJson.js} +1 -1
  78. package/dist/build/{writePages.js → full/writePages.js} +1 -1
  79. package/dist/build/{writeRequests.js → full/writeRequests.js} +11 -3
  80. package/dist/build/{writeTypes.js → full/writeTypes.js} +1 -1
  81. package/dist/build/jit/addInstalledTypes.js +51 -0
  82. package/dist/build/jit/buildJsShallow.js +41 -0
  83. package/dist/build/jit/buildPageJit.js +271 -0
  84. package/dist/build/jit/buildShallowPages.js +90 -0
  85. package/dist/build/jit/createPageRegistry.js +85 -0
  86. package/dist/build/jit/detectMissingPluginPackages.js +62 -0
  87. package/dist/build/jit/isPageContentPath.js +24 -0
  88. package/dist/build/jit/pageContentKeys.js +26 -0
  89. package/dist/build/jit/shallowBuild.js +242 -0
  90. package/dist/build/jit/updateServerPackageJsonJit.js +33 -0
  91. package/dist/build/jit/validatePageTypes.js +73 -0
  92. package/dist/build/jit/writePageJit.js +44 -0
  93. package/dist/build/jit/writePageRegistry.js +23 -0
  94. package/dist/build/jit/writeSourcelessPages.js +23 -0
  95. package/dist/build/testSchema.js +45 -7
  96. package/dist/build/validateConfig.js +2 -2
  97. package/dist/build/validateOperatorsDynamic.js +28 -0
  98. package/dist/build/writeApi.js +1 -1
  99. package/dist/build/writeApp.js +1 -1
  100. package/dist/build/writeAuth.js +1 -1
  101. package/dist/build/writeConfig.js +1 -1
  102. package/dist/build/writeConnections.js +1 -1
  103. package/dist/build/writeGlobal.js +1 -1
  104. package/dist/build/writeLogger.js +19 -0
  105. package/dist/build/writeMaps.js +1 -1
  106. package/dist/build/writeMenus.js +1 -1
  107. package/dist/build/writePluginImports/generateImportFile.js +1 -1
  108. package/dist/build/writePluginImports/writeActionImports.js +1 -1
  109. package/dist/build/writePluginImports/writeActionSchemaMap.js +42 -0
  110. package/dist/build/writePluginImports/writeAuthImports.js +1 -1
  111. package/dist/build/writePluginImports/writeBlockImports.js +1 -1
  112. package/dist/build/writePluginImports/writeBlockSchemaMap.js +42 -0
  113. package/dist/build/writePluginImports/writeConnectionImports.js +1 -1
  114. package/dist/build/writePluginImports/writeIconImports.js +1 -1
  115. package/dist/build/writePluginImports/writeOperatorImports.js +1 -1
  116. package/dist/build/writePluginImports/writeOperatorSchemaMap.js +49 -0
  117. package/dist/build/writePluginImports/writePluginImports.js +16 -1
  118. package/dist/build/writePluginImports/writeStyleImports.js +1 -1
  119. package/dist/createContext.js +16 -4
  120. package/dist/defaultTypesMap.js +479 -457
  121. package/dist/index.js +190 -127
  122. package/dist/indexDev.js +19 -0
  123. package/dist/lowdefySchema.js +588 -0
  124. package/dist/scripts/generateDefaultTypes.js +2 -1
  125. package/dist/scripts/run.js +1 -1
  126. package/dist/{test → test-utils}/buildRefs/testBuildRefsAsyncFunction.js +1 -1
  127. package/dist/{test → test-utils}/buildRefs/testBuildRefsErrorResolver.js +1 -1
  128. package/dist/{test → test-utils}/buildRefs/testBuildRefsNullResolver.js +1 -1
  129. package/dist/{test → test-utils}/buildRefs/testBuildRefsParsingResolver.js +1 -1
  130. package/dist/{test → test-utils}/buildRefs/testBuildRefsResolver.js +1 -1
  131. package/dist/{test → test-utils}/buildRefs/testBuildRefsTransform.js +1 -1
  132. package/dist/{test → test-utils}/buildRefs/testBuildRefsTransformIdentity.js +1 -1
  133. package/dist/test-utils/buildRefs/testJitPageResolver.js +24 -0
  134. package/dist/test-utils/createTestLogger.js +36 -0
  135. package/dist/test-utils/parseTestYaml.js +126 -0
  136. package/dist/test-utils/runBuild.js +228 -0
  137. package/dist/test-utils/runBuildForSnapshots.js +704 -0
  138. package/dist/{test → test-utils}/testContext.js +12 -2
  139. package/dist/utils/collectExceptions.js +34 -0
  140. package/dist/utils/countOperators.js +31 -12
  141. package/dist/utils/createBuildHandleError.js +38 -0
  142. package/dist/utils/createCheckDuplicateId.js +15 -9
  143. package/dist/utils/createCounter.js +16 -3
  144. package/dist/utils/createHandleWarning.js +41 -0
  145. package/dist/utils/createPluginTypesMap.js +1 -1
  146. package/dist/utils/extractOperatorKey.js +36 -0
  147. package/dist/utils/findConfigKey.js +37 -0
  148. package/dist/utils/findSimilarString.js +53 -0
  149. package/dist/utils/logCollectedErrors.js +30 -0
  150. package/dist/utils/makeId.js +16 -8
  151. package/dist/utils/preserveMetaProperties.js +27 -0
  152. package/dist/utils/readConfigFile.js +1 -1
  153. package/dist/utils/setNonEnumerableProperty.js +23 -0
  154. package/dist/utils/traverseConfig.js +43 -0
  155. package/dist/utils/tryBuildStep.js +46 -0
  156. package/dist/utils/writeBuildArtifact.js +1 -1
  157. package/package.json +46 -41
  158. package/dist/build/buildPages/buildPages.js +0 -31
  159. package/dist/build/buildRefs/evaluateBuildOperators.js +0 -34
  160. package/dist/build/buildRefs/getRefsFromFile.js +0 -37
  161. package/dist/build/buildRefs/populateRefs.js +0 -43
  162. package/dist/build/buildRefs/recursiveBuild.js +0 -85
  163. 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.
@@ -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 recursiveBuild from './recursiveBuild.js';
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
- async function buildRefs({ context }) {
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
- let components = await recursiveBuild({
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
- count: 0
54
+ referencedFrom: null
24
55
  });
25
- components = await evaluateBuildOperators({
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-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.
@@ -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,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 { 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
- if (ext === 'njk') {
25
- content = parseNunjucks(content, vars);
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
- content = YAML.parse(content);
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
- content = JSON5.parse(content);
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-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.
@@ -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 Error(`Error calling resolver "${refDef.resolver}" from "${referencedFrom}": ${error.message}`);
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 Error(`Tried to reference with resolver "${refDef.resolver}" from "${referencedFrom}", but received "${content}".`);
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-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,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 getUserJavascriptFunction from './getUserJavascriptFunction.js';
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 Error(`Error calling transformer "${refDef.transformer}" from "${refDef.path}": ${error.message}`);
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;