| 1 | import * as Schema from "effect/Schema"; |
| 2 | import { compileTagsFilter } from "./Tags.ts"; |
| 3 | |
| 4 | const Branch = Schema.Struct({ |
| 5 | /** Tag expressions are ANDed, just like repeated --tags flags. Empty selects all. */ |
| 6 | tags: Schema.Array(Schema.String), |
| 7 | concurrency: Schema.optional( |
| 8 | Schema.Union([ |
| 9 | Schema.Int.check(Schema.isGreaterThan(0)), |
| 10 | Schema.Literal("unbounded"), |
| 11 | ]), |
| 12 | ), |
| 13 | }); |
| 14 | const Plan = Schema.Array( |
| 15 | Schema.Union([Branch, Schema.Array(Branch).check(Schema.isMinLength(1))]), |
| 16 | ).check(Schema.isMinLength(1)); |
| 17 | |
| 18 | export type TestPlan = typeof Plan.Type; |
| 19 | |
| 20 | /** Parse before collection so an invalid plan cannot trigger test imports. */ |
| 21 | export const parsePlan = (json: string): TestPlan => { |
| 22 | try { |
| 23 | const plan = Schema.decodeUnknownSync(Plan, { onExcessProperty: "error" })( |
| 24 | JSON.parse(json), |
| 25 | ); |
| 26 | for (const phase of plan) { |
| 27 | for (const branch of Array.isArray(phase) ? phase : [phase]) { |
| 28 | compileTagsFilter(branch.tags); |
| 29 | } |
| 30 | } |
| 31 | return plan; |
| 32 | } catch (cause) { |
| 33 | throw new Error( |
| 34 | `Invalid --plan: ${cause instanceof Error ? cause.message : String(cause)}`, |
| 35 | ); |
| 36 | } |
| 37 | }; |
| 38 | |
| 39 | /** Shared plain/TUI/log representation of a dry-run plan. */ |
| 40 | export const formatPlanPreview = ( |
| 41 | phases: Extract< |
| 42 | import("./Reporter.ts").TestEvent, |
| 43 | { _tag: "PlanPreview" } |
| 44 | >["phases"], |
| 45 | ): string => |
| 46 | [ |
| 47 | "Dry run — no tests or hooks executed", |
| 48 | ...phases.flatMap((branches, index) => [ |
| 49 | `Phase ${index + 1}${branches.length > 1 ? " (parallel)" : ""}:`, |
| 50 | ...branches.map( |
| 51 | (branch, i) => |
| 52 | ` Branch ${i + 1}: tags=${JSON.stringify(branch.tags)}, concurrency=${branch.concurrency} — ${branch.tests} tests in ${branch.files} files${branch.skipped ? ` (${branch.skipped} skipped/todo)` : ""}`, |
| 53 | ), |
| 54 | ]), |
| 55 | ].join("\n"); |