@lowdefy/build 4.7.2 → 5.0.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 (57) hide show
  1. package/dist/build/addDefaultPages/404.js +8 -2
  2. package/dist/build/buildApi/buildRoutine/validateStep.js +7 -5
  3. package/dist/build/buildApi/validateEndpoint.js +6 -5
  4. package/dist/build/buildConnections.js +6 -0
  5. package/dist/build/buildImports/buildIconImports.js +5 -1
  6. package/dist/build/buildImports/buildImportsDev.js +1 -6
  7. package/dist/build/buildImports/buildImportsProd.js +1 -6
  8. package/dist/build/buildImports/validateIconImports.js +65 -0
  9. package/dist/build/buildJs/jsMapParser.js +5 -2
  10. package/dist/build/buildPages/buildBlock/buildBlock.js +12 -4
  11. package/dist/build/buildPages/buildBlock/buildEvents.js +34 -1
  12. package/dist/build/buildPages/buildBlock/buildRequests.js +7 -5
  13. package/dist/build/buildPages/buildBlock/buildSubBlocks.js +9 -9
  14. package/dist/build/buildPages/buildBlock/countBlockOperators.js +1 -1
  15. package/dist/build/buildPages/buildBlock/moveAreasToSlots.js +31 -0
  16. package/dist/build/buildPages/buildBlock/{moveSkeletonBlocksToArea.js → moveSkeletonBlocksToSlot.js} +8 -8
  17. package/dist/build/buildPages/buildBlock/{moveSubBlocksToArea.js → moveSubBlocksToSlot.js} +3 -3
  18. package/dist/build/buildPages/buildBlock/normalizeClassAndStyles.js +124 -0
  19. package/dist/build/buildPages/buildBlock/normalizeLayout.js +68 -0
  20. package/dist/build/buildPages/buildBlock/setBlockId.js +7 -1
  21. package/dist/build/buildPages/buildBlock/validateSlots.js +34 -0
  22. package/dist/build/buildPages/buildPage.js +23 -1
  23. package/dist/build/buildRefs/addLineNumbers.js +76 -0
  24. package/dist/build/{buildImports/buildStyleImports.js → buildRefs/getLineNumber.js} +4 -10
  25. package/dist/build/buildRefs/getRefContent.js +9 -1
  26. package/dist/build/buildRefs/parseRefContent.js +4 -66
  27. package/dist/build/buildTypes.js +4 -2
  28. package/dist/build/cleanBuildDirectory.js +3 -1
  29. package/dist/build/collectPageContent.js +57 -0
  30. package/dist/build/jit/buildPageJit.js +14 -3
  31. package/dist/build/jit/collectSkeletonSourceFiles.js +75 -0
  32. package/dist/build/jit/extractIconData.js +16 -1
  33. package/dist/build/jit/pageContentKeys.js +1 -0
  34. package/dist/build/jit/shallowBuild.js +34 -1
  35. package/dist/build/jit/stripPageContent.js +29 -0
  36. package/dist/build/jit/writePageJit.js +9 -1
  37. package/dist/build/testSchema.js +3 -0
  38. package/dist/build/writePluginImports/collectBlockSourceContent.js +65 -0
  39. package/dist/build/writePluginImports/writeActionSchemaMap.js +1 -1
  40. package/dist/build/writePluginImports/writeBlockSchemaMap.js +45 -7
  41. package/dist/build/writePluginImports/writeGlobalsCss.js +126 -0
  42. package/dist/build/writePluginImports/writeOperatorSchemaMap.js +1 -1
  43. package/dist/build/writePluginImports/writePluginImports.js +7 -2
  44. package/dist/build/writeTheme.js +28 -0
  45. package/dist/createContext.js +2 -0
  46. package/dist/defaultTypesMap.js +1693 -837
  47. package/dist/index.js +16 -0
  48. package/dist/lowdefySchema.js +100 -0
  49. package/dist/scripts/generateDefaultTypes.js +5 -10
  50. package/dist/test-utils/runBuild.js +3 -0
  51. package/dist/test-utils/runBuildForSnapshots.js +5 -2
  52. package/dist/test-utils/testContext.js +2 -1
  53. package/dist/utils/createHandleWarning.js +3 -0
  54. package/dist/utils/createPluginTypesMap.js +5 -9
  55. package/dist/utils/validateId.js +24 -0
  56. package/package.json +47 -47
  57. package/dist/build/writePluginImports/writeStyleImports.js +0 -34
