@nocobase/flow-engine 2.0.0-beta.21 → 2.0.0-beta.23
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/lib/FlowDefinition.d.ts +2 -0
- package/lib/JSRunner.js +23 -1
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +66 -13
- package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +24 -4
- package/lib/components/variables/VariableInput.js +1 -2
- package/lib/components/variables/VariableTag.js +46 -39
- package/lib/data-source/index.js +11 -5
- package/lib/flowContext.js +5 -1
- package/lib/flowI18n.js +6 -4
- package/lib/locale/en-US.json +2 -1
- package/lib/locale/index.d.ts +2 -0
- package/lib/locale/zh-CN.json +1 -0
- package/lib/resources/sqlResource.d.ts +3 -3
- package/lib/types.d.ts +12 -0
- package/lib/utils/associationObjectVariable.d.ts +2 -2
- package/lib/utils/index.d.ts +4 -2
- package/lib/utils/index.js +16 -0
- package/lib/utils/resolveRunJSObjectValues.d.ts +16 -0
- package/lib/utils/resolveRunJSObjectValues.js +61 -0
- package/lib/utils/runjsValue.d.ts +29 -0
- package/lib/utils/runjsValue.js +275 -0
- package/lib/utils/safeGlobals.d.ts +14 -0
- package/lib/utils/safeGlobals.js +37 -2
- package/lib/utils/schema-utils.d.ts +10 -0
- package/lib/utils/schema-utils.js +61 -0
- package/package.json +4 -4
- package/src/JSRunner.ts +29 -1
- package/src/__tests__/JSRunner.test.ts +64 -0
- package/src/__tests__/flowContext.test.ts +78 -0
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +99 -14
- package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +41 -7
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +94 -1
- package/src/components/variables/VariableInput.tsx +4 -2
- package/src/components/variables/VariableTag.tsx +54 -45
- package/src/components/variables/__tests__/VariableTag.test.tsx +50 -0
- package/src/data-source/index.ts +11 -5
- package/src/flowContext.ts +6 -2
- package/src/flowI18n.ts +7 -5
- package/src/locale/en-US.json +2 -1
- package/src/locale/zh-CN.json +1 -0
- package/src/resources/sqlResource.ts +3 -3
- package/src/types.ts +12 -0
- package/src/utils/__tests__/runjsValue.test.ts +44 -0
- package/src/utils/__tests__/safeGlobals.test.ts +8 -0
- package/src/utils/__tests__/utils.test.ts +95 -0
- package/src/utils/associationObjectVariable.ts +2 -2
- package/src/utils/index.ts +20 -2
- package/src/utils/resolveRunJSObjectValues.ts +46 -0
- package/src/utils/runjsValue.ts +287 -0
- package/src/utils/safeGlobals.ts +55 -1
- package/src/utils/schema-utils.ts +79 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type RunJSValue = {
|
|
11
|
+
code: string;
|
|
12
|
+
version?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const RUNJS_ALLOWED_KEYS = new Set(['code', 'version']);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Strictly detect RunJSValue to avoid conflicting with normal constant objects.
|
|
19
|
+
* - MUST be a plain object (not array)
|
|
20
|
+
* - MUST include string `code`
|
|
21
|
+
* - MAY include string `version`
|
|
22
|
+
* - MUST NOT include any other enumerable keys
|
|
23
|
+
*/
|
|
24
|
+
export function isRunJSValue(value: any): value is RunJSValue {
|
|
25
|
+
if (!value || typeof value !== 'object') return false;
|
|
26
|
+
if (Array.isArray(value)) return false;
|
|
27
|
+
const keys = Object.keys(value);
|
|
28
|
+
if (!keys.includes('code')) return false;
|
|
29
|
+
if (typeof (value as any).code !== 'string') return false;
|
|
30
|
+
if ('version' in value && (value as any).version != null && typeof (value as any).version !== 'string') return false;
|
|
31
|
+
for (const k of keys) {
|
|
32
|
+
if (!RUNJS_ALLOWED_KEYS.has(k)) return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function normalizeRunJSValue(value: RunJSValue): Required<RunJSValue> {
|
|
38
|
+
return {
|
|
39
|
+
code: String(value?.code ?? ''),
|
|
40
|
+
version: String(value?.version ?? 'v1'),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function stripStringsAndComments(code: string): string {
|
|
45
|
+
// Keep template literals untouched (may include ${} expressions).
|
|
46
|
+
let out = '';
|
|
47
|
+
let state: 'code' | 'single' | 'double' | 'line' | 'block' = 'code';
|
|
48
|
+
for (let i = 0; i < code.length; i++) {
|
|
49
|
+
const ch = code[i];
|
|
50
|
+
const next = i + 1 < code.length ? code[i + 1] : '';
|
|
51
|
+
|
|
52
|
+
if (state === 'code') {
|
|
53
|
+
if (ch === '/' && next === '/') {
|
|
54
|
+
out += ' ';
|
|
55
|
+
i++;
|
|
56
|
+
state = 'line';
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (ch === '/' && next === '*') {
|
|
60
|
+
out += ' ';
|
|
61
|
+
i++;
|
|
62
|
+
state = 'block';
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (ch === "'") {
|
|
66
|
+
out += ' ';
|
|
67
|
+
state = 'single';
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (ch === '"') {
|
|
71
|
+
out += ' ';
|
|
72
|
+
state = 'double';
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
out += ch;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (state === 'line') {
|
|
80
|
+
if (ch === '\n') {
|
|
81
|
+
out += '\n';
|
|
82
|
+
state = 'code';
|
|
83
|
+
} else {
|
|
84
|
+
out += ' ';
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (state === 'block') {
|
|
90
|
+
if (ch === '*' && next === '/') {
|
|
91
|
+
out += ' ';
|
|
92
|
+
i++;
|
|
93
|
+
state = 'code';
|
|
94
|
+
} else {
|
|
95
|
+
out += ch === '\n' ? '\n' : ' ';
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (state === 'single') {
|
|
101
|
+
if (ch === '\\') {
|
|
102
|
+
out += ' ';
|
|
103
|
+
i++;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (ch === "'") {
|
|
107
|
+
out += ' ';
|
|
108
|
+
state = 'code';
|
|
109
|
+
} else {
|
|
110
|
+
out += ch === '\n' ? '\n' : ' ';
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// state === 'double'
|
|
116
|
+
if (ch === '\\') {
|
|
117
|
+
out += ' ';
|
|
118
|
+
i++;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (ch === '"') {
|
|
122
|
+
out += ' ';
|
|
123
|
+
state = 'code';
|
|
124
|
+
} else {
|
|
125
|
+
out += ch === '\n' ? '\n' : ' ';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function stripComments(code: string): string {
|
|
132
|
+
let out = '';
|
|
133
|
+
let state: 'code' | 'single' | 'double' | 'line' | 'block' = 'code';
|
|
134
|
+
for (let i = 0; i < code.length; i++) {
|
|
135
|
+
const ch = code[i];
|
|
136
|
+
const next = i + 1 < code.length ? code[i + 1] : '';
|
|
137
|
+
|
|
138
|
+
if (state === 'code') {
|
|
139
|
+
if (ch === '/' && next === '/') {
|
|
140
|
+
out += ' ';
|
|
141
|
+
i++;
|
|
142
|
+
state = 'line';
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (ch === '/' && next === '*') {
|
|
146
|
+
out += ' ';
|
|
147
|
+
i++;
|
|
148
|
+
state = 'block';
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (ch === "'") {
|
|
152
|
+
out += ch;
|
|
153
|
+
state = 'single';
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (ch === '"') {
|
|
157
|
+
out += ch;
|
|
158
|
+
state = 'double';
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
out += ch;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (state === 'line') {
|
|
166
|
+
if (ch === '\n') {
|
|
167
|
+
out += '\n';
|
|
168
|
+
state = 'code';
|
|
169
|
+
} else {
|
|
170
|
+
out += ' ';
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (state === 'block') {
|
|
176
|
+
if (ch === '*' && next === '/') {
|
|
177
|
+
out += ' ';
|
|
178
|
+
i++;
|
|
179
|
+
state = 'code';
|
|
180
|
+
} else {
|
|
181
|
+
out += ch === '\n' ? '\n' : ' ';
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (state === 'single') {
|
|
187
|
+
out += ch;
|
|
188
|
+
if (ch === '\\') {
|
|
189
|
+
const nextCh = i + 1 < code.length ? code[i + 1] : '';
|
|
190
|
+
if (nextCh) {
|
|
191
|
+
out += nextCh;
|
|
192
|
+
i++;
|
|
193
|
+
}
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (ch === "'") {
|
|
197
|
+
state = 'code';
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// state === 'double'
|
|
203
|
+
out += ch;
|
|
204
|
+
if (ch === '\\') {
|
|
205
|
+
const nextCh = i + 1 < code.length ? code[i + 1] : '';
|
|
206
|
+
if (nextCh) {
|
|
207
|
+
out += nextCh;
|
|
208
|
+
i++;
|
|
209
|
+
}
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (ch === '"') {
|
|
213
|
+
state = 'code';
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function normalizeSubPath(raw: string): { subPath: string; wildcard: boolean } {
|
|
220
|
+
if (!raw) return { subPath: '', wildcard: false };
|
|
221
|
+
let s = raw;
|
|
222
|
+
// Convert simple string literal keys: ['a'] / ["a"] -> .a
|
|
223
|
+
s = s.replace(/\[['"]([a-zA-Z_$][a-zA-Z0-9_$]*)['"]\]/g, '.$1');
|
|
224
|
+
|
|
225
|
+
// Any remaining bracket access with non-numeric content is considered dynamic -> wildcard.
|
|
226
|
+
const bracketRe = /\[([^\]]+)\]/g;
|
|
227
|
+
let m: RegExpExecArray | null;
|
|
228
|
+
while ((m = bracketRe.exec(s))) {
|
|
229
|
+
const inner = String(m[1] ?? '').trim();
|
|
230
|
+
if (/^\d+$/.test(inner)) continue;
|
|
231
|
+
if (/^['"][a-zA-Z_$][a-zA-Z0-9_$]*['"]$/.test(inner)) continue;
|
|
232
|
+
return { subPath: s.startsWith('.') ? s.slice(1) : s, wildcard: true };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (s.startsWith('.')) s = s.slice(1);
|
|
236
|
+
return { subPath: s, wildcard: false };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Heuristic extraction of ctx variable usage from RunJS code.
|
|
241
|
+
*
|
|
242
|
+
* Returns a map: varName -> string[] subPaths
|
|
243
|
+
* - subPath '' means the variable root is used (or dependency is dynamic), caller MAY treat it as wildcard.
|
|
244
|
+
* - Only best-effort parsing; correctness prefers over-approximation.
|
|
245
|
+
*/
|
|
246
|
+
export function extractUsedVariablePathsFromRunJS(code: string): Record<string, string[]> {
|
|
247
|
+
if (typeof code !== 'string' || !code.trim()) return {};
|
|
248
|
+
const src = stripStringsAndComments(code);
|
|
249
|
+
const srcWithStrings = stripComments(code);
|
|
250
|
+
const usage = new Map<string, Set<string>>();
|
|
251
|
+
|
|
252
|
+
const add = (varName: string, subPath: string) => {
|
|
253
|
+
if (!varName) return;
|
|
254
|
+
const set = usage.get(varName) || new Set<string>();
|
|
255
|
+
set.add(subPath || '');
|
|
256
|
+
usage.set(varName, set);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// dot form: ctx.foo.bar / ctx.foo[0].bar (excluding ctx.method(...))
|
|
260
|
+
const dotRe = /ctx\.([a-zA-Z_$][a-zA-Z0-9_$]*(?:(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)|(?:\[[^\]]+\]))*)(?!\s*\()/g;
|
|
261
|
+
let match: RegExpExecArray | null;
|
|
262
|
+
while ((match = dotRe.exec(src))) {
|
|
263
|
+
const pathAfterCtx = match[1] || '';
|
|
264
|
+
const firstKeyMatch = pathAfterCtx.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)/);
|
|
265
|
+
if (!firstKeyMatch) continue;
|
|
266
|
+
const firstKey = firstKeyMatch[1];
|
|
267
|
+
const rest = pathAfterCtx.slice(firstKey.length);
|
|
268
|
+
const { subPath, wildcard } = normalizeSubPath(rest);
|
|
269
|
+
add(firstKey, wildcard ? '' : subPath);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// bracket root: ctx['foo'].bar / ctx["foo"][0] (excluding ctx['method'](...))
|
|
273
|
+
const bracketRootRe =
|
|
274
|
+
/ctx\s*\[\s*(['"])([a-zA-Z_$][a-zA-Z0-9_$]*)\1\s*\]((?:(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)|(?:\[[^\]]+\]))*)(?!\s*\()/g;
|
|
275
|
+
while ((match = bracketRootRe.exec(srcWithStrings))) {
|
|
276
|
+
const varName = match[2] || '';
|
|
277
|
+
const rest = match[3] || '';
|
|
278
|
+
const { subPath, wildcard } = normalizeSubPath(rest);
|
|
279
|
+
add(varName, wildcard ? '' : subPath);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const out: Record<string, string[]> = {};
|
|
283
|
+
for (const [k, set] of usage.entries()) {
|
|
284
|
+
out[k] = Array.from(set);
|
|
285
|
+
}
|
|
286
|
+
return out;
|
|
287
|
+
}
|
package/src/utils/safeGlobals.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* 统一的安全全局对象代理:window/document/navigator
|
|
12
|
-
* - window:仅允许常用的定时器、console、Math、Date、FormData、addEventListener、open(安全包装)、location(安全代理)
|
|
12
|
+
* - window:仅允许常用的定时器、console、Math、Date、FormData、Blob、URL、addEventListener、open(安全包装)、location(安全代理)
|
|
13
13
|
* - document:仅允许 createElement/querySelector/querySelectorAll
|
|
14
14
|
* - navigator:仅提供极少量低风险能力(clipboard.writeText、onLine、language、languages)
|
|
15
15
|
* - 不允许随意访问未声明的属性,最小权限原则
|
|
@@ -211,6 +211,8 @@ export function createSafeWindow(extra?: Record<string, any>) {
|
|
|
211
211
|
Math,
|
|
212
212
|
Date,
|
|
213
213
|
FormData,
|
|
214
|
+
...(typeof Blob !== 'undefined' ? { Blob } : {}),
|
|
215
|
+
...(typeof URL !== 'undefined' ? { URL } : {}),
|
|
214
216
|
// 事件侦听仅绑定到真实 window,便于少量需要的全局监听
|
|
215
217
|
addEventListener: addEventListener.bind(window),
|
|
216
218
|
// 安全的 window.open 代理
|
|
@@ -350,3 +352,55 @@ export function createSafeNavigator(extra?: Record<string, any>) {
|
|
|
350
352
|
},
|
|
351
353
|
);
|
|
352
354
|
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Create a safe globals object for RunJS execution.
|
|
358
|
+
*
|
|
359
|
+
* - Always tries to provide `navigator`
|
|
360
|
+
* - Best-effort provides `window` and `document` in browser environments
|
|
361
|
+
* - Never throws (so callers can decide how to handle missing globals)
|
|
362
|
+
*/
|
|
363
|
+
export function createSafeRunJSGlobals(extraGlobals?: Record<string, any>): Record<string, any> {
|
|
364
|
+
const globals: Record<string, any> = {};
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const navigator = createSafeNavigator();
|
|
368
|
+
globals.navigator = navigator;
|
|
369
|
+
try {
|
|
370
|
+
globals.window = createSafeWindow({ navigator });
|
|
371
|
+
} catch {
|
|
372
|
+
// ignore when window is not available (e.g. SSR/tests)
|
|
373
|
+
}
|
|
374
|
+
} catch {
|
|
375
|
+
// ignore
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
globals.document = createSafeDocument();
|
|
380
|
+
} catch {
|
|
381
|
+
// ignore when document is not available (e.g. SSR/tests)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return extraGlobals ? { ...globals, ...extraGlobals } : globals;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Execute RunJS with safe globals (window/document/navigator).
|
|
389
|
+
*
|
|
390
|
+
* Keeps `this` binding by calling `ctx.runjs(...)` instead of passing bare function references.
|
|
391
|
+
*/
|
|
392
|
+
export async function runjsWithSafeGlobals(
|
|
393
|
+
ctx: unknown,
|
|
394
|
+
code: string,
|
|
395
|
+
options?: any,
|
|
396
|
+
extraGlobals?: Record<string, any>,
|
|
397
|
+
): Promise<any> {
|
|
398
|
+
if (!ctx || (typeof ctx !== 'object' && typeof ctx !== 'function')) return undefined;
|
|
399
|
+
const runjs = (ctx as { runjs?: unknown }).runjs;
|
|
400
|
+
if (typeof runjs !== 'function') return undefined;
|
|
401
|
+
return (ctx as { runjs: (code: string, variables?: Record<string, any>, options?: any) => Promise<any> }).runjs(
|
|
402
|
+
code,
|
|
403
|
+
createSafeRunJSGlobals(extraGlobals),
|
|
404
|
+
options,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
@@ -277,3 +277,82 @@ export async function shouldHideStepInSettings<TModel extends FlowModel = FlowMo
|
|
|
277
277
|
|
|
278
278
|
return !!hideInSettings;
|
|
279
279
|
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 解析步骤在设置菜单中的禁用状态与提示文案。
|
|
283
|
+
* - 支持 StepDefinition.disabledInSettings 与 ActionDefinition.disabledInSettings(step 优先)。
|
|
284
|
+
* - 支持 StepDefinition.disabledReasonInSettings 与 ActionDefinition.disabledReasonInSettings(step 优先)。
|
|
285
|
+
* - 以上属性均支持静态值与函数(接收 FlowRuntimeContext)。
|
|
286
|
+
*/
|
|
287
|
+
export async function resolveStepDisabledInSettings<TModel extends FlowModel = FlowModel>(
|
|
288
|
+
model: TModel,
|
|
289
|
+
flow: any,
|
|
290
|
+
step: StepDefinition,
|
|
291
|
+
): Promise<{ disabled: boolean; reason?: string }> {
|
|
292
|
+
if (!step) return { disabled: false };
|
|
293
|
+
|
|
294
|
+
let disabledInSettings = step.disabledInSettings;
|
|
295
|
+
let disabledReasonInSettings = step.disabledReasonInSettings;
|
|
296
|
+
|
|
297
|
+
if ((typeof disabledInSettings === 'undefined' || typeof disabledReasonInSettings === 'undefined') && step.use) {
|
|
298
|
+
try {
|
|
299
|
+
const action = model.getAction?.(step.use);
|
|
300
|
+
if (typeof disabledInSettings === 'undefined') {
|
|
301
|
+
disabledInSettings = action?.disabledInSettings;
|
|
302
|
+
}
|
|
303
|
+
if (typeof disabledReasonInSettings === 'undefined') {
|
|
304
|
+
disabledReasonInSettings = action?.disabledReasonInSettings;
|
|
305
|
+
}
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.warn(`Failed to get action ${step.use}:`, error);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let ctx: FlowRuntimeContext<TModel> | null = null;
|
|
312
|
+
const getContext = () => {
|
|
313
|
+
if (ctx) return ctx;
|
|
314
|
+
ctx = new FlowRuntimeContext(model, flow.key, 'settings');
|
|
315
|
+
setupRuntimeContextSteps(ctx, flow.steps, model, flow.key);
|
|
316
|
+
ctx.defineProperty('currentStep', { value: step });
|
|
317
|
+
return ctx;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
let disabled = false;
|
|
321
|
+
if (typeof disabledInSettings === 'function') {
|
|
322
|
+
try {
|
|
323
|
+
disabled = !!(await disabledInSettings(getContext() as any));
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.warn(`Error evaluating disabledInSettings for step '${step.key || ''}' in flow '${flow.key}':`, error);
|
|
326
|
+
return { disabled: false };
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
disabled = !!disabledInSettings;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (!disabled) {
|
|
333
|
+
return { disabled: false };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
let reason: string | undefined;
|
|
337
|
+
if (typeof disabledReasonInSettings === 'function') {
|
|
338
|
+
try {
|
|
339
|
+
const resolved = await disabledReasonInSettings(getContext() as any);
|
|
340
|
+
if (typeof resolved !== 'undefined' && resolved !== null && resolved !== '') {
|
|
341
|
+
reason = String(resolved);
|
|
342
|
+
}
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.warn(
|
|
345
|
+
`Error evaluating disabledReasonInSettings for step '${step.key || ''}' in flow '${flow.key}':`,
|
|
346
|
+
error,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
} else if (
|
|
350
|
+
typeof disabledReasonInSettings !== 'undefined' &&
|
|
351
|
+
disabledReasonInSettings !== null &&
|
|
352
|
+
disabledReasonInSettings !== ''
|
|
353
|
+
) {
|
|
354
|
+
reason = String(disabledReasonInSettings);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return { disabled: true, reason };
|
|
358
|
+
}
|