( steps: unknown, maxSteps: number, )
| 139 | } |
| 140 | |
| 141 | export function validateAndNormalizeBatchSteps( |
| 142 | steps: unknown, |
| 143 | maxSteps: number, |
| 144 | ): NormalizedBatchStep[] { |
| 145 | if (!Array.isArray(steps) || steps.length === 0) { |
| 146 | throw new AppError('INVALID_ARGS', 'batch requires a non-empty batchSteps array.'); |
| 147 | } |
| 148 | assertBatchStepCount(steps.length, maxSteps); |
| 149 | |
| 150 | const normalized: NormalizedBatchStep[] = []; |
| 151 | for (let index = 0; index < steps.length; index += 1) { |
| 152 | const step = steps[index]; |
| 153 | if (!isRecord(step)) { |
| 154 | throw new AppError('INVALID_ARGS', `Invalid batch step at index ${index}.`); |
| 155 | } |
| 156 | const unknownKeys = Object.keys(step).filter((key) => !batchAllowedStepKeys.has(key)); |
| 157 | if (unknownKeys.length > 0) { |
| 158 | const fields = unknownKeys.map((key) => `"${key}"`).join(', '); |
| 159 | throw new AppError( |
| 160 | 'INVALID_ARGS', |
| 161 | `Batch step ${index + 1} has unknown field(s): ${fields}. Allowed fields: command, positionals, flags, runtime.`, |
| 162 | ); |
| 163 | } |
| 164 | const command = normalizeBatchCommandName(step.command); |
| 165 | if (!command) { |
| 166 | throw new AppError('INVALID_ARGS', `Batch step ${index + 1} requires command.`); |
| 167 | } |
| 168 | assertBatchRuntimeCommandAllowed(command, index + 1); |
| 169 | if (step.positionals !== undefined && !Array.isArray(step.positionals)) { |
| 170 | throw new AppError('INVALID_ARGS', `Batch step ${index + 1} positionals must be an array.`); |
| 171 | } |
| 172 | const positionals = (step.positionals ?? []) as unknown[]; |
| 173 | if (positionals.some((value) => typeof value !== 'string')) { |
| 174 | throw new AppError( |
| 175 | 'INVALID_ARGS', |
| 176 | `Batch step ${index + 1} positionals must contain only strings.`, |
| 177 | ); |
| 178 | } |
| 179 | if (step.flags !== undefined && !isRecord(step.flags)) { |
| 180 | throw new AppError('INVALID_ARGS', `Batch step ${index + 1} flags must be an object.`); |
| 181 | } |
| 182 | normalized.push({ |
| 183 | command, |
| 184 | positionals: positionals as string[], |
| 185 | flags: (step.flags ?? {}) as Record<string, unknown>, |
| 186 | runtime: parseBatchStepRuntime(step.runtime, index + 1), |
| 187 | }); |
| 188 | } |
| 189 | return normalized; |
| 190 | } |
| 191 | |
| 192 | function buildBatchStepFlags( |
| 193 | parentFlags: BatchFlags | Record<string, unknown> | undefined, |
no test coverage detected