reposets 0.4.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +87 -75
  2. package/bin/reposets.js +68 -17
  3. package/cli/commands/credentials.js +170 -49
  4. package/cli/commands/doctor.js +364 -91
  5. package/cli/commands/drift.js +48 -0
  6. package/cli/commands/history.js +203 -0
  7. package/cli/commands/init.js +110 -104
  8. package/cli/commands/list.js +60 -39
  9. package/cli/commands/nuke.js +127 -0
  10. package/cli/commands/sync.js +219 -59
  11. package/cli/commands/validate.js +57 -41
  12. package/cli/flags.js +36 -0
  13. package/cli/logger.js +48 -0
  14. package/index.d.ts +471 -1499
  15. package/index.js +3 -18
  16. package/lib/config-refs.js +76 -0
  17. package/lib/credential-labels.js +0 -0
  18. package/lib/fingerprint.js +52 -0
  19. package/lib/org-only.js +61 -0
  20. package/lib/schema-issues.js +50 -0
  21. package/package.json +11 -10
  22. package/schemas/annotations.js +81 -0
  23. package/schemas/common.js +83 -48
  24. package/schemas/config.js +214 -212
  25. package/schemas/credentials.js +190 -54
  26. package/schemas/environment.js +27 -21
  27. package/schemas/ruleset.js +235 -139
  28. package/services/ConfigFiles.js +126 -104
  29. package/services/CredentialResolver.js +97 -33
  30. package/services/OnePasswordClient.js +88 -16
  31. package/services/SyncLogger.js +107 -76
  32. package/store/AppliedState.js +0 -0
  33. package/store/RepoCache.js +86 -0
  34. package/store/SyncJournal.js +92 -0
  35. package/store/migrations.js +86 -0
  36. package/sync/SyncEngine.js +156 -0
  37. package/sync/decide.js +56 -0
  38. package/sync/phase.js +55 -0
  39. package/sync/phases/cleanup.js +220 -0
  40. package/sync/phases/code-scanning.js +187 -0
  41. package/sync/phases/environments.js +106 -0
  42. package/sync/phases/index.js +39 -0
  43. package/sync/phases/resource.js +149 -0
  44. package/sync/phases/rulesets.js +186 -0
  45. package/sync/phases/secrets.js +138 -0
  46. package/sync/phases/security.js +129 -0
  47. package/sync/phases/settings.js +274 -0
  48. package/sync/phases/variables.js +132 -0
  49. package/tsdoc-metadata.json +1 -1
  50. package/bin/reposets.d.ts +0 -1
  51. package/errors.js +0 -12
  52. package/lib/crypto.js +0 -27
  53. package/services/GitHubClient.js +0 -875
  54. package/services/SyncEngine.js +0 -580
@@ -1,406 +1,500 @@
1
- import { taplo, tombi } from "xdg-effect";
2
- import { Schema } from "effect";
1
+ import { docs, taplo, tombi } from "./annotations.js";
2
+ import { Effect, Schema } from "effect";
3
3
 
4
4
  //#region src/schemas/ruleset.ts
