59 lines
3.6 KiB
TypeScript
59 lines
3.6 KiB
TypeScript
import type {WorkflowCatalog} from '../../types/bot/workflow/metadata.ts';
|
|
import {node_registry, query_registry, start_registry} from './registry.ts';
|
|
import {event_registry} from './node_action/event_node/registry.ts';
|
|
import {describe, edge_input_output, result_output} from './metadata.ts';
|
|
import {node_fields, policy_fields} from './common_template.ts';
|
|
import {assert_json} from './runtime/value.ts';
|
|
import {validate_fields, validate_i18n, validate_output} from './runtime/template.ts';
|
|
|
|
export function validate_workflow_catalog(catalog: WorkflowCatalog): void {
|
|
assert_json(catalog);
|
|
if (catalog.catalog_version !== 1) throw new Error('Invalid workflow catalog version');
|
|
for (const group of [catalog.nodes, catalog.start_nodes, catalog.events, catalog.queries]) {
|
|
const names = new Set<string>();
|
|
for (const item of group) {
|
|
if (!/^[a-zA-Z0-9_-]{1,100}$/.test(item.name) || names.has(item.name) || !/^\d+\.\d+\.\d+$/.test(item.version)) {
|
|
throw new Error('Invalid or duplicate workflow catalog entry');
|
|
}
|
|
names.add(item.name); validate_i18n(item.i18n); validate_fields(item.config); validate_output(item.output);
|
|
}
|
|
}
|
|
for (const node of catalog.nodes) {
|
|
const ports = new Set<string>();
|
|
for (const port of node.ports) {
|
|
if (!/^[a-zA-Z0-9_-]+$/.test(port.name) || ports.has(port.name)) throw new Error('Invalid workflow port');
|
|
ports.add(port.name); validate_i18n(port.i18n);
|
|
}
|
|
if (!ports.has('failure')) throw new Error('Workflow node needs failure port');
|
|
}
|
|
validate_fields(catalog.common.node_fields); validate_fields(catalog.common.policy_fields);
|
|
validate_output(catalog.common.result); validate_output(catalog.common.edge_input);
|
|
for (const reference of catalog.common.references) validate_i18n(reference.i18n);
|
|
}
|
|
|
|
/** Pure catalog inspection: no Bot, actor, event subscriptions or user code execution. */
|
|
export function get_workflow_catalog(options: {allow_code?: boolean} = {}): WorkflowCatalog {
|
|
const catalog: WorkflowCatalog = structuredClone({
|
|
catalog_version: 1,
|
|
nodes: Object.values(node_registry).map(module => module.config_template),
|
|
start_nodes: Object.values(start_registry).map(module => module.config_template),
|
|
events: Object.values(event_registry).map(module => module.config_template),
|
|
queries: Object.values(query_registry).map(module => module.config_template),
|
|
common: {
|
|
node_fields, policy_fields, result: result_output, edge_input: edge_input_output,
|
|
references: [
|
|
{root: 'trigger', i18n: describe('触发参数', '本次运行的事件、手动输入或定时触发数据。')},
|
|
{root: 'vars', i18n: describe('运行变量', '当前分支变量,合并后包含无冲突的分支更新。')},
|
|
{root: 'outputs', i18n: describe('上游节点结果', '沿已执行连线传入的节点结果;条件分支和 any 合并不保证所有祖先都存在。')},
|
|
{root: 'last_result', i18n: describe('最近结果', '当前路径最近一个节点的执行结果和节点 ID。')},
|
|
{root: 'inputs', i18n: describe('当前入线', '以连线 ID 为键的直接上游结果;all 合并包含全部匹配入线。')},
|
|
],
|
|
},
|
|
});
|
|
const code = catalog.nodes.find(node => node.name === 'code');
|
|
if (code) code.availability = options.allow_code
|
|
? {available: true} : {available: false, reason: 'CODE_EXECUTION_DISABLED'};
|
|
validate_workflow_catalog(catalog);
|
|
return catalog;
|
|
}
|