$ cat node-template.py

M

Make Array

// Collects multiple inputs into an ordered array. Add items dynamically and drag to reorder. Carries files through when item ports receive image/video/audio outputs.

Process
Utility
template.py
1import json2import os3import shutil4import sys56INPUT_DIR = "/data/input"7OUTPUT_DIR = "/data/output"8910def _looks_like_file_value(value):11    """Cheap filename heuristic — used only to decide whether to probe /data/input.1213    The authoritative signal is os.path.exists(INPUT_DIR/<value>): we will not14    copy anything we cannot actually find on disk.15    """16    if not isinstance(value, str) or not value:17        return False18    if "/" in value or "\\" in value or value.startswith("."):19        return False20    return "." in value212223def main():24    try:25        execution_input = json.loads(sys.stdin.read())26        inputs = execution_input.get("inputs", {})27        input_schema = execution_input.get("inputSchema", {})2829        schema_props = input_schema.get("properties", {})3031        def sort_key(k):32            prop = schema_props.get(k, {})33            if "order" in prop:34                return prop["order"]35            try:36                return int(k.split("_")[1])37            except (ValueError, IndexError):38                return float("inf")3940        item_keys = sorted(41            [k for k in inputs if k.startswith("item_")],42            key=sort_key,43        )4445        items = []46        files_passed_through = []47        for key in item_keys:48            value = inputs.get(key)49            if value is None or value == "":50                continue51            items.append(value)52            if _looks_like_file_value(value):53                staged_path = os.path.join(INPUT_DIR, value)54                if os.path.isfile(staged_path):55                    files_passed_through.append(value)5657        # When any item resolves to an actually-staged file in /data/input,58        # copy it to /data/output. The Docker executor uploads everything in59        # /data/output to MinIO under this node's (node_id, execution_id),60        # which is the key downstream edge resolvers reconstruct.61        if files_passed_through:62            os.makedirs(OUTPUT_DIR, exist_ok=True)63            for filename in files_passed_through:64                shutil.copy(65                    os.path.join(INPUT_DIR, filename),66                    os.path.join(OUTPUT_DIR, filename),67                )68            print(69                "make_array: passed through {} file(s)".format(len(files_passed_through)),70                file=sys.stderr,71            )7273        print(74            "make_array: collected {} item(s) from {} port(s)".format(len(items), len(item_keys)),75            file=sys.stderr,76        )7778        output = {"items": items}79        # Companion key consumed by the edge resolver to override the80        # statically-Any source_port_type. "File" is enough — is_file_type()81        # treats it as a file family and the resolver will download each82        # item from MinIO using this node's (source_node_id, src_exec_id).83        if files_passed_through:84            output["_items_port_type"] = "File"8586        print(json.dumps(output))8788    except Exception as e:89        import traceback90        error_output = {91            "error": str(e),92            "errorType": type(e).__name__,93            "traceback": traceback.format_exc(),94        }95        print(json.dumps(error_output), file=sys.stderr)96        sys.exit(1)979899if __name__ == "__main__":100    main()

$ git log --oneline

v1.1.0
HEAD
2026-08-18
v1.1.02026-05-18
v1.0.02026-05-17