| 306 | } |
| 307 | |
| 308 | function parseJsonOps(parsed: Record<string, string>): JsonOp[] | null { |
| 309 | if (!parsed.jsonOps && !parsed["json-ops"]) return null; |
| 310 | const raw = parsed.jsonOps ?? parsed["json-ops"]; |
| 311 | let parsedJson: unknown; |
| 312 | try { |
| 313 | parsedJson = JSON.parse(raw); |
| 314 | } catch (e: any) { |
| 315 | fail(`Invalid --json-ops JSON: ${e.message}`); |
| 316 | } |
| 317 | if (!Array.isArray(parsedJson) || parsedJson.length === 0) { |
| 318 | fail("--json-ops must be a non-empty JSON array"); |
| 319 | } |
| 320 | for (const op of parsedJson as any[]) { |
| 321 | if (!op || typeof op !== "object" || typeof op.op !== "string") { |
| 322 | fail("Each op must be an object with an 'op' field"); |
| 323 | } |
| 324 | } |
| 325 | return parsedJson as JsonOp[]; |
| 326 | } |
| 327 | |
| 328 | function countOccurrences(haystack: string, needle: string): number { |
| 329 | if (!needle) return 0; |