| 1169 | out << "{"; |
| 1170 | bool first = true; |
| 1171 | for (const auto & [key, value] : context.values) { |
| 1172 | if (!first) { |
| 1173 | out << ","; |
| 1174 | } |
| 1175 | first = false; |
| 1176 | out << "\n " << engine::io::json::stringify_string(key) |
| 1177 | << ": " << engine::io::json::stringify_string(value); |
| 1178 | } |
| 1179 | out << "\n}\n"; |
| 1180 | std::cout << "workflow_manifest_out=" << path.string() << "\n"; |
| 1181 | } |
| 1182 | |
| 1183 | } // namespace |
| 1184 | |
| 1185 | void run_json_workflow( |
| 1186 | const engine::runtime::ModelRegistry & registry, |
| 1187 | const WorkflowRunOptions & options) { |
| 1188 | if (options.workflow_path.empty()) { |
| 1189 | throw std::runtime_error("workflow path is required"); |
| 1190 | } |
| 1191 | if (options.output_dir.empty()) { |
| 1192 | throw std::runtime_error("workflow --out-dir is required"); |
| 1193 | } |
| 1194 | const auto root = engine::io::json::parse_file(options.workflow_path); |
| 1195 | const auto * steps = root.find("steps"); |
| 1196 | if (steps == nullptr || !steps->is_array()) { |
| 1197 | throw std::runtime_error("workflow JSON requires a steps array"); |
| 1198 | } |
| 1199 | WorkflowContext context{ |
| 1200 | options.workflow_path.parent_path().empty() |
| 1201 | ? std::filesystem::current_path() |
| 1202 | : std::filesystem::absolute(options.workflow_path.parent_path()), |
| 1203 | std::filesystem::absolute(options.output_dir), |
| 1204 | {}, |
| 1205 | {}, |
| 1206 | }; |
| 1207 | load_workflow_inputs(root, options, context); |
| 1208 | std::filesystem::create_directories(context.output_dir); |
| 1209 | for (const auto & step : steps->as_array()) { |
| 1210 | run_workflow_step(registry, step, options, context); |
| 1211 | } |
| 1212 | if (options.final_audio_out.has_value()) { |
| 1213 | const auto * final_key = root.find("final_audio"); |
| 1214 | if (final_key == nullptr || final_key->is_null()) { |
no test coverage detected