@lowdefy/build 5.5.1 → 5.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.
- package/dist/build/buildAgents.js +9 -1
- package/dist/build/buildAuth/buildAgentAuth.js +56 -0
- package/dist/build/buildAuth/buildAuth.js +9 -0
- package/dist/build/buildAuth/getAgentRoles.js +39 -0
- package/dist/build/buildAuth/getProtectedAgents.js +29 -0
- package/dist/build/buildAuth/rejectReservedEntityIds.js +52 -0
- package/dist/build/buildPages/buildBlock/buildEvents.js +11 -1
- package/dist/build/buildPages/buildBlock/validateBlock.js +12 -1
- package/dist/build/buildRefs/getKey.js +16 -5
- package/dist/build/buildRefs/walker.js +21 -4
- package/dist/build/fetchModules.js +18 -4
- package/dist/build/registerModules.js +25 -19
- package/dist/build/writeI18n.js +8 -1
- package/dist/build/writePluginImports/writeGlobalsCss.js +0 -1
- package/dist/defaultTypesMap.js +410 -385
- package/dist/test-utils/runBuild.js +8 -0
- package/dist/utils/logCollectedErrors.js +38 -10
- package/dist/utils/validateId.js +11 -0
- package/package.json +50 -50
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import path from 'path';
|
|
16
16
|
import fs from 'fs';
|
|
17
|
-
import { type } from '@lowdefy/helpers';
|
|
17
|
+
import { isReserved, type } from '@lowdefy/helpers';
|
|
18
18
|
import { ConfigError, ConfigWarning } from '@lowdefy/errors';
|
|
19
19
|
import { RESERVED_PLATFORM_TOOL_NAMES } from '@lowdefy/ai-utils';
|
|
20
20
|
import countOperators from '../utils/countOperators.js';
|
|
@@ -56,6 +56,14 @@ function buildAgents({ components, context }) {
|
|
|
56
56
|
});
|
|
57
57
|
components.agents.forEach((agent)=>{
|
|
58
58
|
const configKey = agent['~k'];
|
|
59
|
+
// agent.id becomes agent.agentId, which keys the detectCycles graph and the
|
|
60
|
+
// runtime agent registry - both plain objects. Reject a reserved name here,
|
|
61
|
+
// where the config location is still in hand.
|
|
62
|
+
if (isReserved(agent.id)) {
|
|
63
|
+
throw new ConfigError(`Agent id "${agent.id}" is a reserved name and cannot be used as an id.`, {
|
|
64
|
+
configKey
|
|
65
|
+
});
|
|
66
|
+
}
|
|
59
67
|
// Check duplicates
|
|
60
68
|
checkDuplicateAgentId({
|
|
61
69
|
id: agent.id,
|
|
@@ -0,0 +1,56 @@
|
|
|
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 { type } from '@lowdefy/helpers';
|
|
16
|
+
import { ConfigError } from '@lowdefy/errors';
|
|
17
|
+
import getAgentRoles from './getAgentRoles.js';
|
|
18
|
+
import getProtectedAgents from './getProtectedAgents.js';
|
|
19
|
+
import { isInPatternList } from './matchPattern.js';
|
|
20
|
+
// Agents are served from the API surface, so auth.api patterns match agent ids too.
|
|
21
|
+
function buildAgentAuth({ components, context }) {
|
|
22
|
+
const protectedAgents = getProtectedAgents({
|
|
23
|
+
components
|
|
24
|
+
});
|
|
25
|
+
const agentRoles = getAgentRoles({
|
|
26
|
+
components
|
|
27
|
+
});
|
|
28
|
+
let configPublicApi = [];
|
|
29
|
+
if (type.isArray(components.auth.api.public)) {
|
|
30
|
+
configPublicApi = components.auth.api.public;
|
|
31
|
+
}
|
|
32
|
+
(components.agents ?? []).forEach((agent)=>{
|
|
33
|
+
if (agentRoles[agent.id]) {
|
|
34
|
+
if (isInPatternList(agent.id, configPublicApi)) {
|
|
35
|
+
throw new ConfigError(`Agent "${agent.id}" is both protected by roles and public.`, {
|
|
36
|
+
received: agentRoles[agent.id],
|
|
37
|
+
configKey: agent['~k']
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
agent.auth = {
|
|
41
|
+
public: false,
|
|
42
|
+
roles: agentRoles[agent.id]
|
|
43
|
+
};
|
|
44
|
+
} else if (protectedAgents.includes(agent.id)) {
|
|
45
|
+
agent.auth = {
|
|
46
|
+
public: false
|
|
47
|
+
};
|
|
48
|
+
} else {
|
|
49
|
+
agent.auth = {
|
|
50
|
+
public: true
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return components;
|
|
55
|
+
}
|
|
56
|
+
export default buildAgentAuth;
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { type } from '@lowdefy/helpers';
|
|
16
16
|
import buildAuthPlugins from './buildAuthPlugins.js';
|
|
17
|
+
import buildAgentAuth from './buildAgentAuth.js';
|
|
17
18
|
import buildApiAuth from './buildApiAuth.js';
|
|
18
19
|
import buildPageAuth from './buildPageAuth.js';
|
|
20
|
+
import rejectReservedEntityIds from './rejectReservedEntityIds.js';
|
|
19
21
|
import validateAuthConfig from './validateAuthConfig.js';
|
|
20
22
|
function buildAuth({ components, context }) {
|
|
21
23
|
const configured = !type.isNone(components.auth);
|
|
@@ -23,11 +25,18 @@ function buildAuth({ components, context }) {
|
|
|
23
25
|
components,
|
|
24
26
|
context
|
|
25
27
|
});
|
|
28
|
+
rejectReservedEntityIds({
|
|
29
|
+
components
|
|
30
|
+
});
|
|
26
31
|
components.auth.configured = configured;
|
|
27
32
|
buildApiAuth({
|
|
28
33
|
components,
|
|
29
34
|
context
|
|
30
35
|
});
|
|
36
|
+
buildAgentAuth({
|
|
37
|
+
components,
|
|
38
|
+
context
|
|
39
|
+
});
|
|
31
40
|
buildPageAuth({
|
|
32
41
|
components,
|
|
33
42
|
context
|
|
@@ -0,0 +1,39 @@
|
|
|
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 { matchesPattern } from './matchPattern.js';
|
|
16
|
+
function getAgentRoles({ components }) {
|
|
17
|
+
const roles = components.auth.api.roles;
|
|
18
|
+
const agentIds = (components.agents ?? []).map((agent)=>agent.id);
|
|
19
|
+
const agentRoles = {};
|
|
20
|
+
Object.keys(roles).forEach((roleName)=>{
|
|
21
|
+
roles[roleName].forEach((pattern)=>{
|
|
22
|
+
agentIds.forEach((agentId)=>{
|
|
23
|
+
if (matchesPattern(agentId, pattern)) {
|
|
24
|
+
if (!agentRoles[agentId]) {
|
|
25
|
+
agentRoles[agentId] = new Set();
|
|
26
|
+
}
|
|
27
|
+
agentRoles[agentId].add(roleName);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
Object.keys(agentRoles).forEach((agentId)=>{
|
|
33
|
+
agentRoles[agentId] = [
|
|
34
|
+
...agentRoles[agentId]
|
|
35
|
+
];
|
|
36
|
+
});
|
|
37
|
+
return agentRoles;
|
|
38
|
+
}
|
|
39
|
+
export default getAgentRoles;
|
|
@@ -0,0 +1,29 @@
|
|
|
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 { isInPatternList } from './matchPattern.js';
|
|
17
|
+
function getProtectedAgents({ components }) {
|
|
18
|
+
const agentIds = (components.agents ?? []).map((agent)=>agent.id);
|
|
19
|
+
let protectedAgents = [];
|
|
20
|
+
if (type.isArray(components.auth.api.public)) {
|
|
21
|
+
protectedAgents = agentIds.filter((agentId)=>!isInPatternList(agentId, components.auth.api.public));
|
|
22
|
+
} else if (components.auth.api.protected === true) {
|
|
23
|
+
protectedAgents = agentIds;
|
|
24
|
+
} else if (type.isArray(components.auth.api.protected)) {
|
|
25
|
+
protectedAgents = agentIds.filter((agentId)=>isInPatternList(agentId, components.auth.api.protected));
|
|
26
|
+
}
|
|
27
|
+
return protectedAgents;
|
|
28
|
+
}
|
|
29
|
+
export default getProtectedAgents;
|
|
@@ -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 { isReserved } from '@lowdefy/helpers';
|
|
16
|
+
import { ConfigError } from '@lowdefy/errors';
|
|
17
|
+
// buildAuth runs before validateId sees page, endpoint and agent ids, and it keys
|
|
18
|
+
// plain-object role maps on them: getPageRoles, getApiRoles and getAgentRoles all
|
|
19
|
+
// build `roles[id]` and read it back with a truthiness check. A reserved id reads
|
|
20
|
+
// through Object.prototype there, so the entity is silently marked protected with
|
|
21
|
+
// Object.prototype as its roles - and the object then travels into addKeys, which
|
|
22
|
+
// stamps a '~k' onto Object.prototype and corrupts every plain object in the
|
|
23
|
+
// process. Gate the ids here, where buildAuth first touches them.
|
|
24
|
+
//
|
|
25
|
+
// validateId reports the same ids again in buildApi, buildAgents and buildPages;
|
|
26
|
+
// logCollectedErrors collapses the pair on source line plus message so the author
|
|
27
|
+
// reads it once.
|
|
28
|
+
function rejectReservedEntityIds({ components }) {
|
|
29
|
+
const entityTypes = [
|
|
30
|
+
{
|
|
31
|
+
items: components.pages,
|
|
32
|
+
field: 'Page id'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
items: components.api,
|
|
36
|
+
field: 'Endpoint id'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
items: components.agents,
|
|
40
|
+
field: 'Agent id'
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
entityTypes.forEach(({ items, field })=>{
|
|
44
|
+
(items ?? []).forEach((item)=>{
|
|
45
|
+
if (!isReserved(item.id)) return;
|
|
46
|
+
throw new ConfigError(`${field} "${item.id}" is a reserved name and cannot be used as an id.`, {
|
|
47
|
+
configKey: item['~k']
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export default rejectReservedEntityIds;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
|
-
*/ import { type } from '@lowdefy/helpers';
|
|
15
|
+
*/ import { isReserved, type } from '@lowdefy/helpers';
|
|
16
16
|
import { ConfigError, ConfigWarning } from '@lowdefy/errors';
|
|
17
17
|
import createCheckDuplicateId from '../../../utils/createCheckDuplicateId.js';
|
|
18
18
|
const BROWSER_DEFAULT_SHORTCUTS = new Set([
|
|
@@ -175,6 +175,16 @@ function buildEvents(block, pageContext) {
|
|
|
175
175
|
configKey: eventConfigKey
|
|
176
176
|
});
|
|
177
177
|
}
|
|
178
|
+
// The client's shortcut manager keys a plain object by the normalized
|
|
179
|
+
// shortcut. Normalization neither creates nor removes a reserved name:
|
|
180
|
+
// multi-character key names pass through unchanged, and no reserved
|
|
181
|
+
// name is a single character. So checking the raw string is enough, and
|
|
182
|
+
// a modified form like "Ctrl+__proto__" stays valid.
|
|
183
|
+
if (isReserved(shortcut)) {
|
|
184
|
+
throw new ConfigError(`Event shortcut "${shortcut}" on event "${key}" on block "${block.blockId}" on page "${pageContext.pageId}" is a reserved name and cannot be used as a shortcut.`, {
|
|
185
|
+
configKey: eventConfigKey
|
|
186
|
+
});
|
|
187
|
+
}
|
|
178
188
|
if (BROWSER_DEFAULT_SHORTCUTS.has(shortcut.toLowerCase())) {
|
|
179
189
|
pageContext.context.handleWarning(new ConfigWarning(`Shortcut "${shortcut}" on event "${key}" on block "${block.blockId}" on page "${pageContext.pageId}" conflicts with a browser default.`, {
|
|
180
190
|
configKey: eventConfigKey
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
|
-
*/ import { type } from '@lowdefy/helpers';
|
|
15
|
+
*/ import { isReserved, splitPath, type } from '@lowdefy/helpers';
|
|
16
16
|
import { ConfigError } from '@lowdefy/errors';
|
|
17
17
|
function validateBlock(block, { pageId }, parentConfigKey) {
|
|
18
18
|
const configKey = block?.['~k'] ?? parentConfigKey;
|
|
@@ -33,6 +33,17 @@ function validateBlock(block, { pageId }, parentConfigKey) {
|
|
|
33
33
|
configKey
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
+
// Block ids are dot-paths that nest state, so they bypass validateId's shape gate. Every segment
|
|
37
|
+
// still becomes an object key on the way through get/set, so check per segment: a block
|
|
38
|
+
// `id: a.constructor.b` would reach `get` with a reserved segment and throw mid-render, which
|
|
39
|
+
// ErrorBoundary cannot catch because the throw happens during the render that would mount it.
|
|
40
|
+
const reservedSegment = splitPath(block.id).find(isReserved);
|
|
41
|
+
if (!type.isNone(reservedSegment)) {
|
|
42
|
+
throw new ConfigError(`Block id "${block.id}" uses reserved name "${reservedSegment}" as a path segment, at page "${pageId}".`, {
|
|
43
|
+
received: block.id,
|
|
44
|
+
configKey
|
|
45
|
+
});
|
|
46
|
+
}
|
|
36
47
|
if (type.isNone(block.type)) {
|
|
37
48
|
throw new ConfigError(`Block type is not defined at "${block.id}" on page "${pageId}".`, {
|
|
38
49
|
configKey
|
|
@@ -12,12 +12,23 @@
|
|
|
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 { get } from '@lowdefy/helpers';
|
|
16
|
-
|
|
15
|
+
*/ import { get, ReservedKeyError } from '@lowdefy/helpers';
|
|
16
|
+
import { ConfigError } from '@lowdefy/errors';
|
|
17
|
+
function getKey({ input, refDef, filePath }) {
|
|
17
18
|
if (refDef.key) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
try {
|
|
20
|
+
return get(input, refDef.key, {
|
|
21
|
+
default: null
|
|
22
|
+
});
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (!(error instanceof ReservedKeyError)) throw error;
|
|
25
|
+
// The ref key is author-written YAML, so a reserved name is a config mistake. Without
|
|
26
|
+
// this the bare ReservedKeyError reaches the build with no file and no line.
|
|
27
|
+
throw new ConfigError(`_ref key "${refDef.key}" is a reserved name.`, {
|
|
28
|
+
cause: error,
|
|
29
|
+
filePath
|
|
30
|
+
});
|
|
31
|
+
}
|
|
21
32
|
}
|
|
22
33
|
return input;
|
|
23
34
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import path from 'path';
|
|
16
|
-
import { get, serializer, type } from '@lowdefy/helpers';
|
|
16
|
+
import { get, ReservedKeyError, serializer, type } from '@lowdefy/helpers';
|
|
17
17
|
import { ConfigError } from '@lowdefy/errors';
|
|
18
18
|
import { evaluateOperators } from '@lowdefy/operators';
|
|
19
19
|
import makeRefDefinition from './makeRefDefinition.js';
|
|
@@ -203,19 +203,35 @@ function evaluateBuildOperator(node, ctx) {
|
|
|
203
203
|
}
|
|
204
204
|
return output;
|
|
205
205
|
}
|
|
206
|
+
// _var keys are author-written YAML, so a reserved name (e.g. "constructor") is a config
|
|
207
|
+
// mistake, not a missing var. Wraps both call shapes for that check. The object form call
|
|
208
|
+
// site below passes no `options` argument at all, so a missing key comes back from `get` as
|
|
209
|
+
// `undefined` instead of a default — resolveVar relies on that `undefined` to tell "var not
|
|
210
|
+
// provided" apart from "var provided as null".
|
|
211
|
+
function readVar(key, ctx, options) {
|
|
212
|
+
try {
|
|
213
|
+
return get(ctx.vars, key, options);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (!(error instanceof ReservedKeyError)) throw error;
|
|
216
|
+
throw new ConfigError(`_var key "${key}" is a reserved name.`, {
|
|
217
|
+
cause: error,
|
|
218
|
+
filePath: ctx.currentFile
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
206
222
|
// Resolve a _var node
|
|
207
223
|
function resolveVar(node, ctx) {
|
|
208
224
|
const varDef = node._var;
|
|
209
225
|
// String form: { _var: "key" }
|
|
210
226
|
if (type.isString(varDef)) {
|
|
211
|
-
const value =
|
|
227
|
+
const value = readVar(varDef, ctx, {
|
|
212
228
|
default: null
|
|
213
229
|
});
|
|
214
230
|
return cloneVarValue(value, ctx.sourceRefId);
|
|
215
231
|
}
|
|
216
232
|
// Object form: { _var: { key, default } }
|
|
217
233
|
if (type.isObject(varDef) && type.isString(varDef.key)) {
|
|
218
|
-
const varFromParent =
|
|
234
|
+
const varFromParent = readVar(varDef.key, ctx);
|
|
219
235
|
// Var provided (even if null) → use parent's sourceRefId for location
|
|
220
236
|
if (!type.isUndefined(varFromParent)) {
|
|
221
237
|
return cloneVarValue(varFromParent, ctx.sourceRefId);
|
|
@@ -610,7 +626,8 @@ async function resolveRef(node, ctx) {
|
|
|
610
626
|
// 14. Extract key
|
|
611
627
|
content = getKey({
|
|
612
628
|
input: content,
|
|
613
|
-
refDef
|
|
629
|
+
refDef,
|
|
630
|
+
filePath: ctx.currentFile
|
|
614
631
|
});
|
|
615
632
|
// 15. Tag all nodes with ~r for provenance
|
|
616
633
|
tagRefDeep(content, refDef.id);
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import fs from 'node:fs';
|
|
16
16
|
import path from 'node:path';
|
|
17
|
+
import { ReservedKeyError, setKey } from '@lowdefy/helpers';
|
|
17
18
|
import { ConfigError } from '@lowdefy/errors';
|
|
18
19
|
import fetchGitHubModule from './fetchGitHubModule.js';
|
|
19
20
|
import parseModuleSource from './parseModuleSource.js';
|
|
@@ -28,6 +29,19 @@ function findGitRoot(startPath) {
|
|
|
28
29
|
dir = parent;
|
|
29
30
|
}
|
|
30
31
|
}
|
|
32
|
+
// Module ids come from the app's lowdefy.yaml, so they key this map from user
|
|
33
|
+
// config. Surface a reserved id as the build's ConfigError rather than a bare
|
|
34
|
+
// ReservedKeyError.
|
|
35
|
+
function setResolvedPaths(resolved, id, paths) {
|
|
36
|
+
try {
|
|
37
|
+
setKey(resolved, id, paths);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (!(error instanceof ReservedKeyError)) throw error;
|
|
40
|
+
throw new ConfigError(`Module entry id "${id}" is a reserved name.`, {
|
|
41
|
+
cause: error
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
31
45
|
async function fetchModules({ moduleEntries, context }) {
|
|
32
46
|
const resolved = {};
|
|
33
47
|
for (const entry of moduleEntries){
|
|
@@ -37,22 +51,22 @@ async function fetchModules({ moduleEntries, context }) {
|
|
|
37
51
|
if (!fs.existsSync(path.join(resolvedPath, 'module.lowdefy.yaml'))) {
|
|
38
52
|
throw new ConfigError(`Module "${entry.id}": module.lowdefy.yaml not found at ${resolvedPath}`);
|
|
39
53
|
}
|
|
40
|
-
resolved
|
|
54
|
+
setResolvedPaths(resolved, entry.id, {
|
|
41
55
|
packageRoot: findGitRoot(resolvedPath) ?? resolvedPath,
|
|
42
56
|
moduleRoot: resolvedPath,
|
|
43
57
|
isLocal: true
|
|
44
|
-
};
|
|
58
|
+
});
|
|
45
59
|
} else if (source.type === 'github') {
|
|
46
60
|
const cached = await fetchGitHubModule(source, context);
|
|
47
61
|
const moduleRoot = source.path ? path.join(cached.packageRoot, source.path) : cached.packageRoot;
|
|
48
62
|
if (!fs.existsSync(path.join(moduleRoot, 'module.lowdefy.yaml'))) {
|
|
49
63
|
throw new ConfigError(`Module "${entry.id}": module.lowdefy.yaml not found at path "${source.path || '/'}" in ${source.owner}/${source.repo}@${source.ref}`);
|
|
50
64
|
}
|
|
51
|
-
resolved
|
|
65
|
+
setResolvedPaths(resolved, entry.id, {
|
|
52
66
|
packageRoot: cached.packageRoot,
|
|
53
67
|
moduleRoot,
|
|
54
68
|
isLocal: false
|
|
55
|
-
};
|
|
69
|
+
});
|
|
56
70
|
}
|
|
57
71
|
}
|
|
58
72
|
return resolved;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import path from 'path';
|
|
16
16
|
import semver from 'semver';
|
|
17
|
-
import { type } from '@lowdefy/helpers';
|
|
17
|
+
import { ReservedKeyError, setKey, type } from '@lowdefy/helpers';
|
|
18
18
|
import { ConfigError } from '@lowdefy/errors';
|
|
19
19
|
import operators from '@lowdefy/operators-js/operators/build';
|
|
20
20
|
import { resolve, WalkContext } from './buildRefs/walker.js';
|
|
@@ -69,9 +69,6 @@ async function resolveLocalManifest({ entry, resolvedPaths, context }) {
|
|
|
69
69
|
if (entry.id.includes('/')) {
|
|
70
70
|
throw new ConfigError(`Module entry id "${entry.id}" must not contain '/'. ` + `Use a flat identifier like "team-users".`);
|
|
71
71
|
}
|
|
72
|
-
if (entry.id === '__proto__' || entry.id === 'constructor' || entry.id === 'prototype') {
|
|
73
|
-
throw new ConfigError(`Module entry id "${entry.id}" is a reserved name.`);
|
|
74
|
-
}
|
|
75
72
|
if (!entry.source || !type.isString(entry.source)) {
|
|
76
73
|
throw new ConfigError(`Module entry "${entry.id}": 'source' is required and must be a string.`);
|
|
77
74
|
}
|
|
@@ -151,21 +148,30 @@ async function resolveLocalManifest({ entry, resolvedPaths, context }) {
|
|
|
151
148
|
throw new ConfigError(`Module "${entry.id}" requires plugin "${plugin.name}" version "${plugin.version}" ` + `but the app has version "${appVersion}" installed. ` + `Update the plugin to a compatible version.`);
|
|
152
149
|
}
|
|
153
150
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
151
|
+
try {
|
|
152
|
+
setKey(context.modules, entry.id, {
|
|
153
|
+
id: entry.id,
|
|
154
|
+
source: entry.source,
|
|
155
|
+
packageRoot,
|
|
156
|
+
moduleRoot,
|
|
157
|
+
isLocal,
|
|
158
|
+
consumerVars: entry.vars ?? {},
|
|
159
|
+
varDefs,
|
|
160
|
+
resolvedVarCache: {},
|
|
161
|
+
connections: entry.connections ?? {},
|
|
162
|
+
manifest,
|
|
163
|
+
dependencies,
|
|
164
|
+
moduleDependencies: entry.dependencies ?? {},
|
|
165
|
+
refDef
|
|
166
|
+
});
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (error instanceof ReservedKeyError) {
|
|
169
|
+
throw new ConfigError(`Module entry id "${entry.id}" is a reserved name.`, {
|
|
170
|
+
cause: error
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
169
175
|
}
|
|
170
176
|
async function resolveFullManifest({ entryId, context }) {
|
|
171
177
|
const moduleEntry = context.modules[entryId];
|
package/dist/build/writeI18n.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { ConfigError, ConfigWarning } from '@lowdefy/errors';
|
|
16
|
-
import { mergeObjects, serializer, type } from '@lowdefy/helpers';
|
|
16
|
+
import { isReserved, mergeObjects, serializer, type } from '@lowdefy/helpers';
|
|
17
17
|
function collectPluginMessages({ messagesMap, declaredCodes }) {
|
|
18
18
|
const collected = {};
|
|
19
19
|
Object.values(messagesMap ?? {}).forEach((pluginCatalog)=>{
|
|
@@ -57,6 +57,13 @@ async function writeI18n({ components, context }) {
|
|
|
57
57
|
configKey: locale?.['~k'] ?? i18n['~k']
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
|
+
// The code keys the collected plugin catalog and the merged messages
|
|
61
|
+
// artifact, both plain objects.
|
|
62
|
+
if (isReserved(locale.code)) {
|
|
63
|
+
throw new ConfigError(`Locale code "${locale.code}" in "config.i18n.locales" is a reserved name and cannot be used as a locale code.`, {
|
|
64
|
+
configKey: locale['~k']
|
|
65
|
+
});
|
|
66
|
+
}
|
|
60
67
|
if (codes.has(locale.code)) {
|
|
61
68
|
throw new ConfigError(`Duplicate locale code "${locale.code}" in "config.i18n.locales".`, {
|
|
62
69
|
configKey: locale['~k']
|