@lowdefy/build 5.1.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/buildAgents.js +249 -0
- package/dist/build/buildApi/buildRoutine/countStepTypes.js +3 -0
- package/dist/build/buildApi/buildRoutine/setStepId.js +3 -2
- package/dist/build/buildApi/buildRoutine/validateStep.js +19 -0
- package/dist/build/buildApi/validateEndpoint.js +10 -0
- package/dist/build/buildApi/validateStepReferences.js +4 -4
- package/dist/build/buildAuth/buildApiAuth.js +2 -1
- package/dist/build/buildAuth/buildPageAuth.js +2 -1
- package/dist/build/buildAuth/getApiRoles.js +12 -6
- package/dist/build/buildAuth/getPageRoles.js +12 -6
- package/dist/build/buildAuth/getProtectedApi.js +3 -2
- package/dist/build/buildAuth/getProtectedPages.js +3 -2
- package/dist/build/buildAuth/matchPattern.js +22 -0
- package/dist/build/buildConnections.js +42 -4
- package/dist/build/buildImports/buildImportsDev.js +5 -0
- package/dist/build/buildImports/buildImportsProd.js +1 -0
- package/dist/build/buildJs/jsMapParser.js +25 -12
- package/dist/build/buildJs/writeJs.js +2 -2
- package/dist/build/buildMenu.js +41 -0
- package/dist/build/buildModuleDefs.js +97 -0
- package/dist/build/buildModules.js +96 -0
- package/dist/build/buildPages/buildBlock/buildBlock.js +2 -2
- package/dist/build/buildPages/buildBlock/buildEvents.js +16 -1
- package/dist/build/buildPages/buildBlock/buildSubBlocks.js +2 -1
- package/dist/build/buildPages/buildBlock/validateBlock.js +3 -3
- package/dist/build/buildPages/buildPage.js +1 -0
- package/dist/build/buildPages/validateCallApiRefs.js +31 -0
- package/dist/build/buildRefs/getModuleRefContent.js +81 -0
- package/dist/build/buildRefs/makeRefDefinition.js +6 -0
- package/dist/build/buildRefs/walker.js +424 -44
- package/dist/build/buildTypes.js +7 -0
- package/dist/build/copyAgentFileSystems.js +45 -0
- package/dist/build/fetchGitHubModule.js +94 -0
- package/dist/build/fetchModules.js +60 -0
- package/dist/build/full/buildPages.js +10 -1
- package/dist/build/full/updateServerPackageJson.js +1 -0
- package/dist/build/full/writePages.js +1 -1
- package/dist/build/jit/buildPageJit.js +34 -4
- package/dist/build/jit/collectSkeletonSourceFiles.js +8 -0
- package/dist/build/jit/createPageRegistry.js +10 -1
- package/dist/build/jit/shallowBuild.js +37 -11
- package/dist/build/jit/writePageJit.js +2 -2
- package/dist/build/jit/writeSourcelessPages.js +1 -1
- package/dist/build/parseModuleSource.js +48 -0
- package/dist/build/registerModules.js +247 -0
- package/dist/build/resolveDepTarget.js +43 -0
- package/dist/build/resolveModuleDependencies.js +60 -0
- package/dist/build/resolveModuleOperators.js +27 -0
- package/dist/build/testSchema.js +22 -11
- package/dist/build/writeAgents.js +26 -0
- package/dist/build/writePluginImports/writeAgentImports.js +22 -0
- package/dist/build/writePluginImports/writeGlobalsCss.js +1 -1
- package/dist/build/writePluginImports/writePluginImports.js +5 -0
- package/dist/createContext.js +6 -0
- package/dist/defaultPackages.js +58 -0
- package/dist/defaultTypesMap.js +469 -357
- package/dist/index.js +31 -1
- package/dist/indexDev.js +3 -1
- package/dist/lowdefySchema.js +302 -0
- package/dist/scripts/generateDefaultTypes.js +2 -35
- package/dist/test-utils/testContext.js +2 -0
- package/dist/utils/createPluginTypesMap.js +7 -0
- package/package.json +53 -42
- package/dist/build/jit/stripPageContent.js +0 -29
package/dist/build/buildMenu.js
CHANGED
|
@@ -32,9 +32,50 @@ function buildDefaultMenu({ components, context }) {
|
|
|
32
32
|
];
|
|
33
33
|
return menus;
|
|
34
34
|
}
|
|
35
|
+
function validateMenuItem(menuItem, menuId, context) {
|
|
36
|
+
const configKey = menuItem?.['~k'];
|
|
37
|
+
if (!type.isObject(menuItem)) {
|
|
38
|
+
collectExceptions(context, new ConfigError(`Menu item should be an object on menu "${menuId}".`, {
|
|
39
|
+
received: menuItem,
|
|
40
|
+
configKey
|
|
41
|
+
}));
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (type.isUndefined(menuItem.id)) {
|
|
45
|
+
collectExceptions(context, new ConfigError(`Menu item id missing on menu "${menuId}".`, {
|
|
46
|
+
configKey
|
|
47
|
+
}));
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (!type.isString(menuItem.id)) {
|
|
51
|
+
collectExceptions(context, new ConfigError(`Menu item id is not a string on menu "${menuId}".`, {
|
|
52
|
+
received: menuItem.id,
|
|
53
|
+
configKey
|
|
54
|
+
}));
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (type.isNone(menuItem.type)) {
|
|
58
|
+
collectExceptions(context, new ConfigError(`Menu item type is not defined at "${menuItem.id}" on menu "${menuId}".`, {
|
|
59
|
+
configKey
|
|
60
|
+
}));
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (!type.isString(menuItem.type)) {
|
|
64
|
+
collectExceptions(context, new ConfigError(`Menu item type is not a string at "${menuItem.id}" on menu "${menuId}".`, {
|
|
65
|
+
received: menuItem.type,
|
|
66
|
+
configKey
|
|
67
|
+
}));
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
35
72
|
function loopItems({ parent, menuId, pages, missingPageWarnings, checkDuplicateMenuItemId, context }) {
|
|
36
73
|
if (type.isArray(parent.links)) {
|
|
37
74
|
parent.links.forEach((menuItem)=>{
|
|
75
|
+
if (!validateMenuItem(menuItem, menuId, context)) {
|
|
76
|
+
menuItem.remove = true;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
38
79
|
const configKey = menuItem['~k'];
|
|
39
80
|
if (menuItem.type === 'MenuLink') {
|
|
40
81
|
if (type.isString(menuItem.pageId)) {
|
|
@@ -0,0 +1,97 @@
|
|
|
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 operators from '@lowdefy/operators-js/operators/build';
|
|
16
|
+
import { resolve, WalkContext } from './buildRefs/walker.js';
|
|
17
|
+
import getRefContent from './buildRefs/getRefContent.js';
|
|
18
|
+
import makeRefDefinition from './buildRefs/makeRefDefinition.js';
|
|
19
|
+
import evaluateStaticOperators from './buildRefs/evaluateStaticOperators.js';
|
|
20
|
+
import collectDynamicIdentifiers from './collectDynamicIdentifiers.js';
|
|
21
|
+
import validateOperatorsDynamic from './validateOperatorsDynamic.js';
|
|
22
|
+
import fetchModules from './fetchModules.js';
|
|
23
|
+
import { resolveLocalManifest, resolveFullManifest } from './registerModules.js';
|
|
24
|
+
import resolveModuleDependencies from './resolveModuleDependencies.js';
|
|
25
|
+
validateOperatorsDynamic({
|
|
26
|
+
operators
|
|
27
|
+
});
|
|
28
|
+
const dynamicIdentifiers = collectDynamicIdentifiers({
|
|
29
|
+
operators
|
|
30
|
+
});
|
|
31
|
+
async function parseLowdefyYaml({ context }) {
|
|
32
|
+
const refDef = makeRefDefinition('lowdefy.yaml', null, context.refMap);
|
|
33
|
+
const content = await getRefContent({
|
|
34
|
+
context,
|
|
35
|
+
refDef,
|
|
36
|
+
referencedFrom: null
|
|
37
|
+
});
|
|
38
|
+
const ctx = new WalkContext({
|
|
39
|
+
buildContext: context,
|
|
40
|
+
refId: refDef.id,
|
|
41
|
+
sourceRefId: null,
|
|
42
|
+
vars: {},
|
|
43
|
+
path: '',
|
|
44
|
+
currentFile: refDef.path,
|
|
45
|
+
refChain: new Set(refDef.path ? [
|
|
46
|
+
refDef.path
|
|
47
|
+
] : []),
|
|
48
|
+
operators,
|
|
49
|
+
env: process.env,
|
|
50
|
+
dynamicIdentifiers,
|
|
51
|
+
shouldStop: (path)=>{
|
|
52
|
+
if (path.startsWith('modules')) return false;
|
|
53
|
+
return 'preserve';
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
let config = await resolve(content, ctx);
|
|
57
|
+
config = evaluateStaticOperators({
|
|
58
|
+
context,
|
|
59
|
+
input: config,
|
|
60
|
+
refDef
|
|
61
|
+
});
|
|
62
|
+
return config ?? {};
|
|
63
|
+
}
|
|
64
|
+
async function buildModuleDefs({ context }) {
|
|
65
|
+
const lowdefyConfig = await parseLowdefyYaml({
|
|
66
|
+
context
|
|
67
|
+
});
|
|
68
|
+
context.plugins = lowdefyConfig.plugins ?? [];
|
|
69
|
+
const moduleEntries = lowdefyConfig.modules ?? [];
|
|
70
|
+
if (moduleEntries.length === 0) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const resolvedPaths = await fetchModules({
|
|
74
|
+
moduleEntries,
|
|
75
|
+
context
|
|
76
|
+
});
|
|
77
|
+
// Step 1: Local resolve — concrete arrays, preserved content, exports/deps extracted
|
|
78
|
+
for (const entry of moduleEntries){
|
|
79
|
+
await resolveLocalManifest({
|
|
80
|
+
entry,
|
|
81
|
+
resolvedPaths: resolvedPaths[entry.id],
|
|
82
|
+
context
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// Step 2: Auto-wire and validate dependency wiring
|
|
86
|
+
resolveModuleDependencies({
|
|
87
|
+
context
|
|
88
|
+
});
|
|
89
|
+
// Step 3: Full resolve — cross-module refs, preserved content
|
|
90
|
+
for (const entryId of Object.keys(context.modules)){
|
|
91
|
+
await resolveFullManifest({
|
|
92
|
+
entryId,
|
|
93
|
+
context
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export default buildModuleDefs;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */ /*
|
|
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 { ConfigError } from '@lowdefy/errors';
|
|
16
|
+
import { serializer, type } from '@lowdefy/helpers';
|
|
17
|
+
function validateModuleSecrets({ content, manifest, entryId }) {
|
|
18
|
+
const declaredSecrets = new Set((manifest.secrets ?? []).map((s)=>s.name));
|
|
19
|
+
serializer.copy(content, {
|
|
20
|
+
reviver: (_, value)=>{
|
|
21
|
+
if (!type.isObject(value)) return value;
|
|
22
|
+
const keys = Object.keys(value).filter((k)=>!k.startsWith('~'));
|
|
23
|
+
if (keys.length !== 1) return value;
|
|
24
|
+
if (!type.isUndefined(value['_secret'])) {
|
|
25
|
+
const secretName = value['_secret'];
|
|
26
|
+
if (type.isString(secretName) && !declaredSecrets.has(secretName)) {
|
|
27
|
+
throw new ConfigError(`Module "${entryId}" references secret "${secretName}" ` + `but does not declare it in module.lowdefy.yaml secrets. ` + `Add it to the module's secrets list or remove the reference.`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function buildModules({ components, context }) {
|
|
35
|
+
const moduleEntries = components.modules ?? [];
|
|
36
|
+
delete components.modules;
|
|
37
|
+
for (const entry of moduleEntries){
|
|
38
|
+
const moduleEntry = context.modules[entry.id];
|
|
39
|
+
if (!moduleEntry) {
|
|
40
|
+
throw new ConfigError(`Module entry "${entry.id}" not registered. ` + `Check that buildModuleDefs ran successfully.`);
|
|
41
|
+
}
|
|
42
|
+
const manifest = moduleEntry.manifest;
|
|
43
|
+
// Validate connection remapping keys
|
|
44
|
+
const remapping = moduleEntry.connections ?? {};
|
|
45
|
+
const moduleConnIds = new Set((manifest.connections ?? []).map((c)=>c.id));
|
|
46
|
+
for (const remapKey of Object.keys(remapping)){
|
|
47
|
+
if (!moduleConnIds.has(remapKey)) {
|
|
48
|
+
throw new ConfigError(`Module "${entry.id}" connection remapping references "${remapKey}", ` + `but the module does not export a connection with that id.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Validate secret whitelist on non-remapped content
|
|
52
|
+
for (const page of manifest.pages ?? []){
|
|
53
|
+
validateModuleSecrets({
|
|
54
|
+
content: page,
|
|
55
|
+
manifest,
|
|
56
|
+
entryId: entry.id
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
for (const conn of manifest.connections ?? []){
|
|
60
|
+
if (remapping[conn.id]) continue;
|
|
61
|
+
validateModuleSecrets({
|
|
62
|
+
content: conn,
|
|
63
|
+
manifest,
|
|
64
|
+
entryId: entry.id
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
for (const endpoint of manifest.api ?? []){
|
|
68
|
+
validateModuleSecrets({
|
|
69
|
+
content: endpoint,
|
|
70
|
+
manifest,
|
|
71
|
+
entryId: entry.id
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// Process pages
|
|
75
|
+
for (const page of manifest.pages ?? []){
|
|
76
|
+
page.id = `${entry.id}/${page.id}`;
|
|
77
|
+
components.pages = components.pages ?? [];
|
|
78
|
+
components.pages.push(page);
|
|
79
|
+
}
|
|
80
|
+
// Process connections (skip remapped -- app provides those)
|
|
81
|
+
for (const conn of manifest.connections ?? []){
|
|
82
|
+
if (remapping[conn.id]) continue;
|
|
83
|
+
conn.id = `${entry.id}/${conn.id}`;
|
|
84
|
+
components.connections = components.connections ?? [];
|
|
85
|
+
components.connections.push(conn);
|
|
86
|
+
}
|
|
87
|
+
// Process API endpoints
|
|
88
|
+
for (const endpoint of manifest.api ?? []){
|
|
89
|
+
endpoint.id = `${entry.id}/${endpoint.id}`;
|
|
90
|
+
components.api = components.api ?? [];
|
|
91
|
+
components.api.push(endpoint);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return components;
|
|
95
|
+
}
|
|
96
|
+
export default buildModules;
|
|
@@ -25,8 +25,8 @@ import normalizeLayout from './normalizeLayout.js';
|
|
|
25
25
|
import setBlockId from './setBlockId.js';
|
|
26
26
|
import validateBlock from './validateBlock.js';
|
|
27
27
|
import validateSlots from './validateSlots.js';
|
|
28
|
-
function buildBlock(block, pageContext) {
|
|
29
|
-
validateBlock(block, pageContext);
|
|
28
|
+
function buildBlock(block, pageContext, parentConfigKey) {
|
|
29
|
+
validateBlock(block, pageContext, parentConfigKey);
|
|
30
30
|
setBlockId(block, pageContext);
|
|
31
31
|
normalizeLayout(block, pageContext);
|
|
32
32
|
moveAreasToSlots(block, pageContext);
|
|
@@ -23,7 +23,7 @@ const BROWSER_DEFAULT_SHORTCUTS = new Set([
|
|
|
23
23
|
'mod+q',
|
|
24
24
|
'mod+l'
|
|
25
25
|
]);
|
|
26
|
-
function checkAction(action, { blockId, checkDuplicateActionId, eventId, linkActionRefs, pageId, requestActionRefs, typeCounters }) {
|
|
26
|
+
function checkAction(action, { blockId, callApiActionRefs, checkDuplicateActionId, eventId, linkActionRefs, pageId, requestActionRefs, typeCounters }) {
|
|
27
27
|
const configKey = action['~k'];
|
|
28
28
|
if (type.isUndefined(action.id)) {
|
|
29
29
|
throw new ConfigError(`Action id missing on event "${eventId}" on block "${blockId}" on page "${pageId}".`, {
|
|
@@ -95,6 +95,19 @@ function checkAction(action, { blockId, checkDuplicateActionId, eventId, linkAct
|
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
// Collect static CallAPI action references for validation
|
|
99
|
+
if (action.type === 'CallAPI' && !type.isNone(action.params)) {
|
|
100
|
+
const params = action.params;
|
|
101
|
+
if (type.isObject(params) && type.isString(params.endpointId)) {
|
|
102
|
+
callApiActionRefs.push({
|
|
103
|
+
endpointId: params.endpointId,
|
|
104
|
+
action,
|
|
105
|
+
blockId,
|
|
106
|
+
eventId,
|
|
107
|
+
sourcePageId: pageId
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
98
111
|
}
|
|
99
112
|
function buildEvents(block, pageContext) {
|
|
100
113
|
if (block.events) {
|
|
@@ -133,6 +146,7 @@ function buildEvents(block, pageContext) {
|
|
|
133
146
|
block.events[key].try.map((action)=>checkAction(action, {
|
|
134
147
|
eventId: key,
|
|
135
148
|
blockId: block.blockId,
|
|
149
|
+
callApiActionRefs: pageContext.callApiActionRefs,
|
|
136
150
|
typeCounters: pageContext.typeCounters,
|
|
137
151
|
pageId: pageContext.pageId,
|
|
138
152
|
linkActionRefs: pageContext.linkActionRefs,
|
|
@@ -142,6 +156,7 @@ function buildEvents(block, pageContext) {
|
|
|
142
156
|
block.events[key].catch.map((action)=>checkAction(action, {
|
|
143
157
|
eventId: key,
|
|
144
158
|
blockId: block.blockId,
|
|
159
|
+
callApiActionRefs: pageContext.callApiActionRefs,
|
|
145
160
|
typeCounters: pageContext.typeCounters,
|
|
146
161
|
pageId: pageContext.pageId,
|
|
147
162
|
linkActionRefs: pageContext.linkActionRefs,
|
|
@@ -27,7 +27,8 @@ function buildSubBlocks(block, pageContext) {
|
|
|
27
27
|
configKey: block.slots[key]['~k'] ?? block['~k']
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
|
-
block.slots[key].blocks.
|
|
30
|
+
const slotConfigKey = block.slots[key].blocks['~k'] ?? block.slots[key]['~k'] ?? block['~k'];
|
|
31
|
+
block.slots[key].blocks.map((blk)=>buildBlock(blk, pageContext, slotConfigKey));
|
|
31
32
|
});
|
|
32
33
|
}
|
|
33
34
|
}
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { type } from '@lowdefy/helpers';
|
|
16
16
|
import { ConfigError } from '@lowdefy/errors';
|
|
17
|
-
function validateBlock(block, { pageId }) {
|
|
18
|
-
const configKey = block?.['~k'];
|
|
17
|
+
function validateBlock(block, { pageId }, parentConfigKey) {
|
|
18
|
+
const configKey = block?.['~k'] ?? parentConfigKey;
|
|
19
19
|
if (!type.isObject(block)) {
|
|
20
|
-
throw new ConfigError(`
|
|
20
|
+
throw new ConfigError(`Block should be an object on page "${pageId}".`, {
|
|
21
21
|
received: block,
|
|
22
22
|
configKey
|
|
23
23
|
});
|
|
@@ -57,6 +57,7 @@ function buildPage({ page, index, context, checkDuplicatePageId }) {
|
|
|
57
57
|
buildBlock(page, {
|
|
58
58
|
auth: page.auth,
|
|
59
59
|
blockIdCounter: createCounter(),
|
|
60
|
+
callApiActionRefs: context.callApiActionRefs ?? [],
|
|
60
61
|
checkDuplicateRequestId: createCheckDuplicateId({
|
|
61
62
|
message: 'Duplicate requestId "{{ id }}" on page "{{ pageId }}".'
|
|
62
63
|
}),
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
function validateCallApiRefs({ callApiActionRefs, endpointConfigs, context }) {
|
|
17
|
+
const internalEndpoints = new Set(endpointConfigs.filter((config)=>config.type === 'InternalApi').map((config)=>config.endpointId));
|
|
18
|
+
callApiActionRefs.forEach(({ endpointId, action, sourcePageId })=>{
|
|
19
|
+
if (action.skip === true) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (internalEndpoints.has(endpointId)) {
|
|
23
|
+
context.handleWarning(new ConfigWarning(`CallAPI action on page "${sourcePageId}" targets InternalApi endpoint "${endpointId}". InternalApi endpoints are not accessible from client pages.`, {
|
|
24
|
+
configKey: action['~k'],
|
|
25
|
+
prodError: true,
|
|
26
|
+
checkSlug: 'callapi-internal-refs'
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export default validateCallApiRefs;
|
|
@@ -0,0 +1,81 @@
|
|
|
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 { ConfigError } from '@lowdefy/errors';
|
|
16
|
+
function describeRef(refDef) {
|
|
17
|
+
const parts = [];
|
|
18
|
+
if (refDef.module) parts.push(`module: "${refDef.module}"`);
|
|
19
|
+
if (refDef.component) parts.push(`component: "${refDef.component}"`);
|
|
20
|
+
if (refDef.menu) parts.push(`menu: "${refDef.menu}"`);
|
|
21
|
+
if (refDef.page) parts.push(`page: "${refDef.page}"`);
|
|
22
|
+
if (refDef.connection) parts.push(`connection: "${refDef.connection}"`);
|
|
23
|
+
if (refDef.api) parts.push(`api: "${refDef.api}"`);
|
|
24
|
+
return `_ref { ${parts.join(', ')} }`;
|
|
25
|
+
}
|
|
26
|
+
async function getModuleRefContent({ context, refDef, referencedFrom, walkCtx, configKey }) {
|
|
27
|
+
const rawName = refDef.module;
|
|
28
|
+
let entryId;
|
|
29
|
+
if (walkCtx?.moduleDependencies && rawName in walkCtx.moduleDependencies) {
|
|
30
|
+
entryId = walkCtx.moduleDependencies[rawName];
|
|
31
|
+
} else {
|
|
32
|
+
entryId = rawName;
|
|
33
|
+
}
|
|
34
|
+
const moduleEntry = context.modules[entryId];
|
|
35
|
+
if (!moduleEntry) {
|
|
36
|
+
throw new ConfigError(`${describeRef(refDef)} references module "${rawName}" but no module with that entry id was registered` + (entryId !== rawName ? ` ("${rawName}" was mapped to "${entryId}" via dependency wiring).` : '.'), {
|
|
37
|
+
configKey
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const manifest = moduleEntry.manifest;
|
|
41
|
+
// Cross-module _ref is limited to component and menu.
|
|
42
|
+
// Reject page, connection, api with a clear error pointing to the ID operators.
|
|
43
|
+
if (refDef.page || refDef.connection || refDef.api) {
|
|
44
|
+
const refType = refDef.page ? 'page' : refDef.connection ? 'connection' : 'api';
|
|
45
|
+
const operator = refDef.page ? '_module.pageId' : refDef.connection ? '_module.connectionId' : '_module.endpointId';
|
|
46
|
+
throw new ConfigError(`Cross-module _ref does not support "${refType}". ` + `Use ${operator}: { id: "${refDef[refType]}", module: "${rawName}" } instead.`, {
|
|
47
|
+
configKey
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const exportTypes = [
|
|
51
|
+
'component',
|
|
52
|
+
'menu'
|
|
53
|
+
];
|
|
54
|
+
const exportType = exportTypes.find((t)=>refDef[t]) ?? null;
|
|
55
|
+
if (!exportType) {
|
|
56
|
+
throw new ConfigError('Module _ref requires "component" or "menu" property.', {
|
|
57
|
+
configKey
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const exportName = refDef[exportType];
|
|
61
|
+
const manifestArray = {
|
|
62
|
+
component: manifest.components,
|
|
63
|
+
menu: manifest.menus
|
|
64
|
+
}[exportType] ?? [];
|
|
65
|
+
let content;
|
|
66
|
+
if (exportType === 'component') {
|
|
67
|
+
content = manifestArray.find((item)=>item.id === exportName)?.component;
|
|
68
|
+
} else if (exportType === 'menu') {
|
|
69
|
+
content = manifestArray.find((item)=>item.id === exportName)?.links;
|
|
70
|
+
}
|
|
71
|
+
if (!content) {
|
|
72
|
+
throw new ConfigError(`Module "${entryId}" does not export ${exportType} "${exportName}".`, {
|
|
73
|
+
configKey
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
content,
|
|
78
|
+
entryId
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export default getModuleRefContent;
|
|
@@ -39,6 +39,12 @@ function makeRefDefinition(refDefinition, parent, refMap, lineNumber, walkerPath
|
|
|
39
39
|
vars: get(refDefinition, 'vars', {
|
|
40
40
|
default: {}
|
|
41
41
|
}),
|
|
42
|
+
module: get(refDefinition, 'module'),
|
|
43
|
+
component: get(refDefinition, 'component'),
|
|
44
|
+
menu: get(refDefinition, 'menu'),
|
|
45
|
+
page: get(refDefinition, 'page'),
|
|
46
|
+
connection: get(refDefinition, 'connection'),
|
|
47
|
+
api: get(refDefinition, 'api'),
|
|
42
48
|
...ignoreBuildChecks !== undefined && {
|
|
43
49
|
ignoreBuildChecks
|
|
44
50
|
}
|