* Call a Python function to dynamically detect cron entries.
(opts: {
pythonBin: string;
env: NodeJS.ProcessEnv;
workPath: string;
moduleName: string;
attrName: string;
})
| 184 | * Call a Python function to dynamically detect cron entries. |
| 185 | */ |
| 186 | async function detectDynamicCrons(opts: { |
| 187 | pythonBin: string; |
| 188 | env: NodeJS.ProcessEnv; |
| 189 | workPath: string; |
| 190 | moduleName: string; |
| 191 | attrName: string; |
| 192 | }): Promise<DynamicCronEntry[]> { |
| 193 | const { pythonBin, env, workPath, moduleName, attrName } = opts; |
| 194 | |
| 195 | let stdout: string; |
| 196 | try { |
| 197 | const result = await execa( |
| 198 | pythonBin, |
| 199 | ['-c', script, moduleName, attrName], |
| 200 | { env, cwd: workPath } |
| 201 | ); |
| 202 | stdout = result.stdout; |
| 203 | } catch (err: any) { |
| 204 | // The Python script writes structured JSON errors to stdout before |
| 205 | // exiting non-zero. Prefer that over execa's generic error. |
| 206 | let detail = err?.stderr || err?.message || String(err); |
| 207 | try { |
| 208 | const parsed = JSON.parse(err?.stdout) as DynamicCronResult; |
| 209 | if (parsed.error) detail = parsed.error; |
| 210 | } catch {} |
| 211 | throw new NowBuildError({ |
| 212 | code: 'PYTHON_DYNAMIC_CRON_DETECTION_FAILED', |
| 213 | message: `Failed to detect dynamic cron entries: ${detail}`, |
| 214 | }); |
| 215 | } |
| 216 | |
| 217 | let parsed: DynamicCronResult; |
| 218 | try { |
| 219 | parsed = JSON.parse(stdout); |
| 220 | } catch { |
| 221 | throw new NowBuildError({ |
| 222 | code: 'PYTHON_DYNAMIC_CRON_DETECTION_FAILED', |
| 223 | message: `Dynamic cron detection returned invalid JSON: ${stdout}`, |
| 224 | }); |
| 225 | } |
| 226 | |
| 227 | return parsed.entries || []; |
| 228 | } |
| 229 | |
| 230 | /** |
| 231 | * Convert a module:function string into a cron path. |
no test coverage detected