@@ -0,0 +1,68 @@
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 { ConfigWarning } from '@lowdefy/errors';
17
+ const DEPRECATED_LAYOUT_KEYS = {
18
+ contentGutter: 'gap',
19
+ contentGap: 'gap',
20
+ contentJustify: 'justify',
21
+ contentDirection: 'direction',
22
+ contentWrap: 'wrap',
23
+ contentOverflow: 'overflow'
24
+ };
25
+ function normalizeLayout(block, pageContext) {
26
+ const layout = block.layout;
27
+ if (type.isNone(layout) || !type.isObject(layout)) return;
28
+ // Warn and rename deprecated content* layout properties
29
+ for (const [oldKey, newKey] of Object.entries(DEPRECATED_LAYOUT_KEYS)){
30
+ if (!type.isNone(layout[oldKey])) {
31
+ pageContext.context.handleWarning(new ConfigWarning(`Block "${block.blockId}": layout.${oldKey} is deprecated. Use layout.${newKey} instead.`, {
32
+ configKey: block['~k'],
33
+ prodError: true
34
+ }));
35
+ if (type.isNone(layout[newKey])) {
36
+ layout[newKey] = layout[oldKey];
37
+ }
38
+ delete layout[oldKey];
39
+ }
40
+ }
41
+ // Warn about gutter in slot/area configs
42
+ for (const slot of Object.values(block.slots ?? {})){
43
+ if (!type.isNone(slot.gutter)) {
44
+ pageContext.context.handleWarning(new ConfigWarning(`Block "${block.blockId}": slots.*.gutter is deprecated. Use gap instead.`, {
45
+ configKey: block['~k'],
46
+ prodError: true
47
+ }));
48
+ if (type.isNone(slot.gap)) {
49
+ slot.gap = slot.gutter;
50
+ }
51
+ delete slot.gutter;
52
+ }
53
+ }
54
+ // Also check areas (before they're moved to slots)
55
+ for (const area of Object.values(block.areas ?? {})){
56
+ if (!type.isNone(area.gutter)) {
57
+ pageContext.context.handleWarning(new ConfigWarning(`Block "${block.blockId}": areas.*.gutter is deprecated. Use gap instead.`, {
58
+ configKey: block['~k'],
59
+ prodError: true
60
+ }));
61
+ if (type.isNone(area.gap)) {
62
+ area.gap = area.gutter;
63
+ }
64
+ delete area.gutter;
65
+ }
66
+ }
67
+ }
68
+ export default normalizeLayout;
@@ -12,8 +12,14 @@
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
- */ function setBlockId(block, { pageId, blockIdCounter }) {
15
+ */ import { ConfigError } from '@lowdefy/errors';
16
+ function setBlockId(block, { pageId, blockIdCounter }) {
16
17
  block.blockId = block.id;
18
+ if (block.blockId === pageId && blockIdCounter.getCount(block.blockId) > 0) {
19
+ throw new ConfigError(`Block id "${block.blockId}" on page "${pageId}" collides with the page id. A block cannot have the same id as its page.`, {
20
+ configKey: block['~k']
21
+ });
22
+ }
17
23
  block.id = `block:${pageId}:${block.blockId}:${blockIdCounter.getCount(block.blockId)}`;
18
24
  blockIdCounter.increment(block.blockId);
19
25
  }
@@ -0,0 +1,34 @@
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 { ConfigWarning } from '@lowdefy/errors';
17
+ function validateSlots(block, pageContext) {
18
+ const blockMeta = pageContext.context.blockMetas?.[block.type];
19
+ if (!blockMeta) return;
20
+ if (blockMeta.slots === false) return;
21
+ if (!type.isArray(blockMeta.slots) && !type.isObject(blockMeta.slots)) return;
22
+ if (!type.isObject(block.slots)) return;
23
+ const validSlots = type.isArray(blockMeta.slots) ? new Set(blockMeta.slots) : new Set(Object.keys(blockMeta.slots));
24
+ for (const slotKey of Object.keys(block.slots)){
25
+ if (!validSlots.has(slotKey)) {
26
+ pageContext.context.handleWarning(new ConfigWarning(`Block "${block.blockId}" (${block.type}): Unknown slot "${slotKey}". Valid slots: ${[
27
+ ...validSlots
28
+ ].join(', ')}.`, {
29
+ configKey: block.slots[slotKey]?.['~k'] ?? block['~k']
30
+ }));
31
+ }
32
+ }
33
+ }
34
+ export default validateSlots;
@@ -13,10 +13,11 @@
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
+ import { ConfigError, ConfigWarning } from '@lowdefy/errors';
17
17
  import buildBlock from './buildBlock/buildBlock.js';
18
18
  import collectExceptions from '../../utils/collectExceptions.js';
19
19
  import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
20
+ import validateId from '../../utils/validateId.js';
20
21
  import createCounter from '../../utils/createCounter.js';
21
22
  import validateRequestReferences from './validateRequestReferences.js';
22
23
  function buildPage({ page, index, context, checkDuplicatePageId }) {
@@ -38,6 +39,11 @@ function buildPage({ page, index, context, checkDuplicatePageId }) {
38
39
  failed: true
39
40
  };
40
41
  }
42
+ validateId({
43
+ id: page.id,
44
+ field: 'Page id',
45
+ configKey
46
+ });
41
47
  if (checkDuplicatePageId) {
42
48
  checkDuplicatePageId({
43
49
  id: page.id,
@@ -47,6 +53,7 @@ function buildPage({ page, index, context, checkDuplicatePageId }) {
47
53
  page.pageId = page.id;
48
54
  const requests = [];
49
55
  const requestActionRefs = [];
56
+ const shortcutRefs = [];
50
57
  buildBlock(page, {
51
58
  auth: page.auth,
52
59
  blockIdCounter: createCounter(),
@@ -57,6 +64,7 @@ function buildPage({ page, index, context, checkDuplicatePageId }) {
57
64
  pageId: page.pageId,
58
65
  requests,
59
66
  requestActionRefs,
67
+ shortcutRefs,
60
68
  linkActionRefs: context.linkActionRefs,
61
69
  typeCounters: context.typeCounters
62
70
  });
@@ -69,6 +77,20 @@ function buildPage({ page, index, context, checkDuplicatePageId }) {
69
77
  pageId: page.pageId,
70
78
  context
71
79
  });
80
+ // Warn on duplicate shortcuts within the page
81
+ const seenShortcuts = {};
82
+ shortcutRefs.forEach(({ shortcut, blockId, eventId, configKey })=>{
83
+ if (seenShortcuts[shortcut]) {
84
+ context.handleWarning(new ConfigWarning(`Duplicate shortcut "${shortcut}" on event "${eventId}" on block "${blockId}" on page "${page.pageId}" — already defined on block "${seenShortcuts[shortcut].blockId}".`, {
85
+ configKey
86
+ }));
87
+ } else {
88
+ seenShortcuts[shortcut] = {
89
+ blockId,
90
+ eventId
91
+ };
92
+ }
93
+ });
72
94
  page.requests = requests;
73
95
  }
74
96
  export default buildPage;
@@ -0,0 +1,76 @@
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
+ */ /* eslint-disable no-param-reassign */ import { isMap, isSeq, isPair, isScalar } from 'yaml';
16
+ import setNonEnumerableProperty from '../../utils/setNonEnumerableProperty.js';
17
+ import getLineNumber from './getLineNumber.js';
18
+ function addLineNumbers(node, content, result) {
19
+ if (isMap(node)) {
20
+ const obj = result || {};
21
+ if (node.range) {
22
+ setNonEnumerableProperty(obj, '~l', getLineNumber(content, node.range[0]));
23
+ }
24
+ for (const pair of node.items){
25
+ if (isPair(pair) && isScalar(pair.key)) {
26
+ const key = pair.key.value;
27
+ const value = pair.value;
28
+ // Use key's line number for the value's ~l (more useful for error messages)
29
+ const keyLineNumber = pair.key.range ? getLineNumber(content, pair.key.range[0]) : null;
30
+ if (isMap(value)) {
31
+ const mapResult = addLineNumbers(value, content, {});
32
+ // Override ~l with key's line number if available
33
+ if (keyLineNumber) {
34
+ setNonEnumerableProperty(mapResult, '~l', keyLineNumber);
35
+ }
36
+ obj[key] = mapResult;
37
+ } else if (isSeq(value)) {
38
+ const arrResult = addLineNumbers(value, content, []);
39
+ // Override ~l with key's line number if available
40
+ if (keyLineNumber) {
41
+ setNonEnumerableProperty(arrResult, '~l', keyLineNumber);
42
+ }
43
+ obj[key] = arrResult;
44
+ } else if (isScalar(value)) {
45
+ obj[key] = value.value;
46
+ } else {
47
+ obj[key] = value?.toJSON?.() ?? value;
48
+ }
49
+ }
50
+ }
51
+ return obj;
52
+ }
53
+ if (isSeq(node)) {
54
+ const arr = result || [];
55
+ if (node.range) {
56
+ setNonEnumerableProperty(arr, '~l', getLineNumber(content, node.range[0]));
57
+ }
58
+ for (const item of node.items){
59
+ if (isMap(item)) {
60
+ arr.push(addLineNumbers(item, content, {}));
61
+ } else if (isSeq(item)) {
62
+ arr.push(addLineNumbers(item, content, []));
63
+ } else if (isScalar(item)) {
64
+ arr.push(item.value);
65
+ } else {
66
+ arr.push(item?.toJSON?.() ?? item);
67
+ }
68
+ }
69
+ return arr;
70
+ }
71
+ if (isScalar(node)) {
72
+ return node.value;
73
+ }
74
+ return node?.toJSON?.() ?? node;
75
+ }
76
+ export default addLineNumbers;
@@ -12,14 +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
- */ function buildStyleImports({ blocks, context }) {
16
- const styles = new Set();
17
- blocks.forEach((block)=>{
18
- styles.add(...(context.typesMap.styles.packages[block.package] || []).map((style)=>`${block.package}/${style}`));
19
- styles.add(...(context.typesMap.styles.blocks[block.typeName] || []).map((style)=>`${block.package}/${style}`));
20
- });
21
- return [
22
- ...styles
23
- ].filter((style)=>!!style);
15
+ */ function getLineNumber(content, offset) {
16
+ if (offset == null || offset < 0) return null;
17
+ return content.substring(0, offset).split('\n').length;
24
18
  }
25
- export default buildStyleImports;
19
+ export default getLineNumber;
@@ -12,7 +12,10 @@
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 getConfigFile from './getConfigFile.js';
15
+ */ import { type } from '@lowdefy/helpers';
16
+ import { getFileExtension } from '@lowdefy/node-utils';
17
+ import getConfigFile from './getConfigFile.js';
18
+ import getUserJavascriptFunction from './getUserJavascriptFunction.js';
16
19
  import parseRefContent from './parseRefContent.js';
17
20
  import runRefResolver from './runRefResolver.js';
18
21
  async function getRefContent({ context, refDef, referencedFrom }) {
@@ -29,6 +32,11 @@ async function getRefContent({ context, refDef, referencedFrom }) {
29
32
  refDef,
30
33
  referencedFrom
31
34
  });
35
+ } else if (type.isString(refDef.path) && getFileExtension(refDef.path) === 'js') {
36
+ return getUserJavascriptFunction({
37
+ context,
38
+ filePath: refDef.path
39
+ });
32
40
  } else {
33
41
  content = await getConfigFile({
34
42
  context,
@@ -12,75 +12,13 @@
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 { ConfigError } from '@lowdefy/errors';
15
+ */ import { ConfigError } from '@lowdefy/errors';
16
16
  import { type } from '@lowdefy/helpers';
17
17
  import { getFileExtension, getFileSubExtension } from '@lowdefy/node-utils';
18
18
  import JSON5 from 'json5';
19
- import YAML, { isMap, isSeq, isPair, isScalar } from 'yaml';
19
+ import YAML from 'yaml';
20
+ import addLineNumbers from './addLineNumbers.js';
20
21
  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
22
  function parseYamlWithLineNumbers(content) {
85
23
  const doc = YAML.parseDocument(content);
86
24
  if (doc.errors && doc.errors.length > 0) {
@@ -88,7 +26,7 @@ function parseYamlWithLineNumbers(content) {
88
26
  }
89
27
  return addLineNumbers(doc.contents, content);
90
28
  }
91
- function parseRefContent({ content, refDef }) {
29
+ async function parseRefContent({ content, refDef }) {
92
30
  const { path, vars } = refDef;
93
31
  if (type.isString(path)) {
94
32
  let ext = getFileExtension(path);
@@ -12,9 +12,9 @@
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 basicTypes from '@lowdefy/blocks-basic/types';
15
+ */ import { ConfigError, ConfigWarning } from '@lowdefy/errors';
16
+ import basicTypes from '@lowdefy/blocks-basic/types';
16
17
  import loaderTypes from '@lowdefy/blocks-loaders/types';
17
- import { ConfigError, ConfigWarning } from '@lowdefy/errors';
18
18
  import findSimilarString from '../utils/findSimilarString.js';
19
19
  function buildTypeClass(context, { counter, definitions, store, typeClass, warnIfMissing = false }) {
20
20
  const counts = counter.getCounts();
@@ -61,6 +61,8 @@ function buildTypes({ components, context }) {
61
61
  loaderTypes.blocks.forEach((block)=>typeCounters.blocks.increment(block));
62
62
  // Used for DisplayMessage in @lowdefy/client
63
63
  typeCounters.blocks.increment('Message');
64
+ // Used by blocks-antd Header/PageHeaderMenu/PageSiderMenu darkModeToggle
65
+ typeCounters.actions.increment('SetDarkMode');
64
66
  components.types = {
65
67
  actions: {},
66
68
  auth: {
@@ -12,8 +12,10 @@
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 { cleanDirectory } from '@lowdefy/node-utils';
15
+ */ import path from 'path';
16
+ import { cleanDirectory } from '@lowdefy/node-utils';
16
17
  async function cleanBuildDirectory({ context }) {
17
18
  await cleanDirectory(context.directories.build);
19
+ await cleanDirectory(path.join(context.directories.server, 'lowdefy-build', 'tailwind'));
18
20
  }
19
21
  export default cleanBuildDirectory;
@@ -0,0 +1,57 @@
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
+ */ function walkValue(value, strings) {
16
+ if (typeof value === 'string') {
17
+ strings.push(value);
18
+ return;
19
+ }
20
+ if (Array.isArray(value)) {
21
+ for (const item of value){
22
+ walkValue(item, strings);
23
+ }
24
+ return;
25
+ }
26
+ if (value && typeof value === 'object') {
27
+ for (const v of Object.values(value)){
28
+ walkValue(v, strings);
29
+ }
30
+ }
31
+ }
32
+ function walkBlockProperties(blocks, strings) {
33
+ if (!Array.isArray(blocks)) return;
34
+ for (const block of blocks){
35
+ if (!block) continue;
36
+ if (block.class) {
37
+ walkValue(block.class, strings);
38
+ }
39
+ if (block.properties) {
40
+ walkValue(block.properties, strings);
41
+ }
42
+ walkBlockProperties(block.blocks, strings);
43
+ for (const area of Object.values(block.areas ?? {})){
44
+ walkBlockProperties(area.blocks, strings);
45
+ }
46
+ for (const slot of Object.values(block.slots ?? {})){
47
+ walkBlockProperties(slot.blocks, strings);
48
+ }
49
+ }
50
+ }
51
+ function collectPageContent(pages) {
52
+ const strings = [];
53
+ walkBlockProperties(pages, strings);
54
+ return strings.join('\n');
55
+ }
56
+ export default collectPageContent;
57
+ export { walkBlockProperties };
@@ -70,11 +70,13 @@ async function buildPageJit({ pageId, pageRegistry, context, directories, logger
70
70
  if (!pageEntry) {
71
71
  return null;
72
72
  }
73
- // Reset errors for this build. Keep a local reference so that concurrent
74
- // JIT builds (different pages sharing buildContext) cannot corrupt our
75
- // error list by reassigning buildContext.errors during an await.
73
+ // Reset errors and warnings for this build. Keep local references so that
74
+ // concurrent JIT builds (different pages sharing buildContext) cannot corrupt
75
+ // our lists by reassigning during an await.
76
76
  const buildErrors = [];
77
+ const buildWarnings = [];
77
78
  buildContext.errors = buildErrors;
79
+ buildContext.warnings = buildWarnings;
78
80
  try {
79
81
  // Pages without a source file (e.g., default 404) can only be served from
80
82
  // their pre-built artifact — they have no YAML to re-resolve from.
@@ -277,6 +279,15 @@ async function buildPageJit({ pageId, pageRegistry, context, directories, logger
277
279
  page: finalPage,
278
280
  context: buildContext
279
281
  });
282
+ // Attach warnings after disk write so they don't persist in artifacts
283
+ if (buildWarnings.length > 0) {
284
+ finalPage._warnings = buildWarnings.map((w)=>({
285
+ type: w.name ?? 'ConfigWarning',
286
+ message: w.message,
287
+ source: w.source ?? null,
288
+ stack: w.stack ?? null
289
+ }));
290
+ }
280
291
  return finalPage;
281
292
  } catch (err) {
282
293
  // Attach any collected errors to the thrown error
@@ -0,0 +1,75 @@
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
+ */ // Walk an object/array tree collecting non-enumerable ~r ref IDs.
16
+ // ~r is set by buildRefs (walker.js) on every resolved object/array
17
+ // and is non-enumerable, so Object.keys() won't find it.
18
+ function walkRefIds(obj, refIds) {
19
+ if (obj === null || typeof obj !== 'object') return;
20
+ if (obj['~r'] !== undefined) {
21
+ refIds.add(obj['~r']);
22
+ }
23
+ if (Array.isArray(obj)) {
24
+ for(let i = 0; i < obj.length; i++){
25
+ walkRefIds(obj[i], refIds);
26
+ }
27
+ } else {
28
+ for (const key of Object.keys(obj)){
29
+ walkRefIds(obj[key], refIds);
30
+ }
31
+ }
32
+ }
33
+ // Collect file paths that contribute to skeleton (non-page) config.
34
+ // Walks ~r markers on non-page components, traces each through the
35
+ // refMap parent chain, and scans for scalar-resolving descendants.
36
+ function collectSkeletonSourceFiles({ components, context }) {
37
+ const refIds = new Set();
38
+ for (const key of Object.keys(components)){
39
+ if (key === 'pages') continue;
40
+ walkRefIds(components[key], refIds);
41
+ }
42
+ const sourceFiles = new Set();
43
+ // Walk parent chains for each collected ref ID
44
+ for (const refId of refIds){
45
+ let current = refId;
46
+ while(current != null){
47
+ const entry = context.refMap[current];
48
+ if (!entry) break;
49
+ if (entry.path) {
50
+ sourceFiles.add(entry.path);
51
+ }
52
+ current = entry.parent;
53
+ }
54
+ }
55
+ // Scan for scalar-resolving descendants: refs that resolved to primitives
56
+ // have no ~r marker in the tree but are recorded in the refMap. Include
57
+ // their paths if any ancestor in their parent chain is a collected ref ID.
58
+ for (const [id, entry] of Object.entries(context.refMap)){
59
+ if (refIds.has(id)) continue;
60
+ let current = entry.parent;
61
+ while(current != null){
62
+ if (refIds.has(current)) {
63
+ if (entry.path) {
64
+ sourceFiles.add(entry.path);
65
+ }
66
+ break;
67
+ }
68
+ const parentEntry = context.refMap[current];
69
+ if (!parentEntry) break;
70
+ current = parentEntry.parent;
71
+ }
72
+ }
73
+ return sourceFiles;
74
+ }
75
+ export default collectSkeletonSourceFiles;
@@ -14,6 +14,7 @@
14
14
  limitations under the License.
15
15
  */ import { createRequire } from 'module';
16
16
  import path from 'path';
17
+ import findSimilarString from '../../utils/findSimilarString.js';
17
18
  // Matches the JSON data argument inside GenIcon({...})(props) in react-icons source.
18
19
  // react-icons icons are generated functions of the form:
19
20
  // function IconName(props) { return GenIcon({...})(props); }
@@ -35,7 +36,21 @@ function extractIconData({ icons, directories, logger }) {
35
36
  }
36
37
  }
37
38
  const iconFn = moduleCache[pkg][icon];
38
- if (!iconFn) continue;
39
+ if (!iconFn) {
40
+ if (logger) {
41
+ let message = `Icon "${icon}" not found in "${pkg}".`;
42
+ const suggestion = findSimilarString({
43
+ input: icon,
44
+ candidates: Object.keys(moduleCache[pkg]),
45
+ maxDistance: Math.max(3, Math.ceil(icon.length * 0.4))
46
+ });
47
+ if (suggestion) {
48
+ message += ` Did you mean "${suggestion}"?`;
49
+ }
50
+ logger.warn(message);
51
+ }
52
+ continue;
53
+ }
39
54
  const match = iconFn.toString().match(genIconDataRegex);
40
55
  if (match) {
41
56
  try {
@@ -19,6 +19,7 @@
19
19
  const PAGE_CONTENT_KEYS = [
20
20
  'blocks',
21
21
  'areas',
22
+ 'slots',
22
23
  'events',
23
24
  'requests',
24
25
  'layout'