animastor-comfyui-workflow-connector 0.1.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/LICENSE +21 -0
- package/README.md +298 -0
- package/package.json +41 -0
- package/src/connector-loader.js +978 -0
- package/src/entity-schema.js +340 -0
- package/src/index.js +265 -0
- package/src/workflow-loader.js +138 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// ======================================================
|
|
2
|
+
// Workflow Loader — v2.0.0 (Connector-aware)
|
|
3
|
+
// animastor-comfyui-workflow-connector package
|
|
4
|
+
// ======================================================
|
|
5
|
+
// Loads ComfyUI JSON workflows and their corresponding connectors from the
|
|
6
|
+
// HOST-INJECTED directories, alongside skills, rules and assembly profiles.
|
|
7
|
+
//
|
|
8
|
+
// The connector layer abstracts workflow internals (nodeId, fields)
|
|
9
|
+
// so host code never references them directly.
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const connectorLoader = require('./connector-loader');
|
|
14
|
+
|
|
15
|
+
// Directory resolution order: explicit injection (configure) → env (WF_DIR)
|
|
16
|
+
// → no default. The default is a HOST concern (extraction §1.3): the host
|
|
17
|
+
// passes its asset directories at boot via configure() / the package API.
|
|
18
|
+
let WF_DIR = process.env.WF_DIR || null;
|
|
19
|
+
let loggerRef = console;
|
|
20
|
+
|
|
21
|
+
const logPrefix = '[WORKFLOWS]';
|
|
22
|
+
|
|
23
|
+
function log(msg) { loggerRef.log(`${logPrefix} ${msg}`); }
|
|
24
|
+
function warn(msg) { loggerRef.warn(`${logPrefix} ${msg}`); }
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Dependency-injected configuration (extraction readiness): override the
|
|
28
|
+
* workflows directory and/or the logger before loadWorkflows(). Env vars
|
|
29
|
+
* and defaults still apply when nothing is injected.
|
|
30
|
+
*
|
|
31
|
+
* @param {{ workflowsDir?: string, logger?: object }} [options]
|
|
32
|
+
*/
|
|
33
|
+
function configure({ workflowsDir, connectorsDir, logger } = {}) {
|
|
34
|
+
if (workflowsDir) {
|
|
35
|
+
WF_DIR = workflowsDir;
|
|
36
|
+
loggerRef = logger || loggerRef;
|
|
37
|
+
}
|
|
38
|
+
if (logger) loggerRef = logger;
|
|
39
|
+
// Dirs go together at host boot (extraction §2): when the host injects
|
|
40
|
+
// both, the connectors dir must switch in the SAME call (loadWorkflows()
|
|
41
|
+
// validates connectors in one step). When only the workflows dir is
|
|
42
|
+
// injected, the connector dir keeps its current resolution (env/injected).
|
|
43
|
+
if (workflowsDir && connectorsDir) {
|
|
44
|
+
connectorLoader.configure({ connectorsDir, logger });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the active workflows directory (for diagnostics/messages).
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
function getWorkflowsDir() {
|
|
53
|
+
return WF_DIR;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const workflows = {};
|
|
57
|
+
const workflowHashes = {}; // name → sha256
|
|
58
|
+
|
|
59
|
+
function loadWorkflows() {
|
|
60
|
+
let count = 0;
|
|
61
|
+
// Fresh-load semantics: clear previous entries so re-loading after a
|
|
62
|
+
// directory change (tests, hot reload) never leaves phantom workflows.
|
|
63
|
+
for (const key of Object.keys(workflows)) delete workflows[key];
|
|
64
|
+
for (const key of Object.keys(workflowHashes)) delete workflowHashes[key];
|
|
65
|
+
if (fs.existsSync(WF_DIR)) {
|
|
66
|
+
fs.readdirSync(WF_DIR).forEach(f => {
|
|
67
|
+
if (f.endsWith('.json') && !f.startsWith('old_')) {
|
|
68
|
+
const name = f.replace('.json', '');
|
|
69
|
+
try {
|
|
70
|
+
const raw = fs.readFileSync(path.join(WF_DIR, f), 'utf8');
|
|
71
|
+
const json = JSON.parse(raw);
|
|
72
|
+
workflows[name] = json;
|
|
73
|
+
workflowHashes[name] = connectorLoader.computeWorkflowHash(json);
|
|
74
|
+
count++;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
warn(`Error loading workflow ${f}: ${err.message}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
log(`Loaded ${count} workflows from ${WF_DIR}`);
|
|
81
|
+
|
|
82
|
+
// Load and validate connectors against loaded workflows
|
|
83
|
+
log(`Loading connectors...`);
|
|
84
|
+
const connResult = connectorLoader.initialize(workflows);
|
|
85
|
+
if (connResult.warnings.length > 0) {
|
|
86
|
+
for (const w of connResult.warnings) {
|
|
87
|
+
warn(`⚠️ ${w}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (connResult.errors.length > 0) {
|
|
91
|
+
loggerRef.error(`${logPrefix} ❌ Connector errors: ${connResult.errors.length}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check that every workflow has a matching connector
|
|
95
|
+
const missingConnectors = [];
|
|
96
|
+
for (const wfName of Object.keys(workflows)) {
|
|
97
|
+
if (!connectorLoader.getConnector(wfName)) {
|
|
98
|
+
missingConnectors.push(wfName);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (missingConnectors.length > 0) {
|
|
102
|
+
const msg = `Missing connectors for workflows: ${missingConnectors.join(', ')}. ` +
|
|
103
|
+
`Every workflow must have a matching connector file in the connectors dir (${connectorLoader.getConnectorsDir()}).`;
|
|
104
|
+
loggerRef.error(`${logPrefix} ❌ FATAL: ${msg}`);
|
|
105
|
+
throw new Error(msg);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
log(`Connectors: ${Object.keys(connResult.connectors).length} loaded, ` +
|
|
109
|
+
`${connResult.warnings.length} warnings, ${connResult.errors.length} errors`);
|
|
110
|
+
} else {
|
|
111
|
+
warn(`Workflow directory not found: ${WF_DIR}`);
|
|
112
|
+
}
|
|
113
|
+
return workflows;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getWorkflow(name) {
|
|
117
|
+
if (!workflows[name]) {
|
|
118
|
+
throw new Error(`Workflow not found: ${name}`);
|
|
119
|
+
}
|
|
120
|
+
return JSON.parse(JSON.stringify(workflows[name]));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get the connector for a given workflow name.
|
|
125
|
+
* Returns null if no connector is registered for this workflow.
|
|
126
|
+
*/
|
|
127
|
+
function getConnector(workflowName) {
|
|
128
|
+
return connectorLoader.getConnector(workflowName);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get the computed hash for a workflow.
|
|
133
|
+
*/
|
|
134
|
+
function getWorkflowHash(name) {
|
|
135
|
+
return workflowHashes[name] || null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = { loadWorkflows, getWorkflow, getConnector, getWorkflowHash, workflows, configure, getWorkflowsDir };
|