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.
- package/README.md +87 -75
- package/bin/reposets.js +68 -17
- package/cli/commands/credentials.js +170 -49
- package/cli/commands/doctor.js +364 -91
- package/cli/commands/drift.js +48 -0
- package/cli/commands/history.js +203 -0
- package/cli/commands/init.js +110 -104
- package/cli/commands/list.js +60 -39
- package/cli/commands/nuke.js +127 -0
- package/cli/commands/sync.js +219 -59
- package/cli/commands/validate.js +57 -41
- package/cli/flags.js +36 -0
- package/cli/logger.js +48 -0
- package/index.d.ts +471 -1499
- package/index.js +3 -18
- package/lib/config-refs.js +76 -0
- package/lib/credential-labels.js +0 -0
- package/lib/fingerprint.js +52 -0
- package/lib/org-only.js +61 -0
- package/lib/schema-issues.js +50 -0
- package/package.json +11 -10
- package/schemas/annotations.js +81 -0
- package/schemas/common.js +83 -48
- package/schemas/config.js +214 -212
- package/schemas/credentials.js +190 -54
- package/schemas/environment.js +27 -21
- package/schemas/ruleset.js +235 -139
- package/services/ConfigFiles.js +126 -104
- package/services/CredentialResolver.js +97 -33
- package/services/OnePasswordClient.js +88 -16
- package/services/SyncLogger.js +107 -76
- package/store/AppliedState.js +0 -0
- package/store/RepoCache.js +86 -0
- package/store/SyncJournal.js +92 -0
- package/store/migrations.js +86 -0
- package/sync/SyncEngine.js +156 -0
- package/sync/decide.js +56 -0
- package/sync/phase.js +55 -0
- package/sync/phases/cleanup.js +220 -0
- package/sync/phases/code-scanning.js +187 -0
- package/sync/phases/environments.js +106 -0
- package/sync/phases/index.js +39 -0
- package/sync/phases/resource.js +149 -0
- package/sync/phases/rulesets.js +186 -0
- package/sync/phases/secrets.js +138 -0
- package/sync/phases/security.js +129 -0
- package/sync/phases/settings.js +274 -0
- package/sync/phases/variables.js +132 -0
- package/tsdoc-metadata.json +1 -1
- package/bin/reposets.d.ts +0 -1
- package/errors.js +0 -12
- package/lib/crypto.js +0 -27
- package/services/GitHubClient.js +0 -875
- package/services/SyncEngine.js +0 -580
package/schemas/ruleset.js
CHANGED
|
@@ -1,406 +1,500 @@
|
|
|
1
|
-
import { taplo, tombi } from "
|
|
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
|
-
|
|
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
|
-
}) }).
|
|
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.
|
|
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.
|
|
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).
|
|
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.
|
|
28
|
-
}).
|
|
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.
|
|
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
|
-
})
|
|
39
|
-
exclude: Schema.
|
|
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
|
-
})
|
|
43
|
-
}).
|
|
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
|
-
|
|
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).
|
|
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.
|
|
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.
|
|
93
|
+
id: Schema.Int.annotate({
|
|
64
94
|
title: "Team ID",
|
|
65
95
|
description: "Team ID"
|
|
66
96
|
}),
|
|
67
97
|
type: Schema.Literal("Team")
|
|
68
|
-
}).
|
|
98
|
+
}).annotate({ title: "Reviewer team" })
|
|
69
99
|
});
|
|
70
100
|
const StatusCheckSchema = Schema.Struct({
|
|
71
|
-
context: Schema.String.
|
|
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).
|
|
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.
|
|
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.
|
|
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).
|
|
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.
|
|
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.
|
|
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.
|
|
131
|
+
}) }), Schema.Struct({ exclude: Schema.String.annotate({
|
|
102
132
|
title: "Exclude pattern",
|
|
103
133
|
description: "Glob pattern to exclude"
|
|
104
|
-
}) })).
|
|
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.
|
|
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).
|
|
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
|
-
})).
|
|
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.
|
|
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
|
-
})
|
|
125
|
-
dismiss_stale_reviews: Schema.
|
|
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
|
-
})
|
|
129
|
-
code_owner_review: Schema.
|
|
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
|
-
})
|
|
133
|
-
last_push_approval: Schema.
|
|
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
|
-
})
|
|
137
|
-
resolve_threads: Schema.
|
|
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
|
-
})
|
|
141
|
-
merge_methods: Schema.optional(Schema.Array(Schema.
|
|
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).
|
|
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
|
-
}).
|
|
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.
|
|
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.
|
|
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).
|
|
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).
|
|
204
|
+
required: Schema.Array(StatusCheckSchema).annotate({
|
|
168
205
|
title: "Required checks",
|
|
169
206
|
description: "Status checks that must pass"
|
|
170
207
|
})
|
|
171
|
-
}).
|
|
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.
|
|
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.
|
|
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.
|
|
231
|
+
pattern: Schema.String.annotate({
|
|
186
232
|
title: "Pattern",
|
|
187
233
|
description: "The pattern to match"
|
|
188
234
|
}),
|
|
189
|
-
name: Schema.optional(Schema.String.
|
|
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.
|
|
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
|
-
}).
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
}).
|
|
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.
|
|
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.
|
|
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
|
-
}).
|
|
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.
|
|
316
|
+
tool: Schema.String.annotate({
|
|
252
317
|
title: "Tool name",
|
|
253
318
|
description: "Name of the code scanning tool"
|
|
254
319
|
}),
|
|
255
|
-
alerts: Schema.
|
|
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.
|
|
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
|
-
}).
|
|
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.
|
|
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).
|
|
349
|
+
required: Schema.Array(WorkflowFileSchema).annotate({
|
|
274
350
|
title: "Required workflows",
|
|
275
351
|
description: "Workflows that must pass for this rule"
|
|
276
352
|
})
|
|
277
|
-
}).
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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).
|
|
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).
|
|
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).
|
|
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).
|
|
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").
|
|
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).
|
|
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).
|
|
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
|
-
}).
|
|
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").
|
|
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).
|
|
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
|
-
}).
|
|
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
|
-
|
|
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
|
-
*
|
|
403
|
-
*
|
|
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)
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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 };
|