@nocobase/flow-engine 2.0.10 → 2.0.11
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/JSRunner.d.ts +10 -1
- package/lib/JSRunner.js +50 -5
- package/lib/flowContext.js +4 -1
- package/lib/runjs-context/registry.d.ts +1 -1
- package/lib/runjs-context/setup.js +19 -12
- package/package.json +4 -4
- package/src/JSRunner.ts +68 -4
- package/src/__tests__/JSRunner.test.ts +27 -1
- package/src/__tests__/runjsContext.test.ts +13 -0
- package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
- package/src/flowContext.ts +5 -3
- package/src/runjs-context/registry.ts +1 -1
- package/src/runjs-context/setup.ts +21 -12
package/lib/JSRunner.d.ts
CHANGED
|
@@ -13,11 +13,20 @@ export interface JSRunnerOptions {
|
|
|
13
13
|
version?: string;
|
|
14
14
|
/**
|
|
15
15
|
* Enable RunJS template compatibility preprocessing for `{{ ... }}`.
|
|
16
|
-
* When enabled
|
|
16
|
+
* When enabled (or falling back to version default),
|
|
17
17
|
* the code will be rewritten to call `ctx.resolveJsonTemplate(...)` at runtime.
|
|
18
18
|
*/
|
|
19
19
|
preprocessTemplates?: boolean;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Decide whether RunJS `{{ ... }}` compatibility preprocessing should run.
|
|
23
|
+
*
|
|
24
|
+
* Priority:
|
|
25
|
+
* 1. Explicit `preprocessTemplates` option always wins.
|
|
26
|
+
* 2. Otherwise, `version === 'v2'` disables preprocessing.
|
|
27
|
+
* 3. Fallback keeps v1-compatible behavior (enabled).
|
|
28
|
+
*/
|
|
29
|
+
export declare function shouldPreprocessRunJSTemplates(options?: Pick<JSRunnerOptions, 'preprocessTemplates' | 'version'>): boolean;
|
|
21
30
|
export declare class JSRunner {
|
|
22
31
|
private globals;
|
|
23
32
|
private timeoutMs;
|
package/lib/JSRunner.js
CHANGED
|
@@ -27,11 +27,53 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
27
27
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
28
|
var JSRunner_exports = {};
|
|
29
29
|
__export(JSRunner_exports, {
|
|
30
|
-
JSRunner: () => JSRunner
|
|
30
|
+
JSRunner: () => JSRunner,
|
|
31
|
+
shouldPreprocessRunJSTemplates: () => shouldPreprocessRunJSTemplates
|
|
31
32
|
});
|
|
32
33
|
module.exports = __toCommonJS(JSRunner_exports);
|
|
33
34
|
var import_ses = require("ses");
|
|
34
35
|
var import_exceptions = require("./utils/exceptions");
|
|
36
|
+
function shouldPreprocessRunJSTemplates(options) {
|
|
37
|
+
if (typeof (options == null ? void 0 : options.preprocessTemplates) === "boolean") {
|
|
38
|
+
return options.preprocessTemplates;
|
|
39
|
+
}
|
|
40
|
+
return (options == null ? void 0 : options.version) !== "v2";
|
|
41
|
+
}
|
|
42
|
+
__name(shouldPreprocessRunJSTemplates, "shouldPreprocessRunJSTemplates");
|
|
43
|
+
const BARE_CTX_TEMPLATE_RE = /(^|[=(:,[\s)])(\{\{\s*(ctx(?:\.|\[|\?\.)[^}]*)\s*\}\})/m;
|
|
44
|
+
function extractDeprecatedCtxTemplateUsage(code) {
|
|
45
|
+
const src = String(code || "");
|
|
46
|
+
const m = src.match(BARE_CTX_TEMPLATE_RE);
|
|
47
|
+
if (!m) return null;
|
|
48
|
+
const placeholder = String(m[2] || "").trim();
|
|
49
|
+
const expression = String(m[3] || "").trim();
|
|
50
|
+
if (!placeholder || !expression) return null;
|
|
51
|
+
return { placeholder, expression };
|
|
52
|
+
}
|
|
53
|
+
__name(extractDeprecatedCtxTemplateUsage, "extractDeprecatedCtxTemplateUsage");
|
|
54
|
+
function shouldHintCtxTemplateSyntax(err, usage) {
|
|
55
|
+
const isSyntaxError = err instanceof SyntaxError || String((err == null ? void 0 : err.name) || "") === "SyntaxError";
|
|
56
|
+
if (!isSyntaxError) return false;
|
|
57
|
+
if (!usage) return false;
|
|
58
|
+
const msg = String((err == null ? void 0 : err.message) || err || "");
|
|
59
|
+
return /unexpected token/i.test(msg);
|
|
60
|
+
}
|
|
61
|
+
__name(shouldHintCtxTemplateSyntax, "shouldHintCtxTemplateSyntax");
|
|
62
|
+
function toCtxTemplateSyntaxHintError(err, usage) {
|
|
63
|
+
const hint = `"${usage.placeholder}" has been deprecated and cannot be used as executable RunJS syntax. Use await ctx.getVar("${usage.expression}") instead, or keep "${usage.placeholder}" as a plain string.`;
|
|
64
|
+
const out = new SyntaxError(hint);
|
|
65
|
+
try {
|
|
66
|
+
out.cause = err;
|
|
67
|
+
} catch (_) {
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
out.__runjsHideLocation = true;
|
|
71
|
+
out.stack = `${out.name}: ${out.message}`;
|
|
72
|
+
} catch (_) {
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
__name(toCtxTemplateSyntaxHintError, "toCtxTemplateSyntaxHintError");
|
|
35
77
|
const _JSRunner = class _JSRunner {
|
|
36
78
|
globals;
|
|
37
79
|
timeoutMs;
|
|
@@ -111,11 +153,13 @@ const _JSRunner = class _JSRunner {
|
|
|
111
153
|
if (err instanceof import_exceptions.FlowExitAllException) {
|
|
112
154
|
throw err;
|
|
113
155
|
}
|
|
114
|
-
|
|
156
|
+
const usage = extractDeprecatedCtxTemplateUsage(code);
|
|
157
|
+
const outErr = shouldHintCtxTemplateSyntax(err, usage) && usage ? toCtxTemplateSyntaxHintError(err, usage) : err;
|
|
158
|
+
console.error(outErr);
|
|
115
159
|
return {
|
|
116
160
|
success: false,
|
|
117
|
-
error:
|
|
118
|
-
timeout:
|
|
161
|
+
error: outErr,
|
|
162
|
+
timeout: (outErr == null ? void 0 : outErr.message) === "Execution timed out"
|
|
119
163
|
};
|
|
120
164
|
}
|
|
121
165
|
}
|
|
@@ -124,5 +168,6 @@ __name(_JSRunner, "JSRunner");
|
|
|
124
168
|
let JSRunner = _JSRunner;
|
|
125
169
|
// Annotate the CommonJS export names for ESM import in node:
|
|
126
170
|
0 && (module.exports = {
|
|
127
|
-
JSRunner
|
|
171
|
+
JSRunner,
|
|
172
|
+
shouldPreprocessRunJSTemplates
|
|
128
173
|
});
|
package/lib/flowContext.js
CHANGED
|
@@ -2222,7 +2222,10 @@ const _BaseFlowEngineContext = class _BaseFlowEngineContext extends FlowContext
|
|
|
2222
2222
|
...runnerOptions || {},
|
|
2223
2223
|
globals: mergedGlobals
|
|
2224
2224
|
});
|
|
2225
|
-
const shouldPreprocessTemplates =
|
|
2225
|
+
const shouldPreprocessTemplates = (0, import_JSRunner.shouldPreprocessRunJSTemplates)({
|
|
2226
|
+
version: runnerOptions == null ? void 0 : runnerOptions.version,
|
|
2227
|
+
preprocessTemplates
|
|
2228
|
+
});
|
|
2226
2229
|
const jsCode = await (0, import_utils.prepareRunJsCode)(String(code ?? ""), { preprocessTemplates: shouldPreprocessTemplates });
|
|
2227
2230
|
return runner.run(jsCode);
|
|
2228
2231
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
|
-
export type RunJSVersion = 'v1' | (string & {});
|
|
9
|
+
export type RunJSVersion = 'v1' | 'v2' | (string & {});
|
|
10
10
|
export type RunJSContextCtor = new (delegate: any) => any;
|
|
11
11
|
export type RunJSContextMeta = {
|
|
12
12
|
scenes?: string[];
|
|
@@ -67,19 +67,26 @@ async function setupRunJSContexts() {
|
|
|
67
67
|
import("./contexts/JSRecordActionRunJSContext"),
|
|
68
68
|
import("./contexts/JSCollectionActionRunJSContext")
|
|
69
69
|
]);
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
70
|
+
const registerBuiltins = /* @__PURE__ */ __name((version) => {
|
|
71
|
+
import_registry.RunJSContextRegistry.register(version, "*", import_flowContext.FlowRunJSContext);
|
|
72
|
+
import_registry.RunJSContextRegistry.register(version, "JSBlockModel", JSBlockRunJSContext, { scenes: ["block"] });
|
|
73
|
+
import_registry.RunJSContextRegistry.register(version, "JSFieldModel", JSFieldRunJSContext, { scenes: ["detail"] });
|
|
74
|
+
import_registry.RunJSContextRegistry.register(version, "JSEditableFieldModel", JSEditableFieldRunJSContext, { scenes: ["form"] });
|
|
75
|
+
import_registry.RunJSContextRegistry.register(version, "JSItemModel", JSItemRunJSContext, { scenes: ["form"] });
|
|
76
|
+
import_registry.RunJSContextRegistry.register(version, "JSColumnModel", JSColumnRunJSContext, { scenes: ["table"] });
|
|
77
|
+
import_registry.RunJSContextRegistry.register(version, "FormJSFieldItemModel", FormJSFieldItemRunJSContext, { scenes: ["form"] });
|
|
78
|
+
import_registry.RunJSContextRegistry.register(version, "JSRecordActionModel", JSRecordActionRunJSContext, { scenes: ["table"] });
|
|
79
|
+
import_registry.RunJSContextRegistry.register(version, "JSCollectionActionModel", JSCollectionActionRunJSContext, {
|
|
80
|
+
scenes: ["table"]
|
|
81
|
+
});
|
|
82
|
+
}, "registerBuiltins");
|
|
83
|
+
const versions = ["v1", "v2"];
|
|
84
|
+
for (const version of versions) {
|
|
85
|
+
registerBuiltins(version);
|
|
86
|
+
await (0, import_contributions.applyRunJSContextContributions)(version);
|
|
87
|
+
(0, import_contributions.markRunJSContextsSetupDone)(version);
|
|
88
|
+
}
|
|
81
89
|
done = true;
|
|
82
|
-
(0, import_contributions.markRunJSContextsSetupDone)(v1);
|
|
83
90
|
}
|
|
84
91
|
__name(setupRunJSContexts, "setupRunJSContexts");
|
|
85
92
|
// Annotate the CommonJS export names for ESM import in node:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@formily/antd-v5": "1.x",
|
|
10
10
|
"@formily/reactive": "2.x",
|
|
11
|
-
"@nocobase/sdk": "2.0.
|
|
12
|
-
"@nocobase/shared": "2.0.
|
|
11
|
+
"@nocobase/sdk": "2.0.11",
|
|
12
|
+
"@nocobase/shared": "2.0.11",
|
|
13
13
|
"ahooks": "^3.7.2",
|
|
14
14
|
"dayjs": "^1.11.9",
|
|
15
15
|
"dompurify": "^3.0.2",
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
],
|
|
37
37
|
"author": "NocoBase Team",
|
|
38
38
|
"license": "Apache-2.0",
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "657733e68b31cbb788f8b1b7c4926383c96b5889"
|
|
40
40
|
}
|
package/src/JSRunner.ts
CHANGED
|
@@ -16,12 +16,74 @@ export interface JSRunnerOptions {
|
|
|
16
16
|
version?: string;
|
|
17
17
|
/**
|
|
18
18
|
* Enable RunJS template compatibility preprocessing for `{{ ... }}`.
|
|
19
|
-
* When enabled
|
|
19
|
+
* When enabled (or falling back to version default),
|
|
20
20
|
* the code will be rewritten to call `ctx.resolveJsonTemplate(...)` at runtime.
|
|
21
21
|
*/
|
|
22
22
|
preprocessTemplates?: boolean;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Decide whether RunJS `{{ ... }}` compatibility preprocessing should run.
|
|
27
|
+
*
|
|
28
|
+
* Priority:
|
|
29
|
+
* 1. Explicit `preprocessTemplates` option always wins.
|
|
30
|
+
* 2. Otherwise, `version === 'v2'` disables preprocessing.
|
|
31
|
+
* 3. Fallback keeps v1-compatible behavior (enabled).
|
|
32
|
+
*/
|
|
33
|
+
export function shouldPreprocessRunJSTemplates(
|
|
34
|
+
options?: Pick<JSRunnerOptions, 'preprocessTemplates' | 'version'>,
|
|
35
|
+
): boolean {
|
|
36
|
+
if (typeof options?.preprocessTemplates === 'boolean') {
|
|
37
|
+
return options.preprocessTemplates;
|
|
38
|
+
}
|
|
39
|
+
return options?.version !== 'v2';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Heuristic: detect likely bare `{{ctx.xxx}}` usage in executable positions (not quoted string literals).
|
|
43
|
+
const BARE_CTX_TEMPLATE_RE = /(^|[=(:,[\s)])(\{\{\s*(ctx(?:\.|\[|\?\.)[^}]*)\s*\}\})/m;
|
|
44
|
+
|
|
45
|
+
function extractDeprecatedCtxTemplateUsage(code: string): { placeholder: string; expression: string } | null {
|
|
46
|
+
const src = String(code || '');
|
|
47
|
+
const m = src.match(BARE_CTX_TEMPLATE_RE);
|
|
48
|
+
if (!m) return null;
|
|
49
|
+
const placeholder = String(m[2] || '').trim();
|
|
50
|
+
const expression = String(m[3] || '').trim();
|
|
51
|
+
if (!placeholder || !expression) return null;
|
|
52
|
+
return { placeholder, expression };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function shouldHintCtxTemplateSyntax(err: any, usage: { placeholder: string; expression: string } | null): boolean {
|
|
56
|
+
const isSyntaxError = err instanceof SyntaxError || String((err as any)?.name || '') === 'SyntaxError';
|
|
57
|
+
if (!isSyntaxError) return false;
|
|
58
|
+
if (!usage) return false;
|
|
59
|
+
const msg = String((err as any)?.message || err || '');
|
|
60
|
+
return /unexpected token/i.test(msg);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function toCtxTemplateSyntaxHintError(
|
|
64
|
+
err: any,
|
|
65
|
+
usage: {
|
|
66
|
+
placeholder: string;
|
|
67
|
+
expression: string;
|
|
68
|
+
},
|
|
69
|
+
): Error {
|
|
70
|
+
const hint = `"${usage.placeholder}" has been deprecated and cannot be used as executable RunJS syntax. Use await ctx.getVar("${usage.expression}") instead, or keep "${usage.placeholder}" as a plain string.`;
|
|
71
|
+
const out = new SyntaxError(hint);
|
|
72
|
+
try {
|
|
73
|
+
(out as any).cause = err;
|
|
74
|
+
} catch (_) {
|
|
75
|
+
// ignore
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
// Hint-only error: avoid leaking internal bundle line numbers from stack parsers in preview UI.
|
|
79
|
+
(out as any).__runjsHideLocation = true;
|
|
80
|
+
out.stack = `${out.name}: ${out.message}`;
|
|
81
|
+
} catch (_) {
|
|
82
|
+
// ignore
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
25
87
|
export class JSRunner {
|
|
26
88
|
private globals: Record<string, any>;
|
|
27
89
|
private timeoutMs: number;
|
|
@@ -118,11 +180,13 @@ export class JSRunner {
|
|
|
118
180
|
if (err instanceof FlowExitAllException) {
|
|
119
181
|
throw err;
|
|
120
182
|
}
|
|
121
|
-
|
|
183
|
+
const usage = extractDeprecatedCtxTemplateUsage(code);
|
|
184
|
+
const outErr = shouldHintCtxTemplateSyntax(err, usage) && usage ? toCtxTemplateSyntaxHintError(err, usage) : err;
|
|
185
|
+
console.error(outErr);
|
|
122
186
|
return {
|
|
123
187
|
success: false,
|
|
124
|
-
error:
|
|
125
|
-
timeout:
|
|
188
|
+
error: outErr,
|
|
189
|
+
timeout: (outErr as any)?.message === 'Execution timed out',
|
|
126
190
|
};
|
|
127
191
|
}
|
|
128
192
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
11
|
-
import { JSRunner } from '../JSRunner';
|
|
11
|
+
import { JSRunner, shouldPreprocessRunJSTemplates } from '../JSRunner';
|
|
12
12
|
import { createSafeWindow } from '../utils';
|
|
13
13
|
|
|
14
14
|
describe('JSRunner', () => {
|
|
@@ -30,6 +30,18 @@ describe('JSRunner', () => {
|
|
|
30
30
|
vi.restoreAllMocks();
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
it('shouldPreprocessRunJSTemplates: explicit option has highest priority', () => {
|
|
34
|
+
expect(shouldPreprocessRunJSTemplates({ version: 'v2', preprocessTemplates: true })).toBe(true);
|
|
35
|
+
expect(shouldPreprocessRunJSTemplates({ version: 'v1', preprocessTemplates: false })).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('shouldPreprocessRunJSTemplates: falls back to version policy', () => {
|
|
39
|
+
expect(shouldPreprocessRunJSTemplates({ version: 'v1' })).toBe(true);
|
|
40
|
+
expect(shouldPreprocessRunJSTemplates({ version: 'v2' })).toBe(false);
|
|
41
|
+
expect(shouldPreprocessRunJSTemplates({})).toBe(true);
|
|
42
|
+
expect(shouldPreprocessRunJSTemplates()).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
33
45
|
it('executes simple code and returns value', async () => {
|
|
34
46
|
const runner = new JSRunner();
|
|
35
47
|
const result = await runner.run('return 1 + 2 + 3');
|
|
@@ -152,6 +164,20 @@ describe('JSRunner', () => {
|
|
|
152
164
|
expect((result.error as Error).message).toBe('Execution timed out');
|
|
153
165
|
});
|
|
154
166
|
|
|
167
|
+
it('returns friendly hint when bare {{ctx.xxx}} appears in syntax error', async () => {
|
|
168
|
+
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
169
|
+
const runner = new JSRunner();
|
|
170
|
+
const result = await runner.run('const z = {{ctx.user.id}}');
|
|
171
|
+
expect(result.success).toBe(false);
|
|
172
|
+
expect(result.error).toBeInstanceOf(SyntaxError);
|
|
173
|
+
const msg = String((result.error as any)?.message || '');
|
|
174
|
+
expect(msg).toContain('"{{ctx.user.id}}" has been deprecated');
|
|
175
|
+
expect(msg).toContain('await ctx.getVar("ctx.user.id")');
|
|
176
|
+
expect(msg).not.toContain('(at ');
|
|
177
|
+
expect((result.error as any)?.__runjsHideLocation).toBe(true);
|
|
178
|
+
expect(spy).toHaveBeenCalled();
|
|
179
|
+
});
|
|
180
|
+
|
|
155
181
|
it('skips execution when URL contains skipRunJs=true', async () => {
|
|
156
182
|
// 模拟预览模式下通过 URL 参数跳过代码执行
|
|
157
183
|
if (typeof window !== 'undefined' && typeof window.history?.pushState === 'function') {
|
|
@@ -29,6 +29,10 @@ describe('flowRunJSContext registry and doc', () => {
|
|
|
29
29
|
expect(RunJSContextRegistry['resolve']('v1' as any, '*')).toBeTruthy();
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
it('should register v2 mapping', () => {
|
|
33
|
+
expect(RunJSContextRegistry['resolve']('v2' as any, '*')).toBeTruthy();
|
|
34
|
+
});
|
|
35
|
+
|
|
32
36
|
it('should register all context types', () => {
|
|
33
37
|
const contextTypes = [
|
|
34
38
|
'JSBlockModel',
|
|
@@ -44,12 +48,20 @@ describe('flowRunJSContext registry and doc', () => {
|
|
|
44
48
|
const ctor = RunJSContextRegistry['resolve']('v1' as any, modelClass);
|
|
45
49
|
expect(ctor).toBeTruthy();
|
|
46
50
|
});
|
|
51
|
+
|
|
52
|
+
contextTypes.forEach((modelClass) => {
|
|
53
|
+
const ctor = RunJSContextRegistry['resolve']('v2' as any, modelClass);
|
|
54
|
+
expect(ctor).toBeTruthy();
|
|
55
|
+
});
|
|
47
56
|
});
|
|
48
57
|
|
|
49
58
|
it('should expose scene metadata for contexts', () => {
|
|
50
59
|
expect(getRunJSScenesForModel('JSBlockModel', 'v1')).toEqual(['block']);
|
|
51
60
|
expect(getRunJSScenesForModel('JSFieldModel', 'v1')).toEqual(['detail']);
|
|
61
|
+
expect(getRunJSScenesForModel('JSBlockModel', 'v2')).toEqual(['block']);
|
|
62
|
+
expect(getRunJSScenesForModel('JSFieldModel', 'v2')).toEqual(['detail']);
|
|
52
63
|
expect(getRunJSScenesForModel('UnknownModel', 'v1')).toEqual([]);
|
|
64
|
+
expect(getRunJSScenesForModel('UnknownModel', 'v2')).toEqual([]);
|
|
53
65
|
});
|
|
54
66
|
|
|
55
67
|
it('should only execute once (idempotent)', async () => {
|
|
@@ -175,6 +187,7 @@ describe('flowRunJSContext registry and doc', () => {
|
|
|
175
187
|
const ctx = new FlowContext();
|
|
176
188
|
ctx.defineProperty('model', { value: { constructor: { name: 'JSColumnModel' } } });
|
|
177
189
|
expect(getRunJSScenesForContext(ctx as any, { version: 'v1' })).toEqual(['table']);
|
|
190
|
+
expect(getRunJSScenesForContext(ctx as any, { version: 'v2' })).toEqual(['table']);
|
|
178
191
|
});
|
|
179
192
|
|
|
180
193
|
it('JSBlockModel context should have element property in doc', () => {
|
|
@@ -36,6 +36,29 @@ describe('ctx.runjs preprocessTemplates default', () => {
|
|
|
36
36
|
expect(r.value).toBe('{{ctx.user.id}}');
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
it('disables template preprocess by default for version v2', async () => {
|
|
40
|
+
const engine = new FlowEngine();
|
|
41
|
+
const ctx = engine.context as any;
|
|
42
|
+
ctx.defineProperty('user', { value: { id: 123 } });
|
|
43
|
+
|
|
44
|
+
const r = await ctx.runjs('return "{{ctx.user.id}}";', undefined, { version: 'v2' });
|
|
45
|
+
expect(r.success).toBe(true);
|
|
46
|
+
expect(r.value).toBe('{{ctx.user.id}}');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('keeps explicit preprocessTemplates override higher priority than version', async () => {
|
|
50
|
+
const engine = new FlowEngine();
|
|
51
|
+
const ctx = engine.context as any;
|
|
52
|
+
ctx.defineProperty('user', { value: { id: 123 } });
|
|
53
|
+
|
|
54
|
+
const r = await ctx.runjs('return "{{ctx.user.id}}";', undefined, {
|
|
55
|
+
version: 'v2',
|
|
56
|
+
preprocessTemplates: true,
|
|
57
|
+
});
|
|
58
|
+
expect(r.success).toBe(true);
|
|
59
|
+
expect(r.value).toBe('123');
|
|
60
|
+
});
|
|
61
|
+
|
|
39
62
|
it('does not double-preprocess already prepared code', async () => {
|
|
40
63
|
const engine = new FlowEngine();
|
|
41
64
|
const ctx = engine.context as any;
|
package/src/flowContext.ts
CHANGED
|
@@ -27,7 +27,7 @@ import { ContextPathProxy } from './ContextPathProxy';
|
|
|
27
27
|
import { DataSource, DataSourceManager } from './data-source';
|
|
28
28
|
import { FlowEngine } from './flowEngine';
|
|
29
29
|
import { FlowI18n } from './flowI18n';
|
|
30
|
-
import { JSRunner, JSRunnerOptions } from './JSRunner';
|
|
30
|
+
import { JSRunner, JSRunnerOptions, shouldPreprocessRunJSTemplates } from './JSRunner';
|
|
31
31
|
import type { FlowModel } from './models/flowModel';
|
|
32
32
|
import type { ForkFlowModel } from './models/forkFlowModel';
|
|
33
33
|
import { FlowResource, FlowSQLRepository } from './resources';
|
|
@@ -3035,8 +3035,10 @@ class BaseFlowEngineContext extends FlowContext {
|
|
|
3035
3035
|
...(runnerOptions || {}),
|
|
3036
3036
|
globals: mergedGlobals,
|
|
3037
3037
|
});
|
|
3038
|
-
|
|
3039
|
-
|
|
3038
|
+
const shouldPreprocessTemplates = shouldPreprocessRunJSTemplates({
|
|
3039
|
+
version: runnerOptions?.version,
|
|
3040
|
+
preprocessTemplates,
|
|
3041
|
+
});
|
|
3040
3042
|
const jsCode = await prepareRunJsCode(String(code ?? ''), { preprocessTemplates: shouldPreprocessTemplates });
|
|
3041
3043
|
return runner.run(jsCode);
|
|
3042
3044
|
},
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// 为避免在模块初始化阶段引入 FlowContext(从而触发循环依赖),不要在顶层导入各类 RunJSContext。
|
|
11
11
|
// 在需要默认映射时(首次 resolve)再使用 createRequire 同步加载对应模块。
|
|
12
12
|
|
|
13
|
-
export type RunJSVersion = 'v1' | (string & {});
|
|
13
|
+
export type RunJSVersion = 'v1' | 'v2' | (string & {});
|
|
14
14
|
export type RunJSContextCtor = new (delegate: any) => any;
|
|
15
15
|
export type RunJSContextMeta = {
|
|
16
16
|
scenes?: string[];
|
|
@@ -41,17 +41,26 @@ export async function setupRunJSContexts() {
|
|
|
41
41
|
import('./contexts/JSCollectionActionRunJSContext'),
|
|
42
42
|
]);
|
|
43
43
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
const registerBuiltins = (version: 'v1' | 'v2') => {
|
|
45
|
+
RunJSContextRegistry.register(version, '*', FlowRunJSContext);
|
|
46
|
+
RunJSContextRegistry.register(version, 'JSBlockModel', JSBlockRunJSContext, { scenes: ['block'] });
|
|
47
|
+
RunJSContextRegistry.register(version, 'JSFieldModel', JSFieldRunJSContext, { scenes: ['detail'] });
|
|
48
|
+
RunJSContextRegistry.register(version, 'JSEditableFieldModel', JSEditableFieldRunJSContext, { scenes: ['form'] });
|
|
49
|
+
RunJSContextRegistry.register(version, 'JSItemModel', JSItemRunJSContext, { scenes: ['form'] });
|
|
50
|
+
RunJSContextRegistry.register(version, 'JSColumnModel', JSColumnRunJSContext, { scenes: ['table'] });
|
|
51
|
+
RunJSContextRegistry.register(version, 'FormJSFieldItemModel', FormJSFieldItemRunJSContext, { scenes: ['form'] });
|
|
52
|
+
RunJSContextRegistry.register(version, 'JSRecordActionModel', JSRecordActionRunJSContext, { scenes: ['table'] });
|
|
53
|
+
RunJSContextRegistry.register(version, 'JSCollectionActionModel', JSCollectionActionRunJSContext, {
|
|
54
|
+
scenes: ['table'],
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const versions: Array<'v1' | 'v2'> = ['v1', 'v2'];
|
|
59
|
+
for (const version of versions) {
|
|
60
|
+
registerBuiltins(version);
|
|
61
|
+
await applyRunJSContextContributions(version);
|
|
62
|
+
markRunJSContextsSetupDone(version);
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
done = true;
|
|
56
|
-
markRunJSContextsSetupDone(v1);
|
|
57
66
|
}
|