5
- const ResolvedRefSchema = Schema.Struct({ resolved: Schema.String.annotations({
5
+ /**
6
+ * A reference to a named value in the active credential profile.
7
+ *
8
+ * @public
9
+ */
10
+ const ResolvedRefSchema = Schema.Struct({ resolved: Schema.String.annotate({
6
11
  title: "Credential label",
7
12
  description: "Reference to a named value in the active credential profile's resolve section"
8
- }) }).annotations({
13
+ }) }).annotate({
9
14
  identifier: "ResolvedRef",
10
15
  title: "Resolved reference",
11
16
  description: "A reference to a credential-resolved value"
12
17
  });
13
- const ActorTypeSchema = Schema.Literal("Integration", "OrganizationAdmin", "RepositoryRole", "Team", "DeployKey").annotations({
18
+ const ActorTypeSchema = Schema.Literals([
19
+ "Integration",
20
+ "OrganizationAdmin",
21
+ "RepositoryRole",
22
+ "Team",
23
+ "DeployKey"
24
+ ]).annotate({
14
25
  title: "Actor type",
15
26
  description: "The type of actor that can bypass a ruleset"
16
27
  });
17
- const BypassModeSchema = Schema.Literal("always", "pull_request", "exempt").annotations({
28
+ const BypassModeSchema = Schema.Literals([
29
+ "always",
30
+ "pull_request",
31
+ "exempt"
32
+ ]).annotate({
18
33
  title: "Bypass mode",
19
34
  description: "When the specified actor can bypass the ruleset"
20
35
  });
36
+ /**
37
+ * An actor permitted to bypass a ruleset's rules.
38
+ *
39
+ * @public
40
+ */
21
41
  const BypassActorSchema = Schema.Struct({
22
- actor_id: Schema.optional(Schema.Union(Schema.Int, ResolvedRefSchema).annotations({
42
+ actor_id: Schema.optional(Schema.Union([Schema.Int, ResolvedRefSchema]).annotate({
23
43
  title: "Actor ID",
24
44
  description: "The ID of the actor, or a { resolved } reference to a credential label."
25
45
  })),
26
46
  actor_type: ActorTypeSchema,
27
- bypass_mode: Schema.optionalWith(BypassModeSchema, { default: () => "always" })
28
- }).annotations({
47
+ bypass_mode: BypassModeSchema.pipe(Schema.withDecodingDefaultKey(Effect.succeed("always")))
48
+ }).annotate({
29
49
  identifier: "BypassActor",
30
50
  title: "Bypass actor",
31
51
  description: "An actor that can bypass rules in a ruleset"
32
52
  });
53
+ /**
54
+ * Include and exclude patterns for matching ref names.
55
+ *
56
+ * @public
57
+ */
33
58
  const RefNameConditionSchema = Schema.Struct({
34
- include: Schema.optionalWith(Schema.Array(Schema.String).annotations({
59
+ include: Schema.Array(Schema.String).annotate({
35
60
  title: "Include patterns",
36
61
  description: "Ref name patterns to include. Accepts ~DEFAULT_BRANCH, ~ALL, or glob patterns.",
37
62
  examples: [["~DEFAULT_BRANCH"]]
38
- }), { default: () => [] }),
39
- exclude: Schema.optionalWith(Schema.Array(Schema.String).annotations({
63
+ }).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
64
+ exclude: Schema.Array(Schema.String).annotate({
40
65
  title: "Exclude patterns",
41
66
  description: "Ref name patterns to exclude"
42
- }), { default: () => [] })
43
- }).annotations({
67
+ }).pipe(Schema.withDecodingDefaultKey(Effect.succeed([])))
68
+ }).annotate({
44
69
  identifier: "RefNameCondition",
45
70
  title: "Ref name condition",
46
71
  description: "Conditions for matching ref names (branches or tags)"
47
72
  });
48
- const RulesetConditionsSchema = Schema.Struct({ ref_name: Schema.optional(RefNameConditionSchema) }).annotations({
73
+ /**
74
+ * The conditions under which a ruleset applies.
75
+ *
76
+ * @public
77
+ */
78
+ const RulesetConditionsSchema = Schema.Struct({ ref_name: Schema.optional(RefNameConditionSchema) }).annotate({
49
79
  identifier: "RulesetConditions",
50
80
  title: "Ruleset conditions",
51
81
  description: "Conditions that determine when the ruleset applies"
52
82
  });
53
83
  const RequiredReviewerSchema = Schema.Struct({
54
- file_patterns: Schema.Array(Schema.String).annotations({
84
+ file_patterns: Schema.Array(Schema.String).annotate({
55
85
  title: "File patterns",
56
86
  description: "File patterns this reviewer must approve (fnmatch syntax)"
57
87
  }),
58
- minimum_approvals: Schema.Int.annotations({
88
+ minimum_approvals: Schema.Int.annotate({
59
89
  title: "Minimum approvals",
60
90
  description: "Minimum approvals required from this team (0 = optional)"
61
91
  }),
62
92
  reviewer: Schema.Struct({
63
- id: Schema.Int.annotations({
93
+ id: Schema.Int.annotate({
64
94
  title: "Team ID",
65
95
  description: "Team ID"
66
96
  }),
67
97
  type: Schema.Literal("Team")
68
- }).annotations({ title: "Reviewer team" })
98
+ }).annotate({ title: "Reviewer team" })
69
99
  });
70
100
  const StatusCheckSchema = Schema.Struct({
71
- context: Schema.String.annotations({
101
+ context: Schema.String.annotate({
72
102
  title: "Context",
73
103
  description: "The status check context name that must be present on the commit"
74
104
  }),
75
- integration_id: Schema.optional(Schema.Union(Schema.Int, ResolvedRefSchema).annotations({
105
+ integration_id: Schema.optional(Schema.Union([Schema.Int, ResolvedRefSchema]).annotate({
76
106
  title: "Integration ID",
77
107
  description: "The integration ID, or a { resolved } reference to a credential label"
78
108
  }))
79
109
  });
80
110
  const WorkflowFileSchema = Schema.Struct({
81
- path: Schema.String.annotations({
111
+ path: Schema.String.annotate({
82
112
  title: "Workflow path",
83
113
  description: "Path to the workflow file"
84
114
  }),
85
- ref: Schema.optional(Schema.String.annotations({
115
+ ref: Schema.optional(Schema.String.annotate({
86
116
  title: "Ref",
87
117
  description: "Branch or tag of the workflow file"
88
118
  })),
89
- repository_id: Schema.Union(Schema.Int, ResolvedRefSchema).annotations({
119
+ repository_id: Schema.Union([Schema.Int, ResolvedRefSchema]).annotate({
90
120
  title: "Repository ID",
91
121
  description: "Repository ID, or a { resolved } reference to a credential label"
92
122
  }),
93
- sha: Schema.optional(Schema.String.annotations({
123
+ sha: Schema.optional(Schema.String.annotate({
94
124
  title: "SHA",
95
125
  description: "Commit SHA of the workflow file"
96
126
  }))
97
127
  });
98
- const TargetPatternSchema = Schema.Union(Schema.Struct({ include: Schema.String.annotations({
128
+ const TargetPatternSchema = Schema.Union([Schema.Struct({ include: Schema.String.annotate({
99
129
  title: "Include pattern",
100
130
  description: "Glob pattern to include"
101
- }) }), Schema.Struct({ exclude: Schema.String.annotations({
131
+ }) }), Schema.Struct({ exclude: Schema.String.annotate({
102
132
  title: "Exclude pattern",
103
133
  description: "Glob pattern to exclude"
104
- }) })).annotations({
134
+ }) })]).annotate({
105
135
  identifier: "TargetPattern",
106
136
  title: "Target pattern",
107
137
  description: "An include or exclude pattern for ref matching"
108
138
  });
109
- const TargetsSchema = Schema.Union(Schema.Literal("default", "all").annotations({
139
+ const TargetsSchema = Schema.Union([Schema.Literals(["default", "all"]).annotate({
110
140
  title: "Target preset",
111
141
  description: "'default' targets the default branch; 'all' targets all branches/tags"
112
- }), Schema.Array(TargetPatternSchema).annotations({
142
+ }), Schema.Array(TargetPatternSchema).annotate({
113
143
  title: "Custom target patterns",
114
144
  description: "Array of include/exclude patterns for fine-grained ref targeting"
115
- })).annotations({
145
+ })]).annotate({
116
146
  identifier: "Targets",
117
147
  title: "Targets shorthand",
118
148
  description: "Shorthand for specifying ref_name conditions: 'default', 'all', or custom patterns"
119
149
  });
120
150
  const PullRequestsShorthandSchema = Schema.Struct({
121
- approvals: Schema.optionalWith(Schema.Int.pipe(Schema.between(0, 10)).annotations({
151
+ approvals: Schema.Int.check(Schema.isBetween({
152
+ minimum: 0,
153
+ maximum: 10
154
+ })).annotate({
122
155
  title: "Required approvals",
123
156
  description: "Number of approving reviews required (0-10)"
124
- }), { default: () => 0 }),
125
- dismiss_stale_reviews: Schema.optionalWith(Schema.Boolean.annotations({
157
+ }).pipe(Schema.withDecodingDefaultKey(Effect.succeed(0))),
158
+ dismiss_stale_reviews: Schema.Boolean.annotate({
126
159
  title: "Dismiss stale reviews",
127
160
  description: "Dismiss previous approvals when new commits are pushed"
128
- }), { default: () => false }),
129
- code_owner_review: Schema.optionalWith(Schema.Boolean.annotations({
161
+ }).pipe(Schema.withDecodingDefaultKey(Effect.succeed(false))),
162
+ code_owner_review: Schema.Boolean.annotate({
130
163
  title: "Code owner review",
131
164
  description: "Require review from code owners for files they own"
132
- }), { default: () => false }),
133
- last_push_approval: Schema.optionalWith(Schema.Boolean.annotations({
165
+ }).pipe(Schema.withDecodingDefaultKey(Effect.succeed(false))),
166
+ last_push_approval: Schema.Boolean.annotate({
134
167
  title: "Last push approval",
135
168
  description: "Most recent push must be approved by someone other than the pusher"
136
- }), { default: () => false }),
137
- resolve_threads: Schema.optionalWith(Schema.Boolean.annotations({
169
+ }).pipe(Schema.withDecodingDefaultKey(Effect.succeed(false))),
170
+ resolve_threads: Schema.Boolean.annotate({
138
171
  title: "Resolve threads",
139
172
  description: "All review conversations must be resolved before merging"
140
- }), { default: () => false }),
141
- merge_methods: Schema.optional(Schema.Array(Schema.Literal("merge", "squash", "rebase")).annotations({
173
+ }).pipe(Schema.withDecodingDefaultKey(Effect.succeed(false))),
174
+ merge_methods: Schema.optional(Schema.Array(Schema.Literals([
175
+ "merge",
176
+ "squash",
177
+ "rebase"
178
+ ])).annotate({
142
179
  title: "Merge methods",
143
180
  description: "Allowed merge methods. At least one must be enabled."
144
181
  })),
145
- reviewers: Schema.optional(Schema.Array(RequiredReviewerSchema).annotations({
182
+ reviewers: Schema.optional(Schema.Array(RequiredReviewerSchema).annotate({
146
183
  title: "Required reviewers",
147
184
  description: "Teams that must approve specific file patterns"
148
185
  }))
149
- }).annotations({
186
+ }).annotate({
150
187
  identifier: "PullRequestsShorthand",
151
188
  title: "Pull requests shorthand",
152
189
  description: "Simplified pull request configuration (branch rulesets only)"
153
190
  });
154
191
  const StatusChecksShorthandSchema = Schema.Struct({
155
- update_branch: Schema.optional(Schema.Boolean.annotations({
192
+ update_branch: Schema.optional(Schema.Boolean.annotate({
156
193
  title: "Strict status checks",
157
194
  description: "PRs must be tested with the latest code"
158
195
  })),
159
- on_creation: Schema.optional(Schema.Boolean.annotations({
196
+ on_creation: Schema.optional(Schema.Boolean.annotate({
160
197
  title: "Enforce on create",
161
198
  description: "When false, allows branch creation even if checks would prohibit it"
162
199
  })),
163
- default_integration_id: Schema.optional(Schema.Union(Schema.Int, ResolvedRefSchema).annotations({
200
+ default_integration_id: Schema.optional(Schema.Union([Schema.Int, ResolvedRefSchema]).annotate({
164
201
  title: "Default integration ID",
165
202
  description: "Default integration ID applied to all checks that do not specify one"
166
203
  })),
167
- required: Schema.Array(StatusCheckSchema).annotations({
204
+ required: Schema.Array(StatusCheckSchema).annotate({
168
205
  title: "Required checks",
169
206
  description: "Status checks that must pass"
170
207
  })
171
- }).annotations({
208
+ }).annotate({
172
209
  identifier: "StatusChecksShorthand",
173
210
  title: "Status checks shorthand",
174
211
  description: "Simplified status checks configuration"
175
212
  });
176
- const EnforcementSchema = Schema.Literal("disabled", "active", "evaluate").annotations({
213
+ const EnforcementSchema = Schema.Literals([
214
+ "disabled",
215
+ "active",
216
+ "evaluate"
217
+ ]).annotate({
177
218
  title: "Enforcement level",
178
219
  description: "disabled = off, active = enforced, evaluate = test mode (GitHub Enterprise only)"
179
220
  });
180
221
  const PatternEntrySchema = Schema.Struct({
181
- operator: Schema.Literal("starts_with", "ends_with", "contains", "regex").annotations({
222
+ operator: Schema.Literals([
223
+ "starts_with",
224
+ "ends_with",
225
+ "contains",
226
+ "regex"
227
+ ]).annotate({
182
228
  title: "Operator",
183
229
  description: "The operator to use for matching"
184
230
  }),
185
- pattern: Schema.String.annotations({
231
+ pattern: Schema.String.annotate({
186
232
  title: "Pattern",
187
233
  description: "The pattern to match"
188
234
  }),
189
- name: Schema.optional(Schema.String.annotations({
235
+ name: Schema.optional(Schema.String.annotate({
190
236
  title: "Rule name",
191
237
  description: "Display name for this pattern rule"
192
238
  })),
193
- negate: Schema.optional(Schema.Boolean.annotations({
239
+ negate: Schema.optional(Schema.Boolean.annotate({
194
240
  title: "Negate",
195
241
  description: "If true, the rule fails when the pattern matches"
196
242
  }))
197
- }).annotations({
243
+ }).annotate({
198
244
  identifier: "PatternEntry",
199
245
  title: "Pattern entry",
200
246
  description: "A pattern matching rule with operator, pattern, and optional name/negate"
201
247
  });
202
248
  const MergeQueueShorthandSchema = Schema.Struct({
203
- check_timeout: Schema.Int.pipe(Schema.between(1, 360)).annotations({
249
+ check_timeout: Schema.Int.check(Schema.isBetween({
250
+ minimum: 1,
251
+ maximum: 360
252
+ })).annotate({
204
253
  title: "Check timeout (minutes)",
205
254
  description: "Max time for status checks to report"
206
255
  }),
207
- grouping: Schema.Literal("ALLGREEN", "HEADGREEN").annotations({
256
+ grouping: Schema.Literals(["ALLGREEN", "HEADGREEN"]).annotate({
208
257
  title: "Grouping strategy",
209
258
  description: "Whether all commits or only the head commit must pass checks"
210
259
  }),
211
- max_build: Schema.Int.pipe(Schema.between(0, 100)).annotations({
260
+ max_build: Schema.Int.check(Schema.isBetween({
261
+ minimum: 0,
262
+ maximum: 100
263
+ })).annotate({
212
264
  title: "Max entries to build",
213
265
  description: "Max queued PRs requesting checks simultaneously"
214
266
  }),
215
- max_merge: Schema.Int.pipe(Schema.between(0, 100)).annotations({
267
+ max_merge: Schema.Int.check(Schema.isBetween({
268
+ minimum: 0,
269
+ maximum: 100
270
+ })).annotate({
216
271
  title: "Max entries to merge",
217
272
  description: "Max PRs merged together in a group"
218
273
  }),
219
- merge_method: Schema.Literal("MERGE", "SQUASH", "REBASE").annotations({
274
+ merge_method: Schema.Literals([
275
+ "MERGE",
276
+ "SQUASH",
277
+ "REBASE"
278
+ ]).annotate({
220
279
  title: "Merge method",
221
280
  description: "Merge method for queued PRs"
222
281
  }),
223
- min_merge: Schema.Int.pipe(Schema.between(0, 100)).annotations({
282
+ min_merge: Schema.Int.check(Schema.isBetween({
283
+ minimum: 0,
284
+ maximum: 100
285
+ })).annotate({
224
286
  title: "Min entries to merge",
225
287
  description: "Min PRs merged together in a group"
226
288
  }),
227
- min_wait: Schema.Int.pipe(Schema.between(0, 360)).annotations({
289
+ min_wait: Schema.Int.check(Schema.isBetween({
290
+ minimum: 0,
291
+ maximum: 360
292
+ })).annotate({
228
293
  title: "Min wait time (minutes)",
229
294
  description: "Wait time for min group size after first PR is added"
230
295
  })
231
- }).annotations({
296
+ }).annotate({
232
297
  identifier: "MergeQueueShorthand",
233
298
  title: "Merge queue",
234
299
  description: "Merge queue configuration"
235
300
  });
236
301
  const CopilotReviewShorthandSchema = Schema.Struct({
237
- draft_prs: Schema.optional(Schema.Boolean.annotations({
302
+ draft_prs: Schema.optional(Schema.Boolean.annotate({
238
303
  title: "Review draft PRs",
239
304
  description: "Review draft PRs before they are marked ready"
240
305
  })),
241
- on_push: Schema.optional(Schema.Boolean.annotations({
306
+ on_push: Schema.optional(Schema.Boolean.annotate({
242
307
  title: "Review on push",
243
308
  description: "Review each new push to the PR"
244
309
  }))
245
- }).annotations({
310
+ }).annotate({
246
311
  identifier: "CopilotReviewShorthand",
247
312
  title: "Copilot review",
248
313
  description: "Copilot code review configuration"
249
314
  });
250
315
  const CodeScanningEntrySchema = Schema.Struct({
251
- tool: Schema.String.annotations({
316
+ tool: Schema.String.annotate({
252
317
  title: "Tool name",
253
318
  description: "Name of the code scanning tool"
254
319
  }),
255
- alerts: Schema.Literal("none", "errors", "errors_and_warnings", "all").annotations({
320
+ alerts: Schema.Literals([
321
+ "none",
322
+ "errors",
323
+ "errors_and_warnings",
324
+ "all"
325
+ ]).annotate({
256
326
  title: "Alerts threshold",
257
327
  description: "Severity level at which alerts block updates"
258
328
  }),
259
- security_alerts: Schema.Literal("none", "critical", "high_or_higher", "medium_or_higher", "all").annotations({
329
+ security_alerts: Schema.Literals([
330
+ "none",
331
+ "critical",
332
+ "high_or_higher",
333
+ "medium_or_higher",
334
+ "all"
335
+ ]).annotate({
260
336
  title: "Security alerts threshold",
261
337
  description: "Severity level at which security alerts block updates"
262
338
  })
263
- }).annotations({
339
+ }).annotate({
264
340
  identifier: "CodeScanningEntry",
265
341
  title: "Code scanning tool",
266
342
  description: "A code scanning tool with alert thresholds"
267
343
  });
268
344
  const WorkflowsShorthandSchema = Schema.Struct({
269
- on_creation: Schema.optional(Schema.Boolean.annotations({
345
+ on_creation: Schema.optional(Schema.Boolean.annotate({
270
346
  title: "Enforce on creation",
271
347
  description: "Enforce workflows when a branch is created (false = skip on creation)"
272
348
  })),
273
- required: Schema.Array(WorkflowFileSchema).annotations({
349
+ required: Schema.Array(WorkflowFileSchema).annotate({
274
350
  title: "Required workflows",
275
351
  description: "Workflows that must pass for this rule"
276
352
  })
277
- }).annotations({
353
+ }).annotate({
278
354
  identifier: "WorkflowsShorthand",
279
355
  title: "Workflows",
280
356
  description: "Required workflow configuration"
281
357
  });
282
358
  const sharedRulesetFields = {
283
- name: Schema.String.annotations({
359
+ name: Schema.String.annotate({
284
360
  title: "Ruleset name",
285
361
  description: "The name of the ruleset (used for matching when creating or updating)"
286
362
  }),
287
363
  enforcement: EnforcementSchema,
288
364
  conditions: Schema.optional(RulesetConditionsSchema),
289
365
  bypass_actors: Schema.optional(Schema.Array(BypassActorSchema)),
290
- creation: Schema.optional(Schema.Boolean.annotations({
366
+ creation: Schema.optional(Schema.Boolean.annotate({
291
367
  title: "Restrict creation",
292
368
  description: "When true, adds a creation rule"
293
369
  })),
294
- update: Schema.optional(Schema.Boolean.annotations({
370
+ update: Schema.optional(Schema.Boolean.annotate({
295
371
  title: "Restrict updates",
296
372
  description: "When true, adds an update rule with update_allows_fetch_and_merge: true"
297
373
  })),
298
- deletion: Schema.optional(Schema.Boolean.annotations({
374
+ deletion: Schema.optional(Schema.Boolean.annotate({
299
375
  title: "Restrict deletion",
300
376
  description: "When true, adds a deletion rule"
301
377
  })),
302
- required_linear_history: Schema.optional(Schema.Boolean.annotations({
378
+ required_linear_history: Schema.optional(Schema.Boolean.annotate({
303
379
  title: "Require linear history",
304
380
  description: "When true, adds a required_linear_history rule"
305
381
  })),
306
- required_signatures: Schema.optional(Schema.Boolean.annotations({
382
+ required_signatures: Schema.optional(Schema.Boolean.annotate({
307
383
  title: "Require signatures",
308
384
  description: "When true, adds a required_signatures rule"
309
385
  })),
310
- non_fast_forward: Schema.optional(Schema.Boolean.annotations({
386
+ non_fast_forward: Schema.optional(Schema.Boolean.annotate({
311
387
  title: "Prevent non-fast-forward",
312
388
  description: "When true, adds a non_fast_forward rule"
313
389
  })),
314
- deployments: Schema.optional(Schema.Array(Schema.String).annotations({
390
+ deployments: Schema.optional(Schema.Array(Schema.String).annotate({
315
391
  title: "Required deployments",
316
392
  description: "Deployment environments that must succeed; converts to required_deployments rule"
317
393
  })),
318
394
  targets: Schema.optional(TargetsSchema),
319
395
  status_checks: Schema.optional(StatusChecksShorthandSchema),
320
- commit_message: Schema.optional(Schema.Array(PatternEntrySchema).annotations({
396
+ commit_message: Schema.optional(Schema.Array(PatternEntrySchema).annotate({
321
397
  title: "Commit message patterns",
322
398
  description: "Commit message pattern rules"
323
399
  })),
324
- commit_author_email: Schema.optional(Schema.Array(PatternEntrySchema).annotations({
400
+ commit_author_email: Schema.optional(Schema.Array(PatternEntrySchema).annotate({
325
401
  title: "Commit author email patterns",
326
402
  description: "Commit author email pattern rules"
327
403
  })),
328
- committer_email: Schema.optional(Schema.Array(PatternEntrySchema).annotations({
404
+ committer_email: Schema.optional(Schema.Array(PatternEntrySchema).annotate({
329
405
  title: "Committer email patterns",
330
406
  description: "Committer email pattern rules"
331
407
  }))
332
408
  };
409
+ /**
410
+ * A ruleset targeting branches.
411
+ *
412
+ * @public
413
+ */
333
414
  const BranchRulesetSchema = Schema.Struct({
334
415
  ...sharedRulesetFields,
335
- type: Schema.Literal("branch").annotations({
416
+ type: Schema.Literal("branch").annotate({
336
417
  title: "Ruleset type",
337
418
  description: "This ruleset applies to branches"
338
419
  }),
339
420
  pull_requests: Schema.optional(PullRequestsShorthandSchema),
340
421
  merge_queue: Schema.optional(MergeQueueShorthandSchema),
341
422
  copilot_review: Schema.optional(CopilotReviewShorthandSchema),
342
- code_scanning: Schema.optional(Schema.Array(CodeScanningEntrySchema).annotations({
423
+ code_scanning: Schema.optional(Schema.Array(CodeScanningEntrySchema).annotate({
343
424
  title: "Code scanning tools",
344
425
  description: "Code scanning tool requirements"
345
426
  })),
346
427
  workflows: Schema.optional(WorkflowsShorthandSchema),
347
- branch_name: Schema.optional(Schema.Array(PatternEntrySchema).annotations({
428
+ branch_name: Schema.optional(Schema.Array(PatternEntrySchema).annotate({
348
429
  title: "Branch name patterns",
349
430
  description: "Branch name pattern rules"
350
431
  }))
351
- }).annotations({
432
+ }).annotate({
433
+ ...tombi({ tableKeysOrder: "schema" }),
434
+ ...taplo({
435
+ initKeys: [
436
+ "name",
437
+ "type",
438
+ "enforcement",
439
+ "targets"
440
+ ],
441
+ links: { key: docs("06-rulesets.md") }
442
+ }),
352
443
  identifier: "BranchRuleset",
353
444
  title: "Branch ruleset",
354
- description: "A ruleset that applies to branches",
355
- jsonSchema: {
356
- ...tombi({ tableKeysOrder: "schema" }),
357
- ...taplo({
358
- initKeys: [
359
- "name",
360
- "type",
361
- "enforcement",
362
- "targets"
363
- ],
364
- links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/rulesets.md" }
365
- })
366
- }
445
+ description: "A ruleset that applies to branches"
367
446
  });
447
+ /**
448
+ * A ruleset targeting tags.
449
+ *
450
+ * @public
451
+ */
368
452
  const TagRulesetSchema = Schema.Struct({
369
453
  ...sharedRulesetFields,
370
- type: Schema.Literal("tag").annotations({
454
+ type: Schema.Literal("tag").annotate({
371
455
  title: "Ruleset type",
372
456
  description: "This ruleset applies to tags"
373
457
  }),
374
- tag_name: Schema.optional(Schema.Array(PatternEntrySchema).annotations({
458
+ tag_name: Schema.optional(Schema.Array(PatternEntrySchema).annotate({
375
459
  title: "Tag name patterns",
376
460
  description: "Tag name pattern rules"
377
461
  }))
378
- }).annotations({
462
+ }).annotate({
463
+ ...tombi({ tableKeysOrder: "schema" }),
464
+ ...taplo({
465
+ initKeys: [
466
+ "name",
467
+ "type",
468
+ "enforcement",
469
+ "targets"
470
+ ],
471
+ links: { key: docs("06-rulesets.md") }
472
+ }),
379
473
  identifier: "TagRuleset",
380
474
  title: "Tag ruleset",
381
- description: "A ruleset that applies to tags",
382
- jsonSchema: {
383
- ...tombi({ tableKeysOrder: "schema" }),
384
- ...taplo({
385
- initKeys: [
386
- "name",
387
- "type",
388
- "enforcement",
389
- "targets"
390
- ],
391
- links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/rulesets.md" }
392
- })
393
- }
475
+ description: "A ruleset that applies to tags"
394
476
  });
395
- const RulesetSchema = Schema.Union(BranchRulesetSchema, TagRulesetSchema).annotations({
477
+ /**
478
+ * A repository ruleset, discriminated on `type`.
479
+ *
480
+ * @public
481
+ */
482
+ const RulesetSchema = Schema.Union([BranchRulesetSchema, TagRulesetSchema]).annotate({
483
+ ...taplo({ links: { key: docs("06-rulesets.md") } }),
396
484
  identifier: "Ruleset",
397
485
  title: "Repository ruleset",
398
- description: "A set of rules to apply when specified conditions are met",
399
- jsonSchema: taplo({ links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/rulesets.md" } })
486
+ description: "A set of rules to apply when specified conditions are met"
400
487
  });
401
488
  /**
402
- * Builds an API-compatible ruleset payload from the config-level Ruleset.
403
- * Converts all shorthand fields into the GitHub API rules array format.
489
+ * Build an API-compatible ruleset payload from a config-level {@link Ruleset}.
490
+ *
491
+ * @remarks
492
+ * Converts every shorthand field into the GitHub API's flat `rules` array. The
493
+ * shorthands exist so a TOML config can say `creation = true` instead of
494
+ * spelling out `{ type = "creation" }`; this is where that expansion happens,
495
+ * and it is the only place that knows the mapping.
496
+ *
497
+ * @public
404
498
  */
405
499
  function buildRulesetPayload(ruleset) {
406
500
  const rules = [];
@@ -513,23 +607,25 @@ function buildRulesetPayload(ruleset) {
513
607
  parameters: entry
514
608
  });
515
609
  let conditions = ruleset.conditions;
516
- if (ruleset.targets !== void 0) if (ruleset.targets === "default") conditions = { ref_name: {
517
- include: ["~DEFAULT_BRANCH"],
518
- exclude: []
519
- } };
520
- else if (ruleset.targets === "all") conditions = { ref_name: {
521
- include: ["~ALL"],
522
- exclude: []
523
- } };
524
- else {
525
- const include = [];
526
- const exclude = [];
527
- for (const pattern of ruleset.targets) if ("include" in pattern) include.push(pattern.include);
528
- else exclude.push(pattern.exclude);
529
- conditions = { ref_name: {
530
- include,
531
- exclude
610
+ if (ruleset.targets !== void 0) {
611
+ if (ruleset.targets === "default") conditions = { ref_name: {
612
+ include: ["~DEFAULT_BRANCH"],
613
+ exclude: []
614
+ } };
615
+ else if (ruleset.targets === "all") conditions = { ref_name: {
616
+ include: ["~ALL"],
617
+ exclude: []
532
618
  } };
619
+ else {
620
+ const include = [];
621
+ const exclude = [];
622
+ for (const pattern of ruleset.targets) if ("include" in pattern) include.push(pattern.include);
623
+ else exclude.push(pattern.exclude);
624
+ conditions = { ref_name: {
625
+ include,
626
+ exclude
627
+ } };
628
+ }
533
629
  }
534
630
  return {
535
631
  name: ruleset.name,
@@ -542,4 +638,4 @@ function buildRulesetPayload(ruleset) {
542
638
  }
543
639
 
544
640
  //#endregion
545
- export { BypassActorSchema, ResolvedRefSchema, RulesetSchema, buildRulesetPayload };
641
+ export { BranchRulesetSchema, BypassActorSchema, RefNameConditionSchema, ResolvedRefSchema, RulesetConditionsSchema, RulesetSchema, TagRulesetSchema, buildRulesetPayload };