reposets 0.4.1 → 0.4.2
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 +15 -14
- package/bin/reposets.d.ts +1 -0
- package/bin/reposets.js +25 -507
- package/cli/commands/credentials.js +74 -0
- package/cli/commands/doctor.js +154 -0
- package/cli/commands/init.js +147 -0
- package/cli/commands/list.js +49 -0
- package/cli/commands/sync.js +70 -0
- package/cli/commands/validate.js +53 -0
- package/errors.js +12 -0
- package/index.d.ts +1750 -1840
- package/index.js +19 -2
- package/lib/crypto.js +27 -0
- package/package.json +61 -73
- package/schemas/common.js +130 -0
- package/schemas/config.js +469 -0
- package/schemas/credentials.js +78 -0
- package/schemas/environment.js +70 -0
- package/schemas/ruleset.js +545 -0
- package/services/ConfigFiles.js +119 -0
- package/services/CredentialResolver.js +40 -0
- package/services/GitHubClient.js +875 -0
- package/services/OnePasswordClient.js +30 -0
- package/services/SyncEngine.js +580 -0
- package/services/SyncLogger.js +106 -0
- package/tsdoc-metadata.json +11 -11
- package/500.js +0 -3354
package/500.js
DELETED
|
@@ -1,3354 +0,0 @@
|
|
|
1
|
-
import { Context, Data, Effect, Layer, Option, Ref, Schema } from "effect";
|
|
2
|
-
import { blake2b } from "blakejs";
|
|
3
|
-
import tweetnacl from "tweetnacl";
|
|
4
|
-
import { AppDirsConfig, ConfigError, ConfigFile, ExplicitPath, FirstMatch, Jsonifiable, StaticDir, TomlCodec, UpwardWalk, XdgConfigLive, XdgConfigResolver, XdgSavePath, taplo, tombi } from "xdg-effect";
|
|
5
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
6
|
-
import { isAbsolute, resolve } from "node:path";
|
|
7
|
-
import { Octokit } from "@octokit/rest";
|
|
8
|
-
class ResolveError extends Data.TaggedError("ResolveError") {
|
|
9
|
-
}
|
|
10
|
-
class OnePasswordError extends Data.TaggedError("OnePasswordError") {
|
|
11
|
-
}
|
|
12
|
-
class GitHubApiError extends Data.TaggedError("GitHubApiError") {
|
|
13
|
-
}
|
|
14
|
-
class SyncError extends Data.TaggedError("SyncError") {
|
|
15
|
-
}
|
|
16
|
-
function encryptSecret(publicKey, secretValue) {
|
|
17
|
-
const messageBytes = Buffer.from(secretValue);
|
|
18
|
-
const publicKeyBytes = Buffer.from(publicKey, "base64");
|
|
19
|
-
const ephemeralKeyPair = tweetnacl.box.keyPair();
|
|
20
|
-
const nonceInput = new Uint8Array(64);
|
|
21
|
-
nonceInput.set(ephemeralKeyPair.publicKey);
|
|
22
|
-
nonceInput.set(publicKeyBytes, 32);
|
|
23
|
-
const nonce = blake2b(nonceInput, void 0, 24);
|
|
24
|
-
const ciphertext = tweetnacl.box(messageBytes, nonce, publicKeyBytes, ephemeralKeyPair.secretKey);
|
|
25
|
-
const sealed = new Uint8Array(ephemeralKeyPair.publicKey.length + ciphertext.length);
|
|
26
|
-
sealed.set(ephemeralKeyPair.publicKey);
|
|
27
|
-
sealed.set(ciphertext, ephemeralKeyPair.publicKey.length);
|
|
28
|
-
return Buffer.from(sealed).toString("base64");
|
|
29
|
-
}
|
|
30
|
-
const ResourceFileKind = Schema.Struct({
|
|
31
|
-
file: Schema.Record({
|
|
32
|
-
key: Schema.String,
|
|
33
|
-
value: Schema.String
|
|
34
|
-
}).annotations({
|
|
35
|
-
title: "File entries",
|
|
36
|
-
description: "Named entries with file path values, resolved relative to config directory",
|
|
37
|
-
jsonSchema: tombi({
|
|
38
|
-
additionalKeyLabel: "name"
|
|
39
|
-
})
|
|
40
|
-
})
|
|
41
|
-
});
|
|
42
|
-
const ResourceValueKind = Schema.Struct({
|
|
43
|
-
value: Schema.Record({
|
|
44
|
-
key: Schema.String,
|
|
45
|
-
value: Schema.Union(Schema.String, Schema.Record({
|
|
46
|
-
key: Schema.String,
|
|
47
|
-
value: Jsonifiable
|
|
48
|
-
}))
|
|
49
|
-
}).annotations({
|
|
50
|
-
title: "Value entries",
|
|
51
|
-
description: "Named entries with inline values. Strings used as-is, objects JSON-stringified.",
|
|
52
|
-
jsonSchema: tombi({
|
|
53
|
-
additionalKeyLabel: "name"
|
|
54
|
-
})
|
|
55
|
-
})
|
|
56
|
-
});
|
|
57
|
-
const ResourceResolvedKind = Schema.Struct({
|
|
58
|
-
resolved: Schema.Record({
|
|
59
|
-
key: Schema.String,
|
|
60
|
-
value: Schema.String
|
|
61
|
-
}).annotations({
|
|
62
|
-
title: "Resolved entries",
|
|
63
|
-
description: "Named entries mapped to credential labels. Values come from the active credential profile.",
|
|
64
|
-
jsonSchema: tombi({
|
|
65
|
-
additionalKeyLabel: "name"
|
|
66
|
-
})
|
|
67
|
-
})
|
|
68
|
-
});
|
|
69
|
-
const SecretGroupSchema = Schema.Union(ResourceFileKind, ResourceValueKind, ResourceResolvedKind).annotations({
|
|
70
|
-
identifier: "SecretGroup",
|
|
71
|
-
title: "Secret group",
|
|
72
|
-
description: "A group of secrets. Must be exactly one kind: file, value, or resolved.",
|
|
73
|
-
jsonSchema: taplo({
|
|
74
|
-
links: {
|
|
75
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/secrets-and-variables.md"
|
|
76
|
-
}
|
|
77
|
-
})
|
|
78
|
-
});
|
|
79
|
-
const VariableGroupSchema = Schema.Union(ResourceFileKind, ResourceValueKind, ResourceResolvedKind).annotations({
|
|
80
|
-
identifier: "VariableGroup",
|
|
81
|
-
title: "Variable group",
|
|
82
|
-
description: "A group of variables. Must be exactly one kind: file, value, or resolved.",
|
|
83
|
-
jsonSchema: taplo({
|
|
84
|
-
links: {
|
|
85
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/secrets-and-variables.md"
|
|
86
|
-
}
|
|
87
|
-
})
|
|
88
|
-
});
|
|
89
|
-
const CleanupScopeSchema = Schema.Union(Schema.Boolean, Schema.Struct({
|
|
90
|
-
preserve: Schema.Array(Schema.String).annotations({
|
|
91
|
-
title: "Preserve list",
|
|
92
|
-
description: "Resource names that should never be deleted during cleanup",
|
|
93
|
-
examples: [
|
|
94
|
-
[
|
|
95
|
-
"LEGACY_TOKEN",
|
|
96
|
-
"DEPLOY_KEY"
|
|
97
|
-
]
|
|
98
|
-
]
|
|
99
|
-
})
|
|
100
|
-
})).annotations({
|
|
101
|
-
identifier: "CleanupScope",
|
|
102
|
-
title: "Cleanup scope",
|
|
103
|
-
description: "Controls cleanup for a single resource scope. false disables cleanup, true enables full cleanup, or specify names to preserve."
|
|
104
|
-
});
|
|
105
|
-
const CleanupSecretsSchema = Schema.Struct({
|
|
106
|
-
actions: Schema.optionalWith(CleanupScopeSchema, {
|
|
107
|
-
default: ()=>false
|
|
108
|
-
}).annotations({
|
|
109
|
-
title: "Clean up Actions secrets",
|
|
110
|
-
description: "Delete Actions secrets not declared in any referenced secret group",
|
|
111
|
-
default: false
|
|
112
|
-
}),
|
|
113
|
-
dependabot: Schema.optionalWith(CleanupScopeSchema, {
|
|
114
|
-
default: ()=>false
|
|
115
|
-
}).annotations({
|
|
116
|
-
title: "Clean up Dependabot secrets",
|
|
117
|
-
description: "Delete Dependabot secrets not declared in any referenced secret group",
|
|
118
|
-
default: false
|
|
119
|
-
}),
|
|
120
|
-
codespaces: Schema.optionalWith(CleanupScopeSchema, {
|
|
121
|
-
default: ()=>false
|
|
122
|
-
}).annotations({
|
|
123
|
-
title: "Clean up Codespaces secrets",
|
|
124
|
-
description: "Delete Codespaces secrets not declared in any referenced secret group",
|
|
125
|
-
default: false
|
|
126
|
-
}),
|
|
127
|
-
environments: Schema.optionalWith(CleanupScopeSchema, {
|
|
128
|
-
default: ()=>false
|
|
129
|
-
}).annotations({
|
|
130
|
-
title: "Clean up environment secrets",
|
|
131
|
-
description: "Delete environment secrets not declared in any referenced secret group",
|
|
132
|
-
default: false
|
|
133
|
-
})
|
|
134
|
-
}).annotations({
|
|
135
|
-
identifier: "CleanupSecrets",
|
|
136
|
-
title: "Secrets cleanup configuration",
|
|
137
|
-
description: "Controls deletion of secrets by scope (Actions, Dependabot, Codespaces, environments)."
|
|
138
|
-
});
|
|
139
|
-
const CleanupVariablesSchema = Schema.Struct({
|
|
140
|
-
actions: Schema.optionalWith(CleanupScopeSchema, {
|
|
141
|
-
default: ()=>false
|
|
142
|
-
}).annotations({
|
|
143
|
-
title: "Clean up Actions variables",
|
|
144
|
-
description: "Delete Actions variables not declared in any referenced variable group",
|
|
145
|
-
default: false
|
|
146
|
-
}),
|
|
147
|
-
environments: Schema.optionalWith(CleanupScopeSchema, {
|
|
148
|
-
default: ()=>false
|
|
149
|
-
}).annotations({
|
|
150
|
-
title: "Clean up environment variables",
|
|
151
|
-
description: "Delete environment variables not declared in any referenced variable group",
|
|
152
|
-
default: false
|
|
153
|
-
})
|
|
154
|
-
}).annotations({
|
|
155
|
-
identifier: "CleanupVariables",
|
|
156
|
-
title: "Variables cleanup configuration",
|
|
157
|
-
description: "Controls deletion of variables by scope (Actions, environments)."
|
|
158
|
-
});
|
|
159
|
-
const CleanupSchema = Schema.Struct({
|
|
160
|
-
secrets: Schema.optionalWith(CleanupSecretsSchema, {
|
|
161
|
-
default: ()=>({
|
|
162
|
-
actions: false,
|
|
163
|
-
dependabot: false,
|
|
164
|
-
codespaces: false,
|
|
165
|
-
environments: false
|
|
166
|
-
})
|
|
167
|
-
}).annotations({
|
|
168
|
-
title: "Secrets cleanup",
|
|
169
|
-
description: "Controls cleanup of secrets by scope"
|
|
170
|
-
}),
|
|
171
|
-
variables: Schema.optionalWith(CleanupVariablesSchema, {
|
|
172
|
-
default: ()=>({
|
|
173
|
-
actions: false,
|
|
174
|
-
environments: false
|
|
175
|
-
})
|
|
176
|
-
}).annotations({
|
|
177
|
-
title: "Variables cleanup",
|
|
178
|
-
description: "Controls cleanup of variables by scope"
|
|
179
|
-
}),
|
|
180
|
-
rulesets: Schema.optionalWith(CleanupScopeSchema, {
|
|
181
|
-
default: ()=>false
|
|
182
|
-
}).annotations({
|
|
183
|
-
title: "Clean up rulesets",
|
|
184
|
-
description: "Delete repository rulesets not declared in any referenced ruleset group",
|
|
185
|
-
default: false
|
|
186
|
-
}),
|
|
187
|
-
environments: Schema.optionalWith(CleanupScopeSchema, {
|
|
188
|
-
default: ()=>false
|
|
189
|
-
}).annotations({
|
|
190
|
-
title: "Clean up environments",
|
|
191
|
-
description: "Delete repository environments not declared in config",
|
|
192
|
-
default: false
|
|
193
|
-
})
|
|
194
|
-
}).annotations({
|
|
195
|
-
identifier: "Cleanup",
|
|
196
|
-
title: "Cleanup configuration",
|
|
197
|
-
description: "Controls deletion of resources not declared in config. All disabled by default.",
|
|
198
|
-
jsonSchema: taplo({
|
|
199
|
-
links: {
|
|
200
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/cleanup.md"
|
|
201
|
-
}
|
|
202
|
-
})
|
|
203
|
-
});
|
|
204
|
-
const ReviewerTypeSchema = Schema.Literal("User", "Team").annotations({
|
|
205
|
-
title: "Reviewer type",
|
|
206
|
-
description: "Whether the reviewer is an individual user or a team"
|
|
207
|
-
});
|
|
208
|
-
const ReviewerSchema = Schema.Struct({
|
|
209
|
-
type: ReviewerTypeSchema,
|
|
210
|
-
id: Schema.Int.annotations({
|
|
211
|
-
title: "Reviewer ID",
|
|
212
|
-
description: "The numeric GitHub ID of the user or team"
|
|
213
|
-
})
|
|
214
|
-
}).annotations({
|
|
215
|
-
identifier: "Reviewer",
|
|
216
|
-
title: "Reviewer",
|
|
217
|
-
description: "A user or team required to review deployments"
|
|
218
|
-
});
|
|
219
|
-
const DeploymentBranchPolicySchema = Schema.Struct({
|
|
220
|
-
name: Schema.String.annotations({
|
|
221
|
-
title: "Pattern",
|
|
222
|
-
description: "The name pattern (branch name, tag name, or glob) to allow deployments from"
|
|
223
|
-
}),
|
|
224
|
-
type: Schema.optionalWith(Schema.Literal("branch", "tag").annotations({
|
|
225
|
-
title: "Policy type",
|
|
226
|
-
description: 'Whether this policy matches branches or tags. Defaults to "branch".'
|
|
227
|
-
}), {
|
|
228
|
-
default: ()=>"branch"
|
|
229
|
-
})
|
|
230
|
-
}).annotations({
|
|
231
|
-
identifier: "DeploymentBranchPolicy",
|
|
232
|
-
title: "Deployment branch policy",
|
|
233
|
-
description: "A custom branch or tag pattern that deployments are allowed from"
|
|
234
|
-
});
|
|
235
|
-
const DeploymentBranchesSchema = Schema.Union(Schema.Literal("all", "protected").annotations({
|
|
236
|
-
title: "Deployment branch preset",
|
|
237
|
-
description: '"all" allows any branch, "protected" allows only protected branches'
|
|
238
|
-
}), Schema.Array(DeploymentBranchPolicySchema).annotations({
|
|
239
|
-
title: "Custom deployment policies",
|
|
240
|
-
description: "Array of branch or tag name patterns allowed to deploy to this environment"
|
|
241
|
-
})).annotations({
|
|
242
|
-
identifier: "DeploymentBranches",
|
|
243
|
-
title: "Deployment branches",
|
|
244
|
-
description: 'Controls which branches can deploy. Use "all", "protected", or a list of custom policies.'
|
|
245
|
-
});
|
|
246
|
-
const EnvironmentSchema = Schema.Struct({
|
|
247
|
-
wait_timer: Schema.optional(Schema.Int.pipe(Schema.between(0, 43200)).annotations({
|
|
248
|
-
title: "Wait timer (minutes)",
|
|
249
|
-
description: "Number of minutes to wait before allowing deployments to proceed (0-43200)"
|
|
250
|
-
})),
|
|
251
|
-
prevent_self_review: Schema.optional(Schema.Boolean.annotations({
|
|
252
|
-
title: "Prevent self-review",
|
|
253
|
-
description: "Prevent the user who triggered the deployment from approving it"
|
|
254
|
-
})),
|
|
255
|
-
reviewers: Schema.optional(Schema.Array(ReviewerSchema).annotations({
|
|
256
|
-
title: "Required reviewers",
|
|
257
|
-
description: "Users or teams required to approve deployments to this environment"
|
|
258
|
-
})),
|
|
259
|
-
deployment_branches: Schema.optional(DeploymentBranchesSchema)
|
|
260
|
-
}).annotations({
|
|
261
|
-
identifier: "Environment",
|
|
262
|
-
title: "Deployment environment",
|
|
263
|
-
description: "Configuration for a GitHub deployment environment",
|
|
264
|
-
jsonSchema: {
|
|
265
|
-
...tombi({
|
|
266
|
-
tableKeysOrder: "schema"
|
|
267
|
-
}),
|
|
268
|
-
...taplo({
|
|
269
|
-
links: {
|
|
270
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/environments.md"
|
|
271
|
-
}
|
|
272
|
-
})
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
const ResolvedRefSchema = Schema.Struct({
|
|
276
|
-
resolved: Schema.String.annotations({
|
|
277
|
-
title: "Credential label",
|
|
278
|
-
description: "Reference to a named value in the active credential profile's resolve section"
|
|
279
|
-
})
|
|
280
|
-
}).annotations({
|
|
281
|
-
identifier: "ResolvedRef",
|
|
282
|
-
title: "Resolved reference",
|
|
283
|
-
description: "A reference to a credential-resolved value"
|
|
284
|
-
});
|
|
285
|
-
const ActorTypeSchema = Schema.Literal("Integration", "OrganizationAdmin", "RepositoryRole", "Team", "DeployKey").annotations({
|
|
286
|
-
title: "Actor type",
|
|
287
|
-
description: "The type of actor that can bypass a ruleset"
|
|
288
|
-
});
|
|
289
|
-
const BypassModeSchema = Schema.Literal("always", "pull_request", "exempt").annotations({
|
|
290
|
-
title: "Bypass mode",
|
|
291
|
-
description: "When the specified actor can bypass the ruleset"
|
|
292
|
-
});
|
|
293
|
-
const BypassActorSchema = Schema.Struct({
|
|
294
|
-
actor_id: Schema.optional(Schema.Union(Schema.Int, ResolvedRefSchema).annotations({
|
|
295
|
-
title: "Actor ID",
|
|
296
|
-
description: "The ID of the actor, or a { resolved } reference to a credential label."
|
|
297
|
-
})),
|
|
298
|
-
actor_type: ActorTypeSchema,
|
|
299
|
-
bypass_mode: Schema.optionalWith(BypassModeSchema, {
|
|
300
|
-
default: ()=>"always"
|
|
301
|
-
})
|
|
302
|
-
}).annotations({
|
|
303
|
-
identifier: "BypassActor",
|
|
304
|
-
title: "Bypass actor",
|
|
305
|
-
description: "An actor that can bypass rules in a ruleset"
|
|
306
|
-
});
|
|
307
|
-
const RefNameConditionSchema = Schema.Struct({
|
|
308
|
-
include: Schema.optionalWith(Schema.Array(Schema.String).annotations({
|
|
309
|
-
title: "Include patterns",
|
|
310
|
-
description: "Ref name patterns to include. Accepts ~DEFAULT_BRANCH, ~ALL, or glob patterns.",
|
|
311
|
-
examples: [
|
|
312
|
-
[
|
|
313
|
-
"~DEFAULT_BRANCH"
|
|
314
|
-
]
|
|
315
|
-
]
|
|
316
|
-
}), {
|
|
317
|
-
default: ()=>[]
|
|
318
|
-
}),
|
|
319
|
-
exclude: Schema.optionalWith(Schema.Array(Schema.String).annotations({
|
|
320
|
-
title: "Exclude patterns",
|
|
321
|
-
description: "Ref name patterns to exclude"
|
|
322
|
-
}), {
|
|
323
|
-
default: ()=>[]
|
|
324
|
-
})
|
|
325
|
-
}).annotations({
|
|
326
|
-
identifier: "RefNameCondition",
|
|
327
|
-
title: "Ref name condition",
|
|
328
|
-
description: "Conditions for matching ref names (branches or tags)"
|
|
329
|
-
});
|
|
330
|
-
const RulesetConditionsSchema = Schema.Struct({
|
|
331
|
-
ref_name: Schema.optional(RefNameConditionSchema)
|
|
332
|
-
}).annotations({
|
|
333
|
-
identifier: "RulesetConditions",
|
|
334
|
-
title: "Ruleset conditions",
|
|
335
|
-
description: "Conditions that determine when the ruleset applies"
|
|
336
|
-
});
|
|
337
|
-
const RequiredReviewerSchema = Schema.Struct({
|
|
338
|
-
file_patterns: Schema.Array(Schema.String).annotations({
|
|
339
|
-
title: "File patterns",
|
|
340
|
-
description: "File patterns this reviewer must approve (fnmatch syntax)"
|
|
341
|
-
}),
|
|
342
|
-
minimum_approvals: Schema.Int.annotations({
|
|
343
|
-
title: "Minimum approvals",
|
|
344
|
-
description: "Minimum approvals required from this team (0 = optional)"
|
|
345
|
-
}),
|
|
346
|
-
reviewer: Schema.Struct({
|
|
347
|
-
id: Schema.Int.annotations({
|
|
348
|
-
title: "Team ID",
|
|
349
|
-
description: "Team ID"
|
|
350
|
-
}),
|
|
351
|
-
type: Schema.Literal("Team")
|
|
352
|
-
}).annotations({
|
|
353
|
-
title: "Reviewer team"
|
|
354
|
-
})
|
|
355
|
-
});
|
|
356
|
-
const StatusCheckSchema = Schema.Struct({
|
|
357
|
-
context: Schema.String.annotations({
|
|
358
|
-
title: "Context",
|
|
359
|
-
description: "The status check context name that must be present on the commit"
|
|
360
|
-
}),
|
|
361
|
-
integration_id: Schema.optional(Schema.Union(Schema.Int, ResolvedRefSchema).annotations({
|
|
362
|
-
title: "Integration ID",
|
|
363
|
-
description: "The integration ID, or a { resolved } reference to a credential label"
|
|
364
|
-
}))
|
|
365
|
-
});
|
|
366
|
-
const WorkflowFileSchema = Schema.Struct({
|
|
367
|
-
path: Schema.String.annotations({
|
|
368
|
-
title: "Workflow path",
|
|
369
|
-
description: "Path to the workflow file"
|
|
370
|
-
}),
|
|
371
|
-
ref: Schema.optional(Schema.String.annotations({
|
|
372
|
-
title: "Ref",
|
|
373
|
-
description: "Branch or tag of the workflow file"
|
|
374
|
-
})),
|
|
375
|
-
repository_id: Schema.Union(Schema.Int, ResolvedRefSchema).annotations({
|
|
376
|
-
title: "Repository ID",
|
|
377
|
-
description: "Repository ID, or a { resolved } reference to a credential label"
|
|
378
|
-
}),
|
|
379
|
-
sha: Schema.optional(Schema.String.annotations({
|
|
380
|
-
title: "SHA",
|
|
381
|
-
description: "Commit SHA of the workflow file"
|
|
382
|
-
}))
|
|
383
|
-
});
|
|
384
|
-
const TargetPatternSchema = Schema.Union(Schema.Struct({
|
|
385
|
-
include: Schema.String.annotations({
|
|
386
|
-
title: "Include pattern",
|
|
387
|
-
description: "Glob pattern to include"
|
|
388
|
-
})
|
|
389
|
-
}), Schema.Struct({
|
|
390
|
-
exclude: Schema.String.annotations({
|
|
391
|
-
title: "Exclude pattern",
|
|
392
|
-
description: "Glob pattern to exclude"
|
|
393
|
-
})
|
|
394
|
-
})).annotations({
|
|
395
|
-
identifier: "TargetPattern",
|
|
396
|
-
title: "Target pattern",
|
|
397
|
-
description: "An include or exclude pattern for ref matching"
|
|
398
|
-
});
|
|
399
|
-
const TargetsSchema = Schema.Union(Schema.Literal("default", "all").annotations({
|
|
400
|
-
title: "Target preset",
|
|
401
|
-
description: "'default' targets the default branch; 'all' targets all branches/tags"
|
|
402
|
-
}), Schema.Array(TargetPatternSchema).annotations({
|
|
403
|
-
title: "Custom target patterns",
|
|
404
|
-
description: "Array of include/exclude patterns for fine-grained ref targeting"
|
|
405
|
-
})).annotations({
|
|
406
|
-
identifier: "Targets",
|
|
407
|
-
title: "Targets shorthand",
|
|
408
|
-
description: "Shorthand for specifying ref_name conditions: 'default', 'all', or custom patterns"
|
|
409
|
-
});
|
|
410
|
-
const PullRequestsShorthandSchema = Schema.Struct({
|
|
411
|
-
approvals: Schema.optionalWith(Schema.Int.pipe(Schema.between(0, 10)).annotations({
|
|
412
|
-
title: "Required approvals",
|
|
413
|
-
description: "Number of approving reviews required (0-10)"
|
|
414
|
-
}), {
|
|
415
|
-
default: ()=>0
|
|
416
|
-
}),
|
|
417
|
-
dismiss_stale_reviews: Schema.optionalWith(Schema.Boolean.annotations({
|
|
418
|
-
title: "Dismiss stale reviews",
|
|
419
|
-
description: "Dismiss previous approvals when new commits are pushed"
|
|
420
|
-
}), {
|
|
421
|
-
default: ()=>false
|
|
422
|
-
}),
|
|
423
|
-
code_owner_review: Schema.optionalWith(Schema.Boolean.annotations({
|
|
424
|
-
title: "Code owner review",
|
|
425
|
-
description: "Require review from code owners for files they own"
|
|
426
|
-
}), {
|
|
427
|
-
default: ()=>false
|
|
428
|
-
}),
|
|
429
|
-
last_push_approval: Schema.optionalWith(Schema.Boolean.annotations({
|
|
430
|
-
title: "Last push approval",
|
|
431
|
-
description: "Most recent push must be approved by someone other than the pusher"
|
|
432
|
-
}), {
|
|
433
|
-
default: ()=>false
|
|
434
|
-
}),
|
|
435
|
-
resolve_threads: Schema.optionalWith(Schema.Boolean.annotations({
|
|
436
|
-
title: "Resolve threads",
|
|
437
|
-
description: "All review conversations must be resolved before merging"
|
|
438
|
-
}), {
|
|
439
|
-
default: ()=>false
|
|
440
|
-
}),
|
|
441
|
-
merge_methods: Schema.optional(Schema.Array(Schema.Literal("merge", "squash", "rebase")).annotations({
|
|
442
|
-
title: "Merge methods",
|
|
443
|
-
description: "Allowed merge methods. At least one must be enabled."
|
|
444
|
-
})),
|
|
445
|
-
reviewers: Schema.optional(Schema.Array(RequiredReviewerSchema).annotations({
|
|
446
|
-
title: "Required reviewers",
|
|
447
|
-
description: "Teams that must approve specific file patterns"
|
|
448
|
-
}))
|
|
449
|
-
}).annotations({
|
|
450
|
-
identifier: "PullRequestsShorthand",
|
|
451
|
-
title: "Pull requests shorthand",
|
|
452
|
-
description: "Simplified pull request configuration (branch rulesets only)"
|
|
453
|
-
});
|
|
454
|
-
const StatusChecksShorthandSchema = Schema.Struct({
|
|
455
|
-
update_branch: Schema.optional(Schema.Boolean.annotations({
|
|
456
|
-
title: "Strict status checks",
|
|
457
|
-
description: "PRs must be tested with the latest code"
|
|
458
|
-
})),
|
|
459
|
-
on_creation: Schema.optional(Schema.Boolean.annotations({
|
|
460
|
-
title: "Enforce on create",
|
|
461
|
-
description: "When false, allows branch creation even if checks would prohibit it"
|
|
462
|
-
})),
|
|
463
|
-
default_integration_id: Schema.optional(Schema.Union(Schema.Int, ResolvedRefSchema).annotations({
|
|
464
|
-
title: "Default integration ID",
|
|
465
|
-
description: "Default integration ID applied to all checks that do not specify one"
|
|
466
|
-
})),
|
|
467
|
-
required: Schema.Array(StatusCheckSchema).annotations({
|
|
468
|
-
title: "Required checks",
|
|
469
|
-
description: "Status checks that must pass"
|
|
470
|
-
})
|
|
471
|
-
}).annotations({
|
|
472
|
-
identifier: "StatusChecksShorthand",
|
|
473
|
-
title: "Status checks shorthand",
|
|
474
|
-
description: "Simplified status checks configuration"
|
|
475
|
-
});
|
|
476
|
-
const EnforcementSchema = Schema.Literal("disabled", "active", "evaluate").annotations({
|
|
477
|
-
title: "Enforcement level",
|
|
478
|
-
description: "disabled = off, active = enforced, evaluate = test mode (GitHub Enterprise only)"
|
|
479
|
-
});
|
|
480
|
-
const PatternEntrySchema = Schema.Struct({
|
|
481
|
-
operator: Schema.Literal("starts_with", "ends_with", "contains", "regex").annotations({
|
|
482
|
-
title: "Operator",
|
|
483
|
-
description: "The operator to use for matching"
|
|
484
|
-
}),
|
|
485
|
-
pattern: Schema.String.annotations({
|
|
486
|
-
title: "Pattern",
|
|
487
|
-
description: "The pattern to match"
|
|
488
|
-
}),
|
|
489
|
-
name: Schema.optional(Schema.String.annotations({
|
|
490
|
-
title: "Rule name",
|
|
491
|
-
description: "Display name for this pattern rule"
|
|
492
|
-
})),
|
|
493
|
-
negate: Schema.optional(Schema.Boolean.annotations({
|
|
494
|
-
title: "Negate",
|
|
495
|
-
description: "If true, the rule fails when the pattern matches"
|
|
496
|
-
}))
|
|
497
|
-
}).annotations({
|
|
498
|
-
identifier: "PatternEntry",
|
|
499
|
-
title: "Pattern entry",
|
|
500
|
-
description: "A pattern matching rule with operator, pattern, and optional name/negate"
|
|
501
|
-
});
|
|
502
|
-
const MergeQueueShorthandSchema = Schema.Struct({
|
|
503
|
-
check_timeout: Schema.Int.pipe(Schema.between(1, 360)).annotations({
|
|
504
|
-
title: "Check timeout (minutes)",
|
|
505
|
-
description: "Max time for status checks to report"
|
|
506
|
-
}),
|
|
507
|
-
grouping: Schema.Literal("ALLGREEN", "HEADGREEN").annotations({
|
|
508
|
-
title: "Grouping strategy",
|
|
509
|
-
description: "Whether all commits or only the head commit must pass checks"
|
|
510
|
-
}),
|
|
511
|
-
max_build: Schema.Int.pipe(Schema.between(0, 100)).annotations({
|
|
512
|
-
title: "Max entries to build",
|
|
513
|
-
description: "Max queued PRs requesting checks simultaneously"
|
|
514
|
-
}),
|
|
515
|
-
max_merge: Schema.Int.pipe(Schema.between(0, 100)).annotations({
|
|
516
|
-
title: "Max entries to merge",
|
|
517
|
-
description: "Max PRs merged together in a group"
|
|
518
|
-
}),
|
|
519
|
-
merge_method: Schema.Literal("MERGE", "SQUASH", "REBASE").annotations({
|
|
520
|
-
title: "Merge method",
|
|
521
|
-
description: "Merge method for queued PRs"
|
|
522
|
-
}),
|
|
523
|
-
min_merge: Schema.Int.pipe(Schema.between(0, 100)).annotations({
|
|
524
|
-
title: "Min entries to merge",
|
|
525
|
-
description: "Min PRs merged together in a group"
|
|
526
|
-
}),
|
|
527
|
-
min_wait: Schema.Int.pipe(Schema.between(0, 360)).annotations({
|
|
528
|
-
title: "Min wait time (minutes)",
|
|
529
|
-
description: "Wait time for min group size after first PR is added"
|
|
530
|
-
})
|
|
531
|
-
}).annotations({
|
|
532
|
-
identifier: "MergeQueueShorthand",
|
|
533
|
-
title: "Merge queue",
|
|
534
|
-
description: "Merge queue configuration"
|
|
535
|
-
});
|
|
536
|
-
const CopilotReviewShorthandSchema = Schema.Struct({
|
|
537
|
-
draft_prs: Schema.optional(Schema.Boolean.annotations({
|
|
538
|
-
title: "Review draft PRs",
|
|
539
|
-
description: "Review draft PRs before they are marked ready"
|
|
540
|
-
})),
|
|
541
|
-
on_push: Schema.optional(Schema.Boolean.annotations({
|
|
542
|
-
title: "Review on push",
|
|
543
|
-
description: "Review each new push to the PR"
|
|
544
|
-
}))
|
|
545
|
-
}).annotations({
|
|
546
|
-
identifier: "CopilotReviewShorthand",
|
|
547
|
-
title: "Copilot review",
|
|
548
|
-
description: "Copilot code review configuration"
|
|
549
|
-
});
|
|
550
|
-
const CodeScanningEntrySchema = Schema.Struct({
|
|
551
|
-
tool: Schema.String.annotations({
|
|
552
|
-
title: "Tool name",
|
|
553
|
-
description: "Name of the code scanning tool"
|
|
554
|
-
}),
|
|
555
|
-
alerts: Schema.Literal("none", "errors", "errors_and_warnings", "all").annotations({
|
|
556
|
-
title: "Alerts threshold",
|
|
557
|
-
description: "Severity level at which alerts block updates"
|
|
558
|
-
}),
|
|
559
|
-
security_alerts: Schema.Literal("none", "critical", "high_or_higher", "medium_or_higher", "all").annotations({
|
|
560
|
-
title: "Security alerts threshold",
|
|
561
|
-
description: "Severity level at which security alerts block updates"
|
|
562
|
-
})
|
|
563
|
-
}).annotations({
|
|
564
|
-
identifier: "CodeScanningEntry",
|
|
565
|
-
title: "Code scanning tool",
|
|
566
|
-
description: "A code scanning tool with alert thresholds"
|
|
567
|
-
});
|
|
568
|
-
const WorkflowsShorthandSchema = Schema.Struct({
|
|
569
|
-
on_creation: Schema.optional(Schema.Boolean.annotations({
|
|
570
|
-
title: "Enforce on creation",
|
|
571
|
-
description: "Enforce workflows when a branch is created (false = skip on creation)"
|
|
572
|
-
})),
|
|
573
|
-
required: Schema.Array(WorkflowFileSchema).annotations({
|
|
574
|
-
title: "Required workflows",
|
|
575
|
-
description: "Workflows that must pass for this rule"
|
|
576
|
-
})
|
|
577
|
-
}).annotations({
|
|
578
|
-
identifier: "WorkflowsShorthand",
|
|
579
|
-
title: "Workflows",
|
|
580
|
-
description: "Required workflow configuration"
|
|
581
|
-
});
|
|
582
|
-
const sharedRulesetFields = {
|
|
583
|
-
name: Schema.String.annotations({
|
|
584
|
-
title: "Ruleset name",
|
|
585
|
-
description: "The name of the ruleset (used for matching when creating or updating)"
|
|
586
|
-
}),
|
|
587
|
-
enforcement: EnforcementSchema,
|
|
588
|
-
conditions: Schema.optional(RulesetConditionsSchema),
|
|
589
|
-
bypass_actors: Schema.optional(Schema.Array(BypassActorSchema)),
|
|
590
|
-
creation: Schema.optional(Schema.Boolean.annotations({
|
|
591
|
-
title: "Restrict creation",
|
|
592
|
-
description: "When true, adds a creation rule"
|
|
593
|
-
})),
|
|
594
|
-
update: Schema.optional(Schema.Boolean.annotations({
|
|
595
|
-
title: "Restrict updates",
|
|
596
|
-
description: "When true, adds an update rule with update_allows_fetch_and_merge: true"
|
|
597
|
-
})),
|
|
598
|
-
deletion: Schema.optional(Schema.Boolean.annotations({
|
|
599
|
-
title: "Restrict deletion",
|
|
600
|
-
description: "When true, adds a deletion rule"
|
|
601
|
-
})),
|
|
602
|
-
required_linear_history: Schema.optional(Schema.Boolean.annotations({
|
|
603
|
-
title: "Require linear history",
|
|
604
|
-
description: "When true, adds a required_linear_history rule"
|
|
605
|
-
})),
|
|
606
|
-
required_signatures: Schema.optional(Schema.Boolean.annotations({
|
|
607
|
-
title: "Require signatures",
|
|
608
|
-
description: "When true, adds a required_signatures rule"
|
|
609
|
-
})),
|
|
610
|
-
non_fast_forward: Schema.optional(Schema.Boolean.annotations({
|
|
611
|
-
title: "Prevent non-fast-forward",
|
|
612
|
-
description: "When true, adds a non_fast_forward rule"
|
|
613
|
-
})),
|
|
614
|
-
deployments: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
615
|
-
title: "Required deployments",
|
|
616
|
-
description: "Deployment environments that must succeed; converts to required_deployments rule"
|
|
617
|
-
})),
|
|
618
|
-
targets: Schema.optional(TargetsSchema),
|
|
619
|
-
status_checks: Schema.optional(StatusChecksShorthandSchema),
|
|
620
|
-
commit_message: Schema.optional(Schema.Array(PatternEntrySchema).annotations({
|
|
621
|
-
title: "Commit message patterns",
|
|
622
|
-
description: "Commit message pattern rules"
|
|
623
|
-
})),
|
|
624
|
-
commit_author_email: Schema.optional(Schema.Array(PatternEntrySchema).annotations({
|
|
625
|
-
title: "Commit author email patterns",
|
|
626
|
-
description: "Commit author email pattern rules"
|
|
627
|
-
})),
|
|
628
|
-
committer_email: Schema.optional(Schema.Array(PatternEntrySchema).annotations({
|
|
629
|
-
title: "Committer email patterns",
|
|
630
|
-
description: "Committer email pattern rules"
|
|
631
|
-
}))
|
|
632
|
-
};
|
|
633
|
-
const BranchRulesetSchema = Schema.Struct({
|
|
634
|
-
...sharedRulesetFields,
|
|
635
|
-
type: Schema.Literal("branch").annotations({
|
|
636
|
-
title: "Ruleset type",
|
|
637
|
-
description: "This ruleset applies to branches"
|
|
638
|
-
}),
|
|
639
|
-
pull_requests: Schema.optional(PullRequestsShorthandSchema),
|
|
640
|
-
merge_queue: Schema.optional(MergeQueueShorthandSchema),
|
|
641
|
-
copilot_review: Schema.optional(CopilotReviewShorthandSchema),
|
|
642
|
-
code_scanning: Schema.optional(Schema.Array(CodeScanningEntrySchema).annotations({
|
|
643
|
-
title: "Code scanning tools",
|
|
644
|
-
description: "Code scanning tool requirements"
|
|
645
|
-
})),
|
|
646
|
-
workflows: Schema.optional(WorkflowsShorthandSchema),
|
|
647
|
-
branch_name: Schema.optional(Schema.Array(PatternEntrySchema).annotations({
|
|
648
|
-
title: "Branch name patterns",
|
|
649
|
-
description: "Branch name pattern rules"
|
|
650
|
-
}))
|
|
651
|
-
}).annotations({
|
|
652
|
-
identifier: "BranchRuleset",
|
|
653
|
-
title: "Branch ruleset",
|
|
654
|
-
description: "A ruleset that applies to branches",
|
|
655
|
-
jsonSchema: {
|
|
656
|
-
...tombi({
|
|
657
|
-
tableKeysOrder: "schema"
|
|
658
|
-
}),
|
|
659
|
-
...taplo({
|
|
660
|
-
initKeys: [
|
|
661
|
-
"name",
|
|
662
|
-
"type",
|
|
663
|
-
"enforcement",
|
|
664
|
-
"targets"
|
|
665
|
-
],
|
|
666
|
-
links: {
|
|
667
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/rulesets.md"
|
|
668
|
-
}
|
|
669
|
-
})
|
|
670
|
-
}
|
|
671
|
-
});
|
|
672
|
-
const TagRulesetSchema = Schema.Struct({
|
|
673
|
-
...sharedRulesetFields,
|
|
674
|
-
type: Schema.Literal("tag").annotations({
|
|
675
|
-
title: "Ruleset type",
|
|
676
|
-
description: "This ruleset applies to tags"
|
|
677
|
-
}),
|
|
678
|
-
tag_name: Schema.optional(Schema.Array(PatternEntrySchema).annotations({
|
|
679
|
-
title: "Tag name patterns",
|
|
680
|
-
description: "Tag name pattern rules"
|
|
681
|
-
}))
|
|
682
|
-
}).annotations({
|
|
683
|
-
identifier: "TagRuleset",
|
|
684
|
-
title: "Tag ruleset",
|
|
685
|
-
description: "A ruleset that applies to tags",
|
|
686
|
-
jsonSchema: {
|
|
687
|
-
...tombi({
|
|
688
|
-
tableKeysOrder: "schema"
|
|
689
|
-
}),
|
|
690
|
-
...taplo({
|
|
691
|
-
initKeys: [
|
|
692
|
-
"name",
|
|
693
|
-
"type",
|
|
694
|
-
"enforcement",
|
|
695
|
-
"targets"
|
|
696
|
-
],
|
|
697
|
-
links: {
|
|
698
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/rulesets.md"
|
|
699
|
-
}
|
|
700
|
-
})
|
|
701
|
-
}
|
|
702
|
-
});
|
|
703
|
-
const RulesetSchema = Schema.Union(BranchRulesetSchema, TagRulesetSchema).annotations({
|
|
704
|
-
identifier: "Ruleset",
|
|
705
|
-
title: "Repository ruleset",
|
|
706
|
-
description: "A set of rules to apply when specified conditions are met",
|
|
707
|
-
jsonSchema: taplo({
|
|
708
|
-
links: {
|
|
709
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/rulesets.md"
|
|
710
|
-
}
|
|
711
|
-
})
|
|
712
|
-
});
|
|
713
|
-
function buildRulesetPayload(ruleset) {
|
|
714
|
-
const rules = [];
|
|
715
|
-
if (true === ruleset.creation) rules.push({
|
|
716
|
-
type: "creation"
|
|
717
|
-
});
|
|
718
|
-
if (true === ruleset.update) rules.push({
|
|
719
|
-
type: "update",
|
|
720
|
-
parameters: {
|
|
721
|
-
update_allows_fetch_and_merge: true
|
|
722
|
-
}
|
|
723
|
-
});
|
|
724
|
-
if (true === ruleset.deletion) rules.push({
|
|
725
|
-
type: "deletion"
|
|
726
|
-
});
|
|
727
|
-
if (true === ruleset.required_linear_history) rules.push({
|
|
728
|
-
type: "required_linear_history"
|
|
729
|
-
});
|
|
730
|
-
if (true === ruleset.required_signatures) rules.push({
|
|
731
|
-
type: "required_signatures"
|
|
732
|
-
});
|
|
733
|
-
if (true === ruleset.non_fast_forward) rules.push({
|
|
734
|
-
type: "non_fast_forward"
|
|
735
|
-
});
|
|
736
|
-
if (void 0 !== ruleset.deployments && ruleset.deployments.length > 0) rules.push({
|
|
737
|
-
type: "required_deployments",
|
|
738
|
-
parameters: {
|
|
739
|
-
required_deployment_environments: ruleset.deployments
|
|
740
|
-
}
|
|
741
|
-
});
|
|
742
|
-
if ("branch" === ruleset.type && void 0 !== ruleset.pull_requests) {
|
|
743
|
-
const pr = ruleset.pull_requests;
|
|
744
|
-
rules.push({
|
|
745
|
-
type: "pull_request",
|
|
746
|
-
parameters: {
|
|
747
|
-
required_approving_review_count: pr.approvals,
|
|
748
|
-
dismiss_stale_reviews_on_push: pr.dismiss_stale_reviews,
|
|
749
|
-
require_code_owner_review: pr.code_owner_review,
|
|
750
|
-
require_last_push_approval: pr.last_push_approval,
|
|
751
|
-
required_review_thread_resolution: pr.resolve_threads,
|
|
752
|
-
...void 0 !== pr.merge_methods ? {
|
|
753
|
-
allowed_merge_methods: pr.merge_methods
|
|
754
|
-
} : {},
|
|
755
|
-
...void 0 !== pr.reviewers ? {
|
|
756
|
-
required_reviewers: pr.reviewers
|
|
757
|
-
} : {}
|
|
758
|
-
}
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
if (void 0 !== ruleset.status_checks) {
|
|
762
|
-
const sc = ruleset.status_checks;
|
|
763
|
-
const checks = sc.required.map((check)=>{
|
|
764
|
-
if (void 0 === check.integration_id && void 0 !== sc.default_integration_id) return {
|
|
765
|
-
...check,
|
|
766
|
-
integration_id: sc.default_integration_id
|
|
767
|
-
};
|
|
768
|
-
return check;
|
|
769
|
-
});
|
|
770
|
-
rules.push({
|
|
771
|
-
type: "required_status_checks",
|
|
772
|
-
parameters: {
|
|
773
|
-
strict_required_status_checks_policy: sc.update_branch ?? true,
|
|
774
|
-
...false === sc.on_creation ? {
|
|
775
|
-
do_not_enforce_on_create: true
|
|
776
|
-
} : {},
|
|
777
|
-
required_status_checks: checks
|
|
778
|
-
}
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
if ("branch" === ruleset.type && void 0 !== ruleset.merge_queue) {
|
|
782
|
-
const mq = ruleset.merge_queue;
|
|
783
|
-
rules.push({
|
|
784
|
-
type: "merge_queue",
|
|
785
|
-
parameters: {
|
|
786
|
-
check_response_timeout_minutes: mq.check_timeout,
|
|
787
|
-
grouping_strategy: mq.grouping,
|
|
788
|
-
max_entries_to_build: mq.max_build,
|
|
789
|
-
max_entries_to_merge: mq.max_merge,
|
|
790
|
-
merge_method: mq.merge_method,
|
|
791
|
-
min_entries_to_merge: mq.min_merge,
|
|
792
|
-
min_entries_to_merge_wait_minutes: mq.min_wait
|
|
793
|
-
}
|
|
794
|
-
});
|
|
795
|
-
}
|
|
796
|
-
if ("branch" === ruleset.type && void 0 !== ruleset.copilot_review) {
|
|
797
|
-
const cr = ruleset.copilot_review;
|
|
798
|
-
rules.push({
|
|
799
|
-
type: "copilot_code_review",
|
|
800
|
-
parameters: {
|
|
801
|
-
...void 0 !== cr.draft_prs ? {
|
|
802
|
-
review_draft_pull_requests: cr.draft_prs
|
|
803
|
-
} : {},
|
|
804
|
-
...void 0 !== cr.on_push ? {
|
|
805
|
-
review_on_push: cr.on_push
|
|
806
|
-
} : {}
|
|
807
|
-
}
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
if ("branch" === ruleset.type && void 0 !== ruleset.code_scanning) rules.push({
|
|
811
|
-
type: "code_scanning",
|
|
812
|
-
parameters: {
|
|
813
|
-
code_scanning_tools: ruleset.code_scanning.map((entry)=>({
|
|
814
|
-
tool: entry.tool,
|
|
815
|
-
alerts_threshold: entry.alerts,
|
|
816
|
-
security_alerts_threshold: entry.security_alerts
|
|
817
|
-
}))
|
|
818
|
-
}
|
|
819
|
-
});
|
|
820
|
-
if ("branch" === ruleset.type && void 0 !== ruleset.workflows) {
|
|
821
|
-
const wf = ruleset.workflows;
|
|
822
|
-
rules.push({
|
|
823
|
-
type: "workflows",
|
|
824
|
-
parameters: {
|
|
825
|
-
...false === wf.on_creation ? {
|
|
826
|
-
do_not_enforce_on_create: true
|
|
827
|
-
} : {},
|
|
828
|
-
workflows: wf.required
|
|
829
|
-
}
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
for (const [field, ruleType] of [
|
|
833
|
-
[
|
|
834
|
-
"commit_message",
|
|
835
|
-
"commit_message_pattern"
|
|
836
|
-
],
|
|
837
|
-
[
|
|
838
|
-
"commit_author_email",
|
|
839
|
-
"commit_author_email_pattern"
|
|
840
|
-
],
|
|
841
|
-
[
|
|
842
|
-
"committer_email",
|
|
843
|
-
"committer_email_pattern"
|
|
844
|
-
]
|
|
845
|
-
]){
|
|
846
|
-
const patterns = ruleset[field];
|
|
847
|
-
if (void 0 !== patterns) for (const entry of patterns)rules.push({
|
|
848
|
-
type: ruleType,
|
|
849
|
-
parameters: entry
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
if ("branch" === ruleset.type && void 0 !== ruleset.branch_name) for (const entry of ruleset.branch_name)rules.push({
|
|
853
|
-
type: "branch_name_pattern",
|
|
854
|
-
parameters: entry
|
|
855
|
-
});
|
|
856
|
-
if ("tag" === ruleset.type && void 0 !== ruleset.tag_name) for (const entry of ruleset.tag_name)rules.push({
|
|
857
|
-
type: "tag_name_pattern",
|
|
858
|
-
parameters: entry
|
|
859
|
-
});
|
|
860
|
-
let conditions = ruleset.conditions;
|
|
861
|
-
if (void 0 !== ruleset.targets) if ("default" === ruleset.targets) conditions = {
|
|
862
|
-
ref_name: {
|
|
863
|
-
include: [
|
|
864
|
-
"~DEFAULT_BRANCH"
|
|
865
|
-
],
|
|
866
|
-
exclude: []
|
|
867
|
-
}
|
|
868
|
-
};
|
|
869
|
-
else if ("all" === ruleset.targets) conditions = {
|
|
870
|
-
ref_name: {
|
|
871
|
-
include: [
|
|
872
|
-
"~ALL"
|
|
873
|
-
],
|
|
874
|
-
exclude: []
|
|
875
|
-
}
|
|
876
|
-
};
|
|
877
|
-
else {
|
|
878
|
-
const include = [];
|
|
879
|
-
const exclude = [];
|
|
880
|
-
for (const pattern of ruleset.targets)if ("include" in pattern) include.push(pattern.include);
|
|
881
|
-
else exclude.push(pattern.exclude);
|
|
882
|
-
conditions = {
|
|
883
|
-
ref_name: {
|
|
884
|
-
include,
|
|
885
|
-
exclude
|
|
886
|
-
}
|
|
887
|
-
};
|
|
888
|
-
}
|
|
889
|
-
return {
|
|
890
|
-
name: ruleset.name,
|
|
891
|
-
target: ruleset.type,
|
|
892
|
-
enforcement: ruleset.enforcement,
|
|
893
|
-
...void 0 !== conditions ? {
|
|
894
|
-
conditions
|
|
895
|
-
} : {},
|
|
896
|
-
...void 0 !== ruleset.bypass_actors ? {
|
|
897
|
-
bypass_actors: ruleset.bypass_actors
|
|
898
|
-
} : {},
|
|
899
|
-
...rules.length > 0 ? {
|
|
900
|
-
rules
|
|
901
|
-
} : {}
|
|
902
|
-
};
|
|
903
|
-
}
|
|
904
|
-
const SecretScopesSchema = Schema.Struct({
|
|
905
|
-
actions: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
906
|
-
title: "Action secret groups",
|
|
907
|
-
description: "Secret groups to sync as GitHub Actions repository secrets",
|
|
908
|
-
examples: [
|
|
909
|
-
[
|
|
910
|
-
"deploy",
|
|
911
|
-
"app"
|
|
912
|
-
]
|
|
913
|
-
]
|
|
914
|
-
})),
|
|
915
|
-
dependabot: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
916
|
-
title: "Dependabot secret groups",
|
|
917
|
-
description: "Secret groups to sync as Dependabot secrets",
|
|
918
|
-
examples: [
|
|
919
|
-
[
|
|
920
|
-
"deploy"
|
|
921
|
-
]
|
|
922
|
-
]
|
|
923
|
-
})),
|
|
924
|
-
codespaces: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
925
|
-
title: "Codespaces secret groups",
|
|
926
|
-
description: "Secret groups to sync as Codespaces secrets",
|
|
927
|
-
examples: [
|
|
928
|
-
[
|
|
929
|
-
"deploy"
|
|
930
|
-
]
|
|
931
|
-
]
|
|
932
|
-
})),
|
|
933
|
-
environments: Schema.optional(Schema.Record({
|
|
934
|
-
key: Schema.String,
|
|
935
|
-
value: Schema.Array(Schema.String).annotations({
|
|
936
|
-
title: "Environment secret groups",
|
|
937
|
-
description: "Secret groups to sync as environment secrets"
|
|
938
|
-
})
|
|
939
|
-
}).annotations({
|
|
940
|
-
title: "Environment secret scopes",
|
|
941
|
-
description: "Map of environment names to secret group references",
|
|
942
|
-
jsonSchema: tombi({
|
|
943
|
-
additionalKeyLabel: "environment_name"
|
|
944
|
-
})
|
|
945
|
-
}))
|
|
946
|
-
}).annotations({
|
|
947
|
-
identifier: "SecretScopes",
|
|
948
|
-
title: "Secret scopes",
|
|
949
|
-
description: "Assign secret groups to GitHub secret scopes (actions, dependabot, codespaces, environments)"
|
|
950
|
-
});
|
|
951
|
-
const VariableScopesSchema = Schema.Struct({
|
|
952
|
-
actions: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
953
|
-
title: "Action variable groups",
|
|
954
|
-
description: "Variable groups to sync as GitHub Actions repository variables",
|
|
955
|
-
examples: [
|
|
956
|
-
[
|
|
957
|
-
"common"
|
|
958
|
-
]
|
|
959
|
-
]
|
|
960
|
-
})),
|
|
961
|
-
environments: Schema.optional(Schema.Record({
|
|
962
|
-
key: Schema.String,
|
|
963
|
-
value: Schema.Array(Schema.String).annotations({
|
|
964
|
-
title: "Environment variable groups",
|
|
965
|
-
description: "Variable groups to sync as environment variables"
|
|
966
|
-
})
|
|
967
|
-
}).annotations({
|
|
968
|
-
title: "Environment variable scopes",
|
|
969
|
-
description: "Map of environment names to variable group references",
|
|
970
|
-
jsonSchema: tombi({
|
|
971
|
-
additionalKeyLabel: "environment_name"
|
|
972
|
-
})
|
|
973
|
-
}))
|
|
974
|
-
}).annotations({
|
|
975
|
-
identifier: "VariableScopes",
|
|
976
|
-
title: "Variable scopes",
|
|
977
|
-
description: "Assign variable groups to GitHub variable scopes (actions, environments)"
|
|
978
|
-
});
|
|
979
|
-
const GroupSchema = Schema.Struct({
|
|
980
|
-
owner: Schema.optional(Schema.String.annotations({
|
|
981
|
-
title: "Owner override",
|
|
982
|
-
description: "GitHub user or organization that owns these repos. Overrides the top-level owner.",
|
|
983
|
-
examples: [
|
|
984
|
-
"savvy-web"
|
|
985
|
-
]
|
|
986
|
-
})),
|
|
987
|
-
repos: Schema.Array(Schema.String).annotations({
|
|
988
|
-
title: "Repository names",
|
|
989
|
-
description: "List of repository names (without owner prefix) to sync in this group",
|
|
990
|
-
examples: [
|
|
991
|
-
[
|
|
992
|
-
"repo-one",
|
|
993
|
-
"repo-two",
|
|
994
|
-
"repo-three"
|
|
995
|
-
]
|
|
996
|
-
],
|
|
997
|
-
jsonSchema: tombi({
|
|
998
|
-
arrayValuesOrder: "ascending"
|
|
999
|
-
})
|
|
1000
|
-
}),
|
|
1001
|
-
credentials: Schema.optional(Schema.String.annotations({
|
|
1002
|
-
title: "Credential profile",
|
|
1003
|
-
description: "Name of the credential profile to use. If only one profile exists, it is used automatically.",
|
|
1004
|
-
examples: [
|
|
1005
|
-
"personal",
|
|
1006
|
-
"work"
|
|
1007
|
-
]
|
|
1008
|
-
})),
|
|
1009
|
-
settings: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
1010
|
-
title: "Settings groups",
|
|
1011
|
-
description: "Names of settings groups to apply to these repos",
|
|
1012
|
-
examples: [
|
|
1013
|
-
[
|
|
1014
|
-
"oss-defaults"
|
|
1015
|
-
]
|
|
1016
|
-
]
|
|
1017
|
-
})),
|
|
1018
|
-
environments: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
1019
|
-
title: "Environments",
|
|
1020
|
-
description: "Names of environment definitions to create/update for these repos",
|
|
1021
|
-
examples: [
|
|
1022
|
-
[
|
|
1023
|
-
"staging",
|
|
1024
|
-
"production"
|
|
1025
|
-
]
|
|
1026
|
-
]
|
|
1027
|
-
})),
|
|
1028
|
-
secrets: Schema.optional(SecretScopesSchema),
|
|
1029
|
-
variables: Schema.optional(VariableScopesSchema),
|
|
1030
|
-
rulesets: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
1031
|
-
title: "Rulesets",
|
|
1032
|
-
description: "Names of rulesets to apply to these repos",
|
|
1033
|
-
examples: [
|
|
1034
|
-
[
|
|
1035
|
-
"workflow",
|
|
1036
|
-
"release"
|
|
1037
|
-
]
|
|
1038
|
-
]
|
|
1039
|
-
})),
|
|
1040
|
-
security: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
1041
|
-
title: "Security groups",
|
|
1042
|
-
description: "Names of security groups (vulnerability alerts, automated security fixes, private vulnerability reporting) to apply to these repos",
|
|
1043
|
-
examples: [
|
|
1044
|
-
[
|
|
1045
|
-
"oss-defaults"
|
|
1046
|
-
]
|
|
1047
|
-
]
|
|
1048
|
-
})),
|
|
1049
|
-
code_scanning: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
1050
|
-
title: "Code scanning groups",
|
|
1051
|
-
description: "Names of code_scanning groups (CodeQL default setup) to apply to these repos",
|
|
1052
|
-
examples: [
|
|
1053
|
-
[
|
|
1054
|
-
"oss-defaults"
|
|
1055
|
-
]
|
|
1056
|
-
]
|
|
1057
|
-
})),
|
|
1058
|
-
cleanup: Schema.optional(CleanupSchema)
|
|
1059
|
-
}).annotations({
|
|
1060
|
-
identifier: "Group",
|
|
1061
|
-
title: "Repository group",
|
|
1062
|
-
description: "A named group of repositories with their resource assignments",
|
|
1063
|
-
jsonSchema: {
|
|
1064
|
-
...tombi({
|
|
1065
|
-
tableKeysOrder: "schema"
|
|
1066
|
-
}),
|
|
1067
|
-
...taplo({
|
|
1068
|
-
initKeys: [
|
|
1069
|
-
"repos"
|
|
1070
|
-
],
|
|
1071
|
-
links: {
|
|
1072
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/configuration.md"
|
|
1073
|
-
}
|
|
1074
|
-
})
|
|
1075
|
-
}
|
|
1076
|
-
});
|
|
1077
|
-
const SquashMergeCommitTitleSchema = Schema.Literal("PR_TITLE", "COMMIT_OR_PR_TITLE").annotations({
|
|
1078
|
-
title: "Squash merge commit title",
|
|
1079
|
-
description: "Default title for squash merge commits: PR_TITLE uses the pull request title, COMMIT_OR_PR_TITLE uses the commit message if only one commit, otherwise the PR title"
|
|
1080
|
-
});
|
|
1081
|
-
const SquashMergeCommitMessageSchema = Schema.Literal("PR_BODY", "COMMIT_MESSAGES", "BLANK").annotations({
|
|
1082
|
-
title: "Squash merge commit message",
|
|
1083
|
-
description: "Default message body for squash merge commits: PR_BODY uses the pull request body, COMMIT_MESSAGES concatenates all commit messages, BLANK leaves it empty"
|
|
1084
|
-
});
|
|
1085
|
-
const MergeCommitTitleSchema = Schema.Literal("PR_TITLE", "MERGE_MESSAGE").annotations({
|
|
1086
|
-
title: "Merge commit title",
|
|
1087
|
-
description: "Default title for merge commits: PR_TITLE uses the pull request title, MERGE_MESSAGE uses the classic merge message"
|
|
1088
|
-
});
|
|
1089
|
-
const MergeCommitMessageSchema = Schema.Literal("PR_BODY", "PR_TITLE", "BLANK").annotations({
|
|
1090
|
-
title: "Merge commit message",
|
|
1091
|
-
description: "Default message body for merge commits: PR_BODY uses the pull request body, PR_TITLE uses the PR title, BLANK leaves it empty"
|
|
1092
|
-
});
|
|
1093
|
-
const SecurityAndAnalysisStatusSchema = Schema.Literal("enabled", "disabled").annotations({
|
|
1094
|
-
identifier: "SecurityAndAnalysisStatus",
|
|
1095
|
-
title: "Security feature status",
|
|
1096
|
-
description: 'Whether the security feature is "enabled" or "disabled"'
|
|
1097
|
-
});
|
|
1098
|
-
const DelegatedBypassReviewerModeSchema = Schema.Literal("ALWAYS", "EXEMPT").annotations({
|
|
1099
|
-
identifier: "DelegatedBypassReviewerMode",
|
|
1100
|
-
title: "Delegated bypass reviewer mode",
|
|
1101
|
-
description: "ALWAYS: reviewer is always required to approve bypass; EXEMPT: reviewer can bypass without review"
|
|
1102
|
-
});
|
|
1103
|
-
const DelegatedBypassReviewerSchema = Schema.Union(Schema.Struct({
|
|
1104
|
-
team: Schema.String.annotations({
|
|
1105
|
-
title: "Team slug",
|
|
1106
|
-
description: 'GitHub team slug (e.g., "security-team"); resolved to numeric reviewer_id at sync time',
|
|
1107
|
-
examples: [
|
|
1108
|
-
"security-team"
|
|
1109
|
-
]
|
|
1110
|
-
}),
|
|
1111
|
-
mode: Schema.optional(DelegatedBypassReviewerModeSchema)
|
|
1112
|
-
}), Schema.Struct({
|
|
1113
|
-
role: Schema.String.annotations({
|
|
1114
|
-
title: "Organization role name",
|
|
1115
|
-
description: 'Organization role name as defined in `GET /orgs/{org}/organization-roles` (e.g., "all_repo_admin", "security_manager"). Resolved to the numeric role ID at sync time.',
|
|
1116
|
-
examples: [
|
|
1117
|
-
"all_repo_admin",
|
|
1118
|
-
"all_repo_maintain",
|
|
1119
|
-
"security_manager"
|
|
1120
|
-
]
|
|
1121
|
-
}),
|
|
1122
|
-
mode: Schema.optional(DelegatedBypassReviewerModeSchema)
|
|
1123
|
-
})).annotations({
|
|
1124
|
-
identifier: "DelegatedBypassReviewer",
|
|
1125
|
-
title: "Delegated bypass reviewer",
|
|
1126
|
-
description: "A reviewer who can approve secret-scanning push-protection bypass requests. Must specify exactly one of team or role."
|
|
1127
|
-
});
|
|
1128
|
-
const SecurityAndAnalysisSchema = Schema.Struct({
|
|
1129
|
-
advanced_security: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
1130
|
-
title: "GitHub Advanced Security",
|
|
1131
|
-
description: "(GHAS-licensed) Master toggle for GitHub Advanced Security features. Free on public repos; requires a GHAS license on private repos."
|
|
1132
|
-
})),
|
|
1133
|
-
code_security: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
1134
|
-
title: "GitHub Code Security",
|
|
1135
|
-
description: "(GHAS-licensed) Toggle GitHub Code Security functionality."
|
|
1136
|
-
})),
|
|
1137
|
-
secret_scanning: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
1138
|
-
title: "Secret scanning",
|
|
1139
|
-
description: "Detect exposed credentials and sensitive data committed to the repository."
|
|
1140
|
-
})),
|
|
1141
|
-
secret_scanning_push_protection: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
1142
|
-
title: "Secret scanning push protection",
|
|
1143
|
-
description: "Block git pushes that contain detected secrets."
|
|
1144
|
-
})),
|
|
1145
|
-
secret_scanning_ai_detection: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
1146
|
-
title: "Secret scanning AI detection",
|
|
1147
|
-
description: "(GHAS-licensed) AI-powered detection of generic secrets beyond standard provider patterns."
|
|
1148
|
-
})),
|
|
1149
|
-
secret_scanning_non_provider_patterns: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
1150
|
-
title: "Secret scanning non-provider patterns",
|
|
1151
|
-
description: "(GHAS-licensed) Detect custom secret patterns beyond the standard provider list."
|
|
1152
|
-
})),
|
|
1153
|
-
secret_scanning_delegated_alert_dismissal: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
1154
|
-
title: "Delegated alert dismissal",
|
|
1155
|
-
description: "(org-only) Allow delegated dismissal of secret scanning alerts."
|
|
1156
|
-
})),
|
|
1157
|
-
secret_scanning_delegated_bypass: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
1158
|
-
title: "Delegated push protection bypass",
|
|
1159
|
-
description: "(org-only) Allow delegated approval of secret scanning push protection bypass requests."
|
|
1160
|
-
})),
|
|
1161
|
-
delegated_bypass_reviewers: Schema.optional(Schema.Array(DelegatedBypassReviewerSchema).annotations({
|
|
1162
|
-
title: "Delegated bypass reviewers",
|
|
1163
|
-
description: "(org-only) Reviewers authorized to approve push protection bypass requests. Each entry must specify a team slug or role name."
|
|
1164
|
-
})),
|
|
1165
|
-
dependabot_security_updates: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
1166
|
-
title: "Dependabot security updates",
|
|
1167
|
-
description: "Automatically open pull requests to patch known dependency vulnerabilities."
|
|
1168
|
-
}))
|
|
1169
|
-
}).annotations({
|
|
1170
|
-
identifier: "SecurityAndAnalysis",
|
|
1171
|
-
title: "Security and analysis",
|
|
1172
|
-
description: "GitHub repository security_and_analysis fields applied via the same PATCH /repos call as other settings. (GHAS-licensed) fields require a GHAS license on private repos; (org-only) fields are silently skipped on personal repos.",
|
|
1173
|
-
jsonSchema: {
|
|
1174
|
-
...tombi({
|
|
1175
|
-
tableKeysOrder: "schema"
|
|
1176
|
-
}),
|
|
1177
|
-
...taplo({
|
|
1178
|
-
links: {
|
|
1179
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/configuration.md"
|
|
1180
|
-
}
|
|
1181
|
-
})
|
|
1182
|
-
}
|
|
1183
|
-
});
|
|
1184
|
-
const SettingsGroupSchema = Schema.Struct({
|
|
1185
|
-
is_template: Schema.optional(Schema.Boolean.annotations({
|
|
1186
|
-
title: "Template repository",
|
|
1187
|
-
description: "Whether the repository is a template that can be used to generate new repositories"
|
|
1188
|
-
})),
|
|
1189
|
-
has_wiki: Schema.optional(Schema.Boolean.annotations({
|
|
1190
|
-
title: "Wikis",
|
|
1191
|
-
description: "Enable the wiki feature for the repository"
|
|
1192
|
-
})),
|
|
1193
|
-
has_issues: Schema.optional(Schema.Boolean.annotations({
|
|
1194
|
-
title: "Issues",
|
|
1195
|
-
description: "Enable the issues feature for the repository"
|
|
1196
|
-
})),
|
|
1197
|
-
has_projects: Schema.optional(Schema.Boolean.annotations({
|
|
1198
|
-
title: "Projects",
|
|
1199
|
-
description: "Enable the projects feature for the repository"
|
|
1200
|
-
})),
|
|
1201
|
-
has_discussions: Schema.optional(Schema.Boolean.annotations({
|
|
1202
|
-
title: "Discussions",
|
|
1203
|
-
description: "Enable the discussions feature for the repository"
|
|
1204
|
-
})),
|
|
1205
|
-
has_sponsorships: Schema.optional(Schema.Boolean.annotations({
|
|
1206
|
-
title: "Sponsorships",
|
|
1207
|
-
description: "Display a Sponsor button for the repository (synced via GraphQL)"
|
|
1208
|
-
})),
|
|
1209
|
-
has_pull_requests: Schema.optional(Schema.Boolean.annotations({
|
|
1210
|
-
title: "Pull requests",
|
|
1211
|
-
description: "Enable the pull requests feature for the repository (synced via GraphQL)"
|
|
1212
|
-
})),
|
|
1213
|
-
allow_forking: Schema.optional(Schema.Boolean.annotations({
|
|
1214
|
-
title: "Allow forking",
|
|
1215
|
-
description: "Allow forking of the repository"
|
|
1216
|
-
})),
|
|
1217
|
-
allow_merge_commit: Schema.optional(Schema.Boolean.annotations({
|
|
1218
|
-
title: "Allow merge commits",
|
|
1219
|
-
description: "Allow merge commits when merging pull requests"
|
|
1220
|
-
})),
|
|
1221
|
-
allow_squash_merge: Schema.optional(Schema.Boolean.annotations({
|
|
1222
|
-
title: "Allow squash merging",
|
|
1223
|
-
description: "Allow squash merging when merging pull requests"
|
|
1224
|
-
})),
|
|
1225
|
-
allow_rebase_merge: Schema.optional(Schema.Boolean.annotations({
|
|
1226
|
-
title: "Allow rebase merging",
|
|
1227
|
-
description: "Allow rebase merging when merging pull requests"
|
|
1228
|
-
})),
|
|
1229
|
-
allow_auto_merge: Schema.optional(Schema.Boolean.annotations({
|
|
1230
|
-
title: "Allow auto-merge",
|
|
1231
|
-
description: "Allow pull requests to be automatically merged once all requirements are met"
|
|
1232
|
-
})),
|
|
1233
|
-
allow_update_branch: Schema.optional(Schema.Boolean.annotations({
|
|
1234
|
-
title: "Always suggest updating pull request branches",
|
|
1235
|
-
description: "Show the update branch button on pull requests"
|
|
1236
|
-
})),
|
|
1237
|
-
squash_merge_commit_title: Schema.optional(SquashMergeCommitTitleSchema),
|
|
1238
|
-
squash_merge_commit_message: Schema.optional(SquashMergeCommitMessageSchema),
|
|
1239
|
-
merge_commit_title: Schema.optional(MergeCommitTitleSchema),
|
|
1240
|
-
merge_commit_message: Schema.optional(MergeCommitMessageSchema),
|
|
1241
|
-
delete_branch_on_merge: Schema.optional(Schema.Boolean.annotations({
|
|
1242
|
-
title: "Automatically delete head branches",
|
|
1243
|
-
description: "Automatically delete head branches after pull requests are merged"
|
|
1244
|
-
})),
|
|
1245
|
-
web_commit_signoff_required: Schema.optional(Schema.Boolean.annotations({
|
|
1246
|
-
title: "Require commit signoff",
|
|
1247
|
-
description: "Require contributors to sign off on web-based commits"
|
|
1248
|
-
})),
|
|
1249
|
-
security_and_analysis: Schema.optional(SecurityAndAnalysisSchema)
|
|
1250
|
-
}, {
|
|
1251
|
-
key: Schema.String,
|
|
1252
|
-
value: Jsonifiable
|
|
1253
|
-
}).annotations({
|
|
1254
|
-
identifier: "SettingsGroup",
|
|
1255
|
-
title: "Settings group",
|
|
1256
|
-
description: "GitHub repository settings to apply. Known fields are typed; additional fields are passed through to the API.",
|
|
1257
|
-
jsonSchema: {
|
|
1258
|
-
...tombi({
|
|
1259
|
-
tableKeysOrder: "schema"
|
|
1260
|
-
}),
|
|
1261
|
-
...taplo({
|
|
1262
|
-
links: {
|
|
1263
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/configuration.md"
|
|
1264
|
-
}
|
|
1265
|
-
})
|
|
1266
|
-
}
|
|
1267
|
-
});
|
|
1268
|
-
const SecurityGroupSchema = Schema.Struct({
|
|
1269
|
-
vulnerability_alerts: Schema.optional(Schema.Boolean.annotations({
|
|
1270
|
-
title: "Vulnerability alerts",
|
|
1271
|
-
description: "Enable Dependabot vulnerability alerts (PUT/DELETE /repos/{o}/{r}/vulnerability-alerts)."
|
|
1272
|
-
})),
|
|
1273
|
-
automated_security_fixes: Schema.optional(Schema.Boolean.annotations({
|
|
1274
|
-
title: "Automated security fixes",
|
|
1275
|
-
description: "Enable Dependabot security pull requests (PUT/DELETE /repos/{o}/{r}/automated-security-fixes). Requires vulnerability_alerts to also be enabled."
|
|
1276
|
-
})),
|
|
1277
|
-
private_vulnerability_reporting: Schema.optional(Schema.Boolean.annotations({
|
|
1278
|
-
title: "Private vulnerability reporting",
|
|
1279
|
-
description: "Enable the private vulnerability reporting inbox (PUT/DELETE /repos/{o}/{r}/private-vulnerability-reporting)."
|
|
1280
|
-
}))
|
|
1281
|
-
}).pipe(Schema.filter((group)=>!(true === group.automated_security_fixes && false === group.vulnerability_alerts), {
|
|
1282
|
-
identifier: "SecurityGroup",
|
|
1283
|
-
message: ()=>"automated_security_fixes = true requires vulnerability_alerts to be enabled (or omitted to leave the existing setting in place)"
|
|
1284
|
-
})).annotations({
|
|
1285
|
-
identifier: "SecurityGroup",
|
|
1286
|
-
title: "Security group",
|
|
1287
|
-
description: "Toggles for repository-level security features that have dedicated PUT/DELETE endpoints (vulnerability alerts, automated security fixes, private vulnerability reporting). Omitted keys are left untouched.",
|
|
1288
|
-
jsonSchema: {
|
|
1289
|
-
...tombi({
|
|
1290
|
-
tableKeysOrder: "schema"
|
|
1291
|
-
}),
|
|
1292
|
-
...taplo({
|
|
1293
|
-
links: {
|
|
1294
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/configuration.md"
|
|
1295
|
-
}
|
|
1296
|
-
})
|
|
1297
|
-
}
|
|
1298
|
-
});
|
|
1299
|
-
const CodeScanningLanguageSchema = Schema.Literal("actions", "c-cpp", "csharp", "go", "java-kotlin", "javascript-typescript", "python", "ruby", "swift").annotations({
|
|
1300
|
-
identifier: "CodeScanningLanguage",
|
|
1301
|
-
title: "CodeQL default-setup language",
|
|
1302
|
-
description: "Languages supported by GitHub code scanning default setup. Note: this is narrower than the CodeQL analyzer (Rust is supported by CodeQL but not by default setup)."
|
|
1303
|
-
});
|
|
1304
|
-
const CodeScanningStateSchema = Schema.Literal("configured", "not-configured").annotations({
|
|
1305
|
-
identifier: "CodeScanningState",
|
|
1306
|
-
title: "Default setup state",
|
|
1307
|
-
description: '"configured" enables CodeQL default setup; "not-configured" disables it.'
|
|
1308
|
-
});
|
|
1309
|
-
const CodeScanningQuerySuiteSchema = Schema.Literal("default", "extended").annotations({
|
|
1310
|
-
identifier: "CodeScanningQuerySuite",
|
|
1311
|
-
title: "Query suite",
|
|
1312
|
-
description: '"default" runs the standard query set; "extended" includes additional security queries.'
|
|
1313
|
-
});
|
|
1314
|
-
const CodeScanningThreatModelSchema = Schema.Literal("remote", "remote_and_local").annotations({
|
|
1315
|
-
identifier: "CodeScanningThreatModel",
|
|
1316
|
-
title: "Threat model",
|
|
1317
|
-
description: '"remote" analyzes network sources only; "remote_and_local" also includes filesystem and environment access.'
|
|
1318
|
-
});
|
|
1319
|
-
const CodeScanningRunnerTypeSchema = Schema.Literal("standard", "labeled").annotations({
|
|
1320
|
-
identifier: "CodeScanningRunnerType",
|
|
1321
|
-
title: "Runner type",
|
|
1322
|
-
description: '"standard" uses GitHub-hosted runners; "labeled" uses runners matching runner_label.'
|
|
1323
|
-
});
|
|
1324
|
-
const CodeScanningGroupSchema = Schema.Struct({
|
|
1325
|
-
state: Schema.optional(CodeScanningStateSchema),
|
|
1326
|
-
languages: Schema.optional(Schema.Array(CodeScanningLanguageSchema).annotations({
|
|
1327
|
-
title: "Languages",
|
|
1328
|
-
description: "CodeQL languages to analyze. Languages not detected in the repository are skipped with a warning at sync time.",
|
|
1329
|
-
examples: [
|
|
1330
|
-
[
|
|
1331
|
-
"javascript-typescript",
|
|
1332
|
-
"python"
|
|
1333
|
-
]
|
|
1334
|
-
]
|
|
1335
|
-
})),
|
|
1336
|
-
query_suite: Schema.optional(CodeScanningQuerySuiteSchema),
|
|
1337
|
-
threat_model: Schema.optional(CodeScanningThreatModelSchema),
|
|
1338
|
-
runner_type: Schema.optional(CodeScanningRunnerTypeSchema),
|
|
1339
|
-
runner_label: Schema.optional(Schema.String.annotations({
|
|
1340
|
-
title: "Runner label",
|
|
1341
|
-
description: 'Self-hosted runner label. Required when runner_type = "labeled".'
|
|
1342
|
-
}))
|
|
1343
|
-
}).pipe(Schema.filter((group)=>"labeled" !== group.runner_type || void 0 !== group.runner_label, {
|
|
1344
|
-
identifier: "CodeScanningGroup",
|
|
1345
|
-
message: ()=>'runner_label is required when runner_type = "labeled"'
|
|
1346
|
-
})).annotations({
|
|
1347
|
-
identifier: "CodeScanningGroup",
|
|
1348
|
-
title: "Code scanning group",
|
|
1349
|
-
description: "CodeQL default setup configuration applied via PATCH /repos/{o}/{r}/code-scanning/default-setup. The endpoint returns 202 Accepted and configures asynchronously; reposets sends the request and does not poll for completion.",
|
|
1350
|
-
jsonSchema: {
|
|
1351
|
-
...tombi({
|
|
1352
|
-
tableKeysOrder: "schema"
|
|
1353
|
-
}),
|
|
1354
|
-
...taplo({
|
|
1355
|
-
links: {
|
|
1356
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/configuration.md"
|
|
1357
|
-
}
|
|
1358
|
-
})
|
|
1359
|
-
}
|
|
1360
|
-
});
|
|
1361
|
-
const LogLevelSchema = Schema.Literal("silent", "info", "verbose", "debug").annotations({
|
|
1362
|
-
identifier: "LogLevel",
|
|
1363
|
-
title: "Log level",
|
|
1364
|
-
description: "Controls output verbosity: silent (none), info (summaries), verbose (per-operation), debug (with sources)"
|
|
1365
|
-
});
|
|
1366
|
-
const ConfigSchema = Schema.Struct({
|
|
1367
|
-
owner: Schema.optional(Schema.String.annotations({
|
|
1368
|
-
title: "Default owner",
|
|
1369
|
-
description: "Default GitHub user or organization for all groups. Can be overridden per group.",
|
|
1370
|
-
examples: [
|
|
1371
|
-
"spencerbeggs",
|
|
1372
|
-
"savvy-web"
|
|
1373
|
-
]
|
|
1374
|
-
})),
|
|
1375
|
-
log_level: Schema.optionalWith(LogLevelSchema, {
|
|
1376
|
-
default: ()=>"info"
|
|
1377
|
-
}).annotations({
|
|
1378
|
-
title: "Log level",
|
|
1379
|
-
description: "Default output verbosity. Can be overridden with --log-level CLI flag."
|
|
1380
|
-
}),
|
|
1381
|
-
settings: Schema.optionalWith(Schema.Record({
|
|
1382
|
-
key: Schema.String,
|
|
1383
|
-
value: SettingsGroupSchema
|
|
1384
|
-
}).annotations({
|
|
1385
|
-
title: "Settings groups",
|
|
1386
|
-
description: "Named groups of GitHub repository settings to apply",
|
|
1387
|
-
jsonSchema: tombi({
|
|
1388
|
-
additionalKeyLabel: "setting_group"
|
|
1389
|
-
})
|
|
1390
|
-
}), {
|
|
1391
|
-
default: ()=>({})
|
|
1392
|
-
}),
|
|
1393
|
-
secrets: Schema.optionalWith(Schema.Record({
|
|
1394
|
-
key: Schema.String,
|
|
1395
|
-
value: SecretGroupSchema
|
|
1396
|
-
}).annotations({
|
|
1397
|
-
title: "Secret groups",
|
|
1398
|
-
description: "Named groups of secrets. Each group is one kind: file, value, or resolved.",
|
|
1399
|
-
jsonSchema: tombi({
|
|
1400
|
-
additionalKeyLabel: "secret_group"
|
|
1401
|
-
})
|
|
1402
|
-
}), {
|
|
1403
|
-
default: ()=>({})
|
|
1404
|
-
}),
|
|
1405
|
-
variables: Schema.optionalWith(Schema.Record({
|
|
1406
|
-
key: Schema.String,
|
|
1407
|
-
value: VariableGroupSchema
|
|
1408
|
-
}).annotations({
|
|
1409
|
-
title: "Variable groups",
|
|
1410
|
-
description: "Named groups of variables. Each group is one kind: file, value, or resolved.",
|
|
1411
|
-
jsonSchema: tombi({
|
|
1412
|
-
additionalKeyLabel: "variable_group"
|
|
1413
|
-
})
|
|
1414
|
-
}), {
|
|
1415
|
-
default: ()=>({})
|
|
1416
|
-
}),
|
|
1417
|
-
rulesets: Schema.optionalWith(Schema.Record({
|
|
1418
|
-
key: Schema.String,
|
|
1419
|
-
value: RulesetSchema
|
|
1420
|
-
}).annotations({
|
|
1421
|
-
title: "Rulesets",
|
|
1422
|
-
description: "Named rulesets defining branch and tag protection rules",
|
|
1423
|
-
jsonSchema: tombi({
|
|
1424
|
-
additionalKeyLabel: "ruleset_name"
|
|
1425
|
-
})
|
|
1426
|
-
}), {
|
|
1427
|
-
default: ()=>({})
|
|
1428
|
-
}),
|
|
1429
|
-
environments: Schema.optionalWith(Schema.Record({
|
|
1430
|
-
key: Schema.String,
|
|
1431
|
-
value: EnvironmentSchema
|
|
1432
|
-
}).annotations({
|
|
1433
|
-
title: "Environments",
|
|
1434
|
-
description: "Named deployment environment configurations",
|
|
1435
|
-
jsonSchema: tombi({
|
|
1436
|
-
additionalKeyLabel: "environment_name"
|
|
1437
|
-
})
|
|
1438
|
-
}), {
|
|
1439
|
-
default: ()=>({})
|
|
1440
|
-
}),
|
|
1441
|
-
security: Schema.optionalWith(Schema.Record({
|
|
1442
|
-
key: Schema.String,
|
|
1443
|
-
value: SecurityGroupSchema
|
|
1444
|
-
}).annotations({
|
|
1445
|
-
title: "Security groups",
|
|
1446
|
-
description: "Named security groups for vulnerability alerts, automated security fixes, and private vulnerability reporting",
|
|
1447
|
-
jsonSchema: tombi({
|
|
1448
|
-
additionalKeyLabel: "security_group"
|
|
1449
|
-
})
|
|
1450
|
-
}), {
|
|
1451
|
-
default: ()=>({})
|
|
1452
|
-
}),
|
|
1453
|
-
code_scanning: Schema.optionalWith(Schema.Record({
|
|
1454
|
-
key: Schema.String,
|
|
1455
|
-
value: CodeScanningGroupSchema
|
|
1456
|
-
}).annotations({
|
|
1457
|
-
title: "Code scanning groups",
|
|
1458
|
-
description: "Named code scanning groups for CodeQL default setup configuration",
|
|
1459
|
-
jsonSchema: tombi({
|
|
1460
|
-
additionalKeyLabel: "code_scanning_group"
|
|
1461
|
-
})
|
|
1462
|
-
}), {
|
|
1463
|
-
default: ()=>({})
|
|
1464
|
-
}),
|
|
1465
|
-
groups: Schema.Record({
|
|
1466
|
-
key: Schema.String,
|
|
1467
|
-
value: GroupSchema
|
|
1468
|
-
}).annotations({
|
|
1469
|
-
title: "Groups",
|
|
1470
|
-
description: "Named groups of repositories with their settings, secrets, variables, rulesets, environments, security, and code scanning assignments",
|
|
1471
|
-
jsonSchema: tombi({
|
|
1472
|
-
additionalKeyLabel: "group_name"
|
|
1473
|
-
})
|
|
1474
|
-
})
|
|
1475
|
-
}).annotations({
|
|
1476
|
-
identifier: "Config",
|
|
1477
|
-
title: "reposets Configuration",
|
|
1478
|
-
description: "Configuration for syncing GitHub repository settings, secrets, variables, rulesets, deployment environments, advanced security toggles, and CodeQL default setup",
|
|
1479
|
-
jsonSchema: {
|
|
1480
|
-
...tombi({
|
|
1481
|
-
tableKeysOrder: "schema"
|
|
1482
|
-
}),
|
|
1483
|
-
...taplo({
|
|
1484
|
-
initKeys: [
|
|
1485
|
-
"owner",
|
|
1486
|
-
"groups"
|
|
1487
|
-
],
|
|
1488
|
-
links: {
|
|
1489
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/configuration.md"
|
|
1490
|
-
}
|
|
1491
|
-
})
|
|
1492
|
-
}
|
|
1493
|
-
});
|
|
1494
|
-
const ResolveSectionSchema = Schema.Struct({
|
|
1495
|
-
op: Schema.optional(Schema.Record({
|
|
1496
|
-
key: Schema.String,
|
|
1497
|
-
value: Schema.String
|
|
1498
|
-
}).annotations({
|
|
1499
|
-
title: "1Password references",
|
|
1500
|
-
description: "Named values resolved via 1Password SDK. Values are op:// reference strings.",
|
|
1501
|
-
jsonSchema: tombi({
|
|
1502
|
-
additionalKeyLabel: "label"
|
|
1503
|
-
})
|
|
1504
|
-
})),
|
|
1505
|
-
file: Schema.optional(Schema.Record({
|
|
1506
|
-
key: Schema.String,
|
|
1507
|
-
value: Schema.String
|
|
1508
|
-
}).annotations({
|
|
1509
|
-
title: "File references",
|
|
1510
|
-
description: "Named values read from files. Values are file paths relative to the credentials directory.",
|
|
1511
|
-
jsonSchema: tombi({
|
|
1512
|
-
additionalKeyLabel: "label"
|
|
1513
|
-
})
|
|
1514
|
-
})),
|
|
1515
|
-
value: Schema.optional(Schema.Record({
|
|
1516
|
-
key: Schema.String,
|
|
1517
|
-
value: Schema.Union(Schema.String, Schema.Record({
|
|
1518
|
-
key: Schema.String,
|
|
1519
|
-
value: Jsonifiable
|
|
1520
|
-
}))
|
|
1521
|
-
}).annotations({
|
|
1522
|
-
title: "Inline values",
|
|
1523
|
-
description: "Named inline values. Strings are used as-is, objects are JSON-stringified.",
|
|
1524
|
-
jsonSchema: tombi({
|
|
1525
|
-
additionalKeyLabel: "label"
|
|
1526
|
-
})
|
|
1527
|
-
}))
|
|
1528
|
-
}).annotations({
|
|
1529
|
-
identifier: "ResolveSection",
|
|
1530
|
-
title: "Resolve section",
|
|
1531
|
-
description: "Named values resolved from 1Password, files, or inline. Referenced by resolved entries in secret and variable groups.",
|
|
1532
|
-
jsonSchema: taplo({
|
|
1533
|
-
links: {
|
|
1534
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/credentials.md"
|
|
1535
|
-
}
|
|
1536
|
-
})
|
|
1537
|
-
});
|
|
1538
|
-
const CredentialProfileSchema = Schema.Struct({
|
|
1539
|
-
github_token: Schema.String.annotations({
|
|
1540
|
-
title: "GitHub token",
|
|
1541
|
-
description: "A GitHub personal access token (fine-grained) with administration, secrets, variables, environments, and GPG keys permissions",
|
|
1542
|
-
examples: [
|
|
1543
|
-
"ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
1544
|
-
]
|
|
1545
|
-
}),
|
|
1546
|
-
op_service_account_token: Schema.optional(Schema.String.annotations({
|
|
1547
|
-
title: "1Password service account token",
|
|
1548
|
-
description: "A 1Password service account token for resolving op:// secret references",
|
|
1549
|
-
examples: [
|
|
1550
|
-
"ops_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
1551
|
-
]
|
|
1552
|
-
})),
|
|
1553
|
-
resolve: Schema.optional(ResolveSectionSchema)
|
|
1554
|
-
}).annotations({
|
|
1555
|
-
identifier: "CredentialProfile",
|
|
1556
|
-
title: "Credential profile",
|
|
1557
|
-
description: "Authentication credentials for a GitHub account with optional named values for secret and variable resolution",
|
|
1558
|
-
jsonSchema: taplo({
|
|
1559
|
-
initKeys: [
|
|
1560
|
-
"github_token"
|
|
1561
|
-
],
|
|
1562
|
-
links: {
|
|
1563
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/credentials.md"
|
|
1564
|
-
}
|
|
1565
|
-
})
|
|
1566
|
-
});
|
|
1567
|
-
const CredentialsSchema = Schema.Struct({
|
|
1568
|
-
profiles: Schema.optionalWith(Schema.Record({
|
|
1569
|
-
key: Schema.String,
|
|
1570
|
-
value: CredentialProfileSchema
|
|
1571
|
-
}).annotations({
|
|
1572
|
-
title: "Credential profiles",
|
|
1573
|
-
description: "Named credential profiles. If only one profile is defined, it is used automatically for all repo groups.",
|
|
1574
|
-
jsonSchema: tombi({
|
|
1575
|
-
additionalKeyLabel: "profile_name"
|
|
1576
|
-
})
|
|
1577
|
-
}), {
|
|
1578
|
-
default: ()=>({})
|
|
1579
|
-
})
|
|
1580
|
-
}).annotations({
|
|
1581
|
-
identifier: "Credentials",
|
|
1582
|
-
title: "reposets Credentials",
|
|
1583
|
-
description: "Authentication profiles for reposets. This file should be gitignored.",
|
|
1584
|
-
jsonSchema: taplo({
|
|
1585
|
-
initKeys: [
|
|
1586
|
-
"profiles"
|
|
1587
|
-
],
|
|
1588
|
-
links: {
|
|
1589
|
-
key: "https://github.com/spencerbeggs/reposets/blob/main/docs/credentials.md"
|
|
1590
|
-
}
|
|
1591
|
-
})
|
|
1592
|
-
});
|
|
1593
|
-
const CONFIG_FILENAME = "reposets.config.toml";
|
|
1594
|
-
const CREDENTIALS_FILENAME = "reposets.credentials.toml";
|
|
1595
|
-
const ReposetsConfigFile = ConfigFile.Tag("reposets/Config");
|
|
1596
|
-
const ReposetsCredentialsFile = ConfigFile.Tag("reposets/Credentials");
|
|
1597
|
-
function validateConfigRefs(config) {
|
|
1598
|
-
const errors = [];
|
|
1599
|
-
const definedSettings = new Set(Object.keys(config.settings));
|
|
1600
|
-
const definedSecrets = new Set(Object.keys(config.secrets));
|
|
1601
|
-
const definedVariables = new Set(Object.keys(config.variables));
|
|
1602
|
-
const definedRulesets = new Set(Object.keys(config.rulesets));
|
|
1603
|
-
const definedEnvironments = new Set(Object.keys(config.environments));
|
|
1604
|
-
const definedSecurity = new Set(Object.keys(config.security));
|
|
1605
|
-
const definedCodeScanning = new Set(Object.keys(config.code_scanning));
|
|
1606
|
-
for (const [groupName, group] of Object.entries(config.groups)){
|
|
1607
|
-
if (group.settings) {
|
|
1608
|
-
for (const ref of group.settings)if (!definedSettings.has(ref)) errors.push(`group '${groupName}': unknown settings group '${ref}'`);
|
|
1609
|
-
}
|
|
1610
|
-
if (group.rulesets) {
|
|
1611
|
-
for (const ref of group.rulesets)if (!definedRulesets.has(ref)) errors.push(`group '${groupName}': unknown ruleset '${ref}'`);
|
|
1612
|
-
}
|
|
1613
|
-
if (group.environments) {
|
|
1614
|
-
for (const ref of group.environments)if (!definedEnvironments.has(ref)) errors.push(`group '${groupName}': unknown environment '${ref}'`);
|
|
1615
|
-
}
|
|
1616
|
-
if (group.security) {
|
|
1617
|
-
for (const ref of group.security)if (!definedSecurity.has(ref)) errors.push(`group '${groupName}': unknown security group '${ref}'`);
|
|
1618
|
-
}
|
|
1619
|
-
if (group.code_scanning) {
|
|
1620
|
-
for (const ref of group.code_scanning)if (!definedCodeScanning.has(ref)) errors.push(`group '${groupName}': unknown code_scanning group '${ref}'`);
|
|
1621
|
-
}
|
|
1622
|
-
if (group.secrets) {
|
|
1623
|
-
if (group.secrets.actions) {
|
|
1624
|
-
for (const ref of group.secrets.actions)if (!definedSecrets.has(ref)) errors.push(`group '${groupName}': unknown secrets group '${ref}'`);
|
|
1625
|
-
}
|
|
1626
|
-
if (group.secrets.dependabot) {
|
|
1627
|
-
for (const ref of group.secrets.dependabot)if (!definedSecrets.has(ref)) errors.push(`group '${groupName}': unknown secrets group '${ref}'`);
|
|
1628
|
-
}
|
|
1629
|
-
if (group.secrets.codespaces) {
|
|
1630
|
-
for (const ref of group.secrets.codespaces)if (!definedSecrets.has(ref)) errors.push(`group '${groupName}': unknown secrets group '${ref}'`);
|
|
1631
|
-
}
|
|
1632
|
-
if (group.secrets.environments) for (const [envName, secretGroups] of Object.entries(group.secrets.environments)){
|
|
1633
|
-
if (!definedEnvironments.has(envName)) errors.push(`group '${groupName}': unknown environment '${envName}' in secrets.environments`);
|
|
1634
|
-
for (const ref of secretGroups)if (!definedSecrets.has(ref)) errors.push(`group '${groupName}': in secrets.environments.'${envName}': unknown secrets group '${ref}'`);
|
|
1635
|
-
}
|
|
1636
|
-
}
|
|
1637
|
-
if (group.variables) {
|
|
1638
|
-
if (group.variables.actions) {
|
|
1639
|
-
for (const ref of group.variables.actions)if (!definedVariables.has(ref)) errors.push(`group '${groupName}': unknown variables group '${ref}'`);
|
|
1640
|
-
}
|
|
1641
|
-
if (group.variables.environments) for (const [envName, varGroups] of Object.entries(group.variables.environments)){
|
|
1642
|
-
if (!definedEnvironments.has(envName)) errors.push(`group '${groupName}': unknown environment '${envName}' in variables.environments`);
|
|
1643
|
-
for (const ref of varGroups)if (!definedVariables.has(ref)) errors.push(`group '${groupName}': in variables.environments.'${envName}': unknown variables group '${ref}'`);
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
if (errors.length > 0) return Effect.fail(new ConfigError({
|
|
1648
|
-
operation: "validate",
|
|
1649
|
-
reason: errors.join("\n")
|
|
1650
|
-
}));
|
|
1651
|
-
return Effect.succeed(config);
|
|
1652
|
-
}
|
|
1653
|
-
function makeConfigFilesLive(configFlag) {
|
|
1654
|
-
const configResolvers = [];
|
|
1655
|
-
if (Option.isSome(configFlag)) {
|
|
1656
|
-
const flag = configFlag.value;
|
|
1657
|
-
if (existsSync(flag) && statSync(flag).isDirectory()) configResolvers.push(StaticDir({
|
|
1658
|
-
dir: flag,
|
|
1659
|
-
filename: CONFIG_FILENAME
|
|
1660
|
-
}));
|
|
1661
|
-
else configResolvers.push(ExplicitPath(flag));
|
|
1662
|
-
}
|
|
1663
|
-
configResolvers.push(UpwardWalk({
|
|
1664
|
-
filename: CONFIG_FILENAME
|
|
1665
|
-
}), XdgConfigResolver({
|
|
1666
|
-
filename: CONFIG_FILENAME
|
|
1667
|
-
}));
|
|
1668
|
-
return XdgConfigLive.multi({
|
|
1669
|
-
app: new AppDirsConfig({
|
|
1670
|
-
namespace: "reposets"
|
|
1671
|
-
}),
|
|
1672
|
-
configs: [
|
|
1673
|
-
{
|
|
1674
|
-
tag: ReposetsConfigFile,
|
|
1675
|
-
schema: ConfigSchema,
|
|
1676
|
-
codec: TomlCodec,
|
|
1677
|
-
strategy: FirstMatch,
|
|
1678
|
-
resolvers: configResolvers,
|
|
1679
|
-
validate: validateConfigRefs
|
|
1680
|
-
},
|
|
1681
|
-
{
|
|
1682
|
-
tag: ReposetsCredentialsFile,
|
|
1683
|
-
schema: CredentialsSchema,
|
|
1684
|
-
codec: TomlCodec,
|
|
1685
|
-
strategy: FirstMatch,
|
|
1686
|
-
resolvers: [
|
|
1687
|
-
UpwardWalk({
|
|
1688
|
-
filename: CREDENTIALS_FILENAME
|
|
1689
|
-
}),
|
|
1690
|
-
XdgConfigResolver({
|
|
1691
|
-
filename: CREDENTIALS_FILENAME
|
|
1692
|
-
})
|
|
1693
|
-
],
|
|
1694
|
-
defaultPath: XdgSavePath(CREDENTIALS_FILENAME)
|
|
1695
|
-
}
|
|
1696
|
-
]
|
|
1697
|
-
});
|
|
1698
|
-
}
|
|
1699
|
-
const ConfigFilesLive = makeConfigFilesLive(Option.none());
|
|
1700
|
-
class OnePasswordClient extends Context.Tag("OnePasswordClient")() {
|
|
1701
|
-
}
|
|
1702
|
-
const OnePasswordClientLive = Layer.succeed(OnePasswordClient, {
|
|
1703
|
-
resolve (reference, serviceAccountToken) {
|
|
1704
|
-
return Effect.tryPromise({
|
|
1705
|
-
try: async ()=>{
|
|
1706
|
-
const { createClient } = await import("@1password/sdk");
|
|
1707
|
-
const client = await createClient({
|
|
1708
|
-
auth: serviceAccountToken,
|
|
1709
|
-
integrationName: "reposets",
|
|
1710
|
-
integrationVersion: "1.0.0"
|
|
1711
|
-
});
|
|
1712
|
-
return await client.secrets.resolve(reference);
|
|
1713
|
-
},
|
|
1714
|
-
catch: (error)=>new OnePasswordError({
|
|
1715
|
-
message: `Failed to resolve ${reference}: ${error instanceof Error ? error.message : String(error)}`
|
|
1716
|
-
})
|
|
1717
|
-
});
|
|
1718
|
-
}
|
|
1719
|
-
});
|
|
1720
|
-
function OnePasswordClientTest(stubs) {
|
|
1721
|
-
return Layer.succeed(OnePasswordClient, {
|
|
1722
|
-
resolve (reference, _serviceAccountToken) {
|
|
1723
|
-
const value = stubs[reference];
|
|
1724
|
-
if (void 0 === value) return Effect.fail(new OnePasswordError({
|
|
1725
|
-
message: `Test stub: unknown reference ${reference}`
|
|
1726
|
-
}));
|
|
1727
|
-
return Effect.succeed(value);
|
|
1728
|
-
}
|
|
1729
|
-
});
|
|
1730
|
-
}
|
|
1731
|
-
class CredentialResolver extends Context.Tag("CredentialResolver")() {
|
|
1732
|
-
}
|
|
1733
|
-
const CredentialResolverLive = Layer.effect(CredentialResolver, Effect.gen(function*() {
|
|
1734
|
-
const opClient = yield* OnePasswordClient;
|
|
1735
|
-
return {
|
|
1736
|
-
resolveAll (profile, basePath) {
|
|
1737
|
-
return Effect.gen(function*() {
|
|
1738
|
-
const result = new Map();
|
|
1739
|
-
const resolveSection = profile.resolve;
|
|
1740
|
-
if (!resolveSection) return result;
|
|
1741
|
-
if (resolveSection.value) for (const [label, val] of Object.entries(resolveSection.value))if ("string" == typeof val) result.set(label, val);
|
|
1742
|
-
else result.set(label, JSON.stringify(val));
|
|
1743
|
-
if (resolveSection.file) for (const [label, filePath] of Object.entries(resolveSection.file)){
|
|
1744
|
-
const fullPath = isAbsolute(filePath) ? filePath : resolve(basePath, filePath);
|
|
1745
|
-
const content = yield* Effect["try"]({
|
|
1746
|
-
try: ()=>readFileSync(fullPath, "utf-8").trim(),
|
|
1747
|
-
catch: (error)=>new ResolveError({
|
|
1748
|
-
message: `Failed to read file for label '${label}': ${error instanceof Error ? error.message : String(error)}`
|
|
1749
|
-
})
|
|
1750
|
-
});
|
|
1751
|
-
result.set(label, content);
|
|
1752
|
-
}
|
|
1753
|
-
if (resolveSection.op) {
|
|
1754
|
-
const opToken = profile.op_service_account_token;
|
|
1755
|
-
if (!opToken) return yield* Effect.fail(new ResolveError({
|
|
1756
|
-
message: "No 1Password service account token provided but resolve.op entries are defined"
|
|
1757
|
-
}));
|
|
1758
|
-
for (const [label, reference] of Object.entries(resolveSection.op)){
|
|
1759
|
-
const value = yield* opClient.resolve(reference, opToken).pipe(Effect.mapError((err)=>new ResolveError({
|
|
1760
|
-
message: `Failed to resolve label '${label}': ${err.message}`
|
|
1761
|
-
})));
|
|
1762
|
-
result.set(label, value);
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
return result;
|
|
1766
|
-
});
|
|
1767
|
-
}
|
|
1768
|
-
};
|
|
1769
|
-
}));
|
|
1770
|
-
class GitHubClient extends Context.Tag("GitHubClient")() {
|
|
1771
|
-
}
|
|
1772
|
-
const ORG_ONLY_SETTINGS = new Set([
|
|
1773
|
-
"allow_forking"
|
|
1774
|
-
]);
|
|
1775
|
-
const SAA_STATUS_FIELDS = new Set([
|
|
1776
|
-
"advanced_security",
|
|
1777
|
-
"code_security",
|
|
1778
|
-
"secret_scanning",
|
|
1779
|
-
"secret_scanning_push_protection",
|
|
1780
|
-
"secret_scanning_ai_detection",
|
|
1781
|
-
"secret_scanning_non_provider_patterns",
|
|
1782
|
-
"secret_scanning_delegated_alert_dismissal",
|
|
1783
|
-
"secret_scanning_delegated_bypass",
|
|
1784
|
-
"dependabot_security_updates"
|
|
1785
|
-
]);
|
|
1786
|
-
function transformSecurityAndAnalysis(value) {
|
|
1787
|
-
if (null === value || "object" != typeof value) return;
|
|
1788
|
-
const input = value;
|
|
1789
|
-
const out = {};
|
|
1790
|
-
for (const [key, raw] of Object.entries(input))if (void 0 !== raw) {
|
|
1791
|
-
if (SAA_STATUS_FIELDS.has(key) && ("enabled" === raw || "disabled" === raw)) out[key] = {
|
|
1792
|
-
status: raw
|
|
1793
|
-
};
|
|
1794
|
-
else if ("delegated_bypass_reviewers" === key && Array.isArray(raw) && raw.length > 0) out.secret_scanning_delegated_bypass_options = {
|
|
1795
|
-
reviewers: raw
|
|
1796
|
-
};
|
|
1797
|
-
}
|
|
1798
|
-
return Object.keys(out).length > 0 ? out : void 0;
|
|
1799
|
-
}
|
|
1800
|
-
const GRAPHQL_SETTINGS = {
|
|
1801
|
-
has_sponsorships: "hasSponsorshipsEnabled",
|
|
1802
|
-
has_pull_requests: "hasPullRequestsEnabled"
|
|
1803
|
-
};
|
|
1804
|
-
function GitHubClientLive(token) {
|
|
1805
|
-
return Layer.succeed(GitHubClient, (()=>{
|
|
1806
|
-
const octokit = new Octokit({
|
|
1807
|
-
auth: token
|
|
1808
|
-
});
|
|
1809
|
-
function wrapError(error) {
|
|
1810
|
-
if (error instanceof Error) {
|
|
1811
|
-
const asAny = error;
|
|
1812
|
-
const status = "number" == typeof asAny.status ? asAny.status : void 0;
|
|
1813
|
-
if (void 0 !== status) return new GitHubApiError({
|
|
1814
|
-
message: error.message,
|
|
1815
|
-
status
|
|
1816
|
-
});
|
|
1817
|
-
return new GitHubApiError({
|
|
1818
|
-
message: error.message
|
|
1819
|
-
});
|
|
1820
|
-
}
|
|
1821
|
-
return new GitHubApiError({
|
|
1822
|
-
message: String(error)
|
|
1823
|
-
});
|
|
1824
|
-
}
|
|
1825
|
-
const ownerTypeCache = new Map();
|
|
1826
|
-
const teamIdCache = new Map();
|
|
1827
|
-
const roleIdCache = new Map();
|
|
1828
|
-
return {
|
|
1829
|
-
getOwnerType (owner) {
|
|
1830
|
-
return Effect.tryPromise({
|
|
1831
|
-
try: async ()=>{
|
|
1832
|
-
const cached = ownerTypeCache.get(owner);
|
|
1833
|
-
if (cached) return cached;
|
|
1834
|
-
const { data } = await octokit.users.getByUsername({
|
|
1835
|
-
username: owner
|
|
1836
|
-
});
|
|
1837
|
-
const ownerType = "Organization" === data.type ? "Organization" : "User";
|
|
1838
|
-
ownerTypeCache.set(owner, ownerType);
|
|
1839
|
-
return ownerType;
|
|
1840
|
-
},
|
|
1841
|
-
catch: wrapError
|
|
1842
|
-
});
|
|
1843
|
-
},
|
|
1844
|
-
syncSecret (owner, repo, name, value, scope) {
|
|
1845
|
-
return Effect.tryPromise({
|
|
1846
|
-
try: async ()=>{
|
|
1847
|
-
if ("actions" === scope) {
|
|
1848
|
-
const { data: publicKey } = await octokit.actions.getRepoPublicKey({
|
|
1849
|
-
owner,
|
|
1850
|
-
repo
|
|
1851
|
-
});
|
|
1852
|
-
const encryptedValue = encryptSecret(publicKey.key, value);
|
|
1853
|
-
await octokit.actions.createOrUpdateRepoSecret({
|
|
1854
|
-
owner,
|
|
1855
|
-
repo,
|
|
1856
|
-
secret_name: name,
|
|
1857
|
-
encrypted_value: encryptedValue,
|
|
1858
|
-
key_id: publicKey.key_id
|
|
1859
|
-
});
|
|
1860
|
-
} else if ("dependabot" === scope) {
|
|
1861
|
-
const { data: publicKey } = await octokit.dependabot.getRepoPublicKey({
|
|
1862
|
-
owner,
|
|
1863
|
-
repo
|
|
1864
|
-
});
|
|
1865
|
-
const encryptedValue = encryptSecret(publicKey.key, value);
|
|
1866
|
-
await octokit.dependabot.createOrUpdateRepoSecret({
|
|
1867
|
-
owner,
|
|
1868
|
-
repo,
|
|
1869
|
-
secret_name: name,
|
|
1870
|
-
encrypted_value: encryptedValue,
|
|
1871
|
-
key_id: publicKey.key_id
|
|
1872
|
-
});
|
|
1873
|
-
} else {
|
|
1874
|
-
const { data: publicKey } = await octokit.codespaces.getRepoPublicKey({
|
|
1875
|
-
owner,
|
|
1876
|
-
repo
|
|
1877
|
-
});
|
|
1878
|
-
const encryptedValue = encryptSecret(publicKey.key, value);
|
|
1879
|
-
await octokit.codespaces.createOrUpdateRepoSecret({
|
|
1880
|
-
owner,
|
|
1881
|
-
repo,
|
|
1882
|
-
secret_name: name,
|
|
1883
|
-
encrypted_value: encryptedValue,
|
|
1884
|
-
key_id: publicKey.key_id
|
|
1885
|
-
});
|
|
1886
|
-
}
|
|
1887
|
-
},
|
|
1888
|
-
catch: wrapError
|
|
1889
|
-
});
|
|
1890
|
-
},
|
|
1891
|
-
syncVariable (owner, repo, name, value) {
|
|
1892
|
-
return Effect.tryPromise({
|
|
1893
|
-
try: async ()=>{
|
|
1894
|
-
const { data } = await octokit.actions.listRepoVariables({
|
|
1895
|
-
owner,
|
|
1896
|
-
repo
|
|
1897
|
-
});
|
|
1898
|
-
const exists = data.variables.some((v)=>v.name === name);
|
|
1899
|
-
if (exists) await octokit.actions.updateRepoVariable({
|
|
1900
|
-
owner,
|
|
1901
|
-
repo,
|
|
1902
|
-
name,
|
|
1903
|
-
value
|
|
1904
|
-
});
|
|
1905
|
-
else await octokit.actions.createRepoVariable({
|
|
1906
|
-
owner,
|
|
1907
|
-
repo,
|
|
1908
|
-
name,
|
|
1909
|
-
value
|
|
1910
|
-
});
|
|
1911
|
-
},
|
|
1912
|
-
catch: wrapError
|
|
1913
|
-
});
|
|
1914
|
-
},
|
|
1915
|
-
syncSettings (owner, repo, settings) {
|
|
1916
|
-
return Effect.tryPromise({
|
|
1917
|
-
try: async ()=>{
|
|
1918
|
-
const restSettings = {};
|
|
1919
|
-
const graphqlInput = {};
|
|
1920
|
-
for (const [key, value] of Object.entries(settings)){
|
|
1921
|
-
if ("security_and_analysis" === key) {
|
|
1922
|
-
const saa = transformSecurityAndAnalysis(value);
|
|
1923
|
-
if (void 0 !== saa) restSettings.security_and_analysis = saa;
|
|
1924
|
-
continue;
|
|
1925
|
-
}
|
|
1926
|
-
const graphqlField = GRAPHQL_SETTINGS[key];
|
|
1927
|
-
if (void 0 !== graphqlField) graphqlInput[graphqlField] = value;
|
|
1928
|
-
else restSettings[key] = value;
|
|
1929
|
-
}
|
|
1930
|
-
if (false === restSettings.allow_merge_commit) {
|
|
1931
|
-
delete restSettings.merge_commit_title;
|
|
1932
|
-
delete restSettings.merge_commit_message;
|
|
1933
|
-
}
|
|
1934
|
-
if (false === restSettings.allow_squash_merge) {
|
|
1935
|
-
delete restSettings.squash_merge_commit_title;
|
|
1936
|
-
delete restSettings.squash_merge_commit_message;
|
|
1937
|
-
}
|
|
1938
|
-
if (Object.keys(restSettings).length > 0) await octokit.repos.update({
|
|
1939
|
-
owner,
|
|
1940
|
-
repo,
|
|
1941
|
-
...restSettings
|
|
1942
|
-
});
|
|
1943
|
-
if (Object.keys(graphqlInput).length > 0) {
|
|
1944
|
-
const { data: repoData } = await octokit.repos.get({
|
|
1945
|
-
owner,
|
|
1946
|
-
repo
|
|
1947
|
-
});
|
|
1948
|
-
await octokit.graphql(`mutation UpdateRepository($input: UpdateRepositoryInput!) {
|
|
1949
|
-
updateRepository(input: $input) {
|
|
1950
|
-
repository { id }
|
|
1951
|
-
}
|
|
1952
|
-
}`, {
|
|
1953
|
-
input: {
|
|
1954
|
-
repositoryId: repoData.node_id,
|
|
1955
|
-
...graphqlInput
|
|
1956
|
-
}
|
|
1957
|
-
});
|
|
1958
|
-
}
|
|
1959
|
-
},
|
|
1960
|
-
catch: wrapError
|
|
1961
|
-
});
|
|
1962
|
-
},
|
|
1963
|
-
syncRuleset (owner, repo, name, payload) {
|
|
1964
|
-
return Effect.tryPromise({
|
|
1965
|
-
try: async ()=>{
|
|
1966
|
-
const { data: existing } = await octokit.repos.getRepoRulesets({
|
|
1967
|
-
owner,
|
|
1968
|
-
repo
|
|
1969
|
-
});
|
|
1970
|
-
const match = existing.find((r)=>r.name === name);
|
|
1971
|
-
const body = {
|
|
1972
|
-
name: payload.name,
|
|
1973
|
-
target: payload.target,
|
|
1974
|
-
enforcement: payload.enforcement,
|
|
1975
|
-
...void 0 !== payload.conditions ? {
|
|
1976
|
-
conditions: payload.conditions
|
|
1977
|
-
} : {},
|
|
1978
|
-
...void 0 !== payload.rules ? {
|
|
1979
|
-
rules: payload.rules
|
|
1980
|
-
} : {},
|
|
1981
|
-
...void 0 !== payload.bypass_actors ? {
|
|
1982
|
-
bypass_actors: payload.bypass_actors
|
|
1983
|
-
} : {}
|
|
1984
|
-
};
|
|
1985
|
-
if (match) await octokit.repos.updateRepoRuleset({
|
|
1986
|
-
owner,
|
|
1987
|
-
repo,
|
|
1988
|
-
ruleset_id: match.id,
|
|
1989
|
-
...body
|
|
1990
|
-
});
|
|
1991
|
-
else await octokit.repos.createRepoRuleset({
|
|
1992
|
-
owner,
|
|
1993
|
-
repo,
|
|
1994
|
-
...body
|
|
1995
|
-
});
|
|
1996
|
-
},
|
|
1997
|
-
catch: wrapError
|
|
1998
|
-
});
|
|
1999
|
-
},
|
|
2000
|
-
listSecrets (owner, repo, scope) {
|
|
2001
|
-
return Effect.tryPromise({
|
|
2002
|
-
try: async ()=>{
|
|
2003
|
-
if ("actions" === scope) {
|
|
2004
|
-
const { data } = await octokit.actions.listRepoSecrets({
|
|
2005
|
-
owner,
|
|
2006
|
-
repo
|
|
2007
|
-
});
|
|
2008
|
-
return data.secrets.map((s)=>({
|
|
2009
|
-
name: s.name
|
|
2010
|
-
}));
|
|
2011
|
-
}
|
|
2012
|
-
if ("dependabot" === scope) {
|
|
2013
|
-
const { data } = await octokit.dependabot.listRepoSecrets({
|
|
2014
|
-
owner,
|
|
2015
|
-
repo
|
|
2016
|
-
});
|
|
2017
|
-
return data.secrets.map((s)=>({
|
|
2018
|
-
name: s.name
|
|
2019
|
-
}));
|
|
2020
|
-
}
|
|
2021
|
-
{
|
|
2022
|
-
const { data } = await octokit.codespaces.listRepoSecrets({
|
|
2023
|
-
owner,
|
|
2024
|
-
repo
|
|
2025
|
-
});
|
|
2026
|
-
return data.secrets.map((s)=>({
|
|
2027
|
-
name: s.name
|
|
2028
|
-
}));
|
|
2029
|
-
}
|
|
2030
|
-
},
|
|
2031
|
-
catch: wrapError
|
|
2032
|
-
});
|
|
2033
|
-
},
|
|
2034
|
-
listVariables (owner, repo) {
|
|
2035
|
-
return Effect.tryPromise({
|
|
2036
|
-
try: async ()=>{
|
|
2037
|
-
const { data } = await octokit.actions.listRepoVariables({
|
|
2038
|
-
owner,
|
|
2039
|
-
repo
|
|
2040
|
-
});
|
|
2041
|
-
return data.variables.map((v)=>({
|
|
2042
|
-
name: v.name
|
|
2043
|
-
}));
|
|
2044
|
-
},
|
|
2045
|
-
catch: wrapError
|
|
2046
|
-
});
|
|
2047
|
-
},
|
|
2048
|
-
listRulesets (owner, repo) {
|
|
2049
|
-
return Effect.tryPromise({
|
|
2050
|
-
try: async ()=>{
|
|
2051
|
-
const { data } = await octokit.repos.getRepoRulesets({
|
|
2052
|
-
owner,
|
|
2053
|
-
repo
|
|
2054
|
-
});
|
|
2055
|
-
return data.map((r)=>({
|
|
2056
|
-
name: r.name,
|
|
2057
|
-
id: r.id,
|
|
2058
|
-
source_type: r.source_type
|
|
2059
|
-
}));
|
|
2060
|
-
},
|
|
2061
|
-
catch: wrapError
|
|
2062
|
-
});
|
|
2063
|
-
},
|
|
2064
|
-
deleteSecret (owner, repo, name, scope) {
|
|
2065
|
-
return Effect.tryPromise({
|
|
2066
|
-
try: async ()=>{
|
|
2067
|
-
if ("actions" === scope) await octokit.actions.deleteRepoSecret({
|
|
2068
|
-
owner,
|
|
2069
|
-
repo,
|
|
2070
|
-
secret_name: name
|
|
2071
|
-
});
|
|
2072
|
-
else if ("dependabot" === scope) await octokit.dependabot.deleteRepoSecret({
|
|
2073
|
-
owner,
|
|
2074
|
-
repo,
|
|
2075
|
-
secret_name: name
|
|
2076
|
-
});
|
|
2077
|
-
else await octokit.codespaces.deleteRepoSecret({
|
|
2078
|
-
owner,
|
|
2079
|
-
repo,
|
|
2080
|
-
secret_name: name
|
|
2081
|
-
});
|
|
2082
|
-
},
|
|
2083
|
-
catch: wrapError
|
|
2084
|
-
});
|
|
2085
|
-
},
|
|
2086
|
-
deleteVariable (owner, repo, name) {
|
|
2087
|
-
return Effect.tryPromise({
|
|
2088
|
-
try: async ()=>{
|
|
2089
|
-
await octokit.actions.deleteRepoVariable({
|
|
2090
|
-
owner,
|
|
2091
|
-
repo,
|
|
2092
|
-
name
|
|
2093
|
-
});
|
|
2094
|
-
},
|
|
2095
|
-
catch: wrapError
|
|
2096
|
-
});
|
|
2097
|
-
},
|
|
2098
|
-
deleteRuleset (owner, repo, rulesetId) {
|
|
2099
|
-
return Effect.tryPromise({
|
|
2100
|
-
try: async ()=>{
|
|
2101
|
-
await octokit.repos.deleteRepoRuleset({
|
|
2102
|
-
owner,
|
|
2103
|
-
repo,
|
|
2104
|
-
ruleset_id: rulesetId
|
|
2105
|
-
});
|
|
2106
|
-
},
|
|
2107
|
-
catch: wrapError
|
|
2108
|
-
});
|
|
2109
|
-
},
|
|
2110
|
-
syncEnvironment (owner, repo, name, config) {
|
|
2111
|
-
return Effect.tryPromise({
|
|
2112
|
-
try: async ()=>{
|
|
2113
|
-
await octokit.repos.createOrUpdateEnvironment({
|
|
2114
|
-
owner,
|
|
2115
|
-
repo,
|
|
2116
|
-
environment_name: name,
|
|
2117
|
-
...config
|
|
2118
|
-
});
|
|
2119
|
-
},
|
|
2120
|
-
catch: wrapError
|
|
2121
|
-
});
|
|
2122
|
-
},
|
|
2123
|
-
syncEnvironmentSecret (owner, repo, envName, name, value) {
|
|
2124
|
-
return Effect.tryPromise({
|
|
2125
|
-
try: async ()=>{
|
|
2126
|
-
const { data: publicKey } = await octokit.actions.getEnvironmentPublicKey({
|
|
2127
|
-
owner,
|
|
2128
|
-
repo,
|
|
2129
|
-
environment_name: envName
|
|
2130
|
-
});
|
|
2131
|
-
const encrypted_value = encryptSecret(publicKey.key, value);
|
|
2132
|
-
await octokit.actions.createOrUpdateEnvironmentSecret({
|
|
2133
|
-
owner,
|
|
2134
|
-
repo,
|
|
2135
|
-
environment_name: envName,
|
|
2136
|
-
secret_name: name,
|
|
2137
|
-
encrypted_value,
|
|
2138
|
-
key_id: publicKey.key_id
|
|
2139
|
-
});
|
|
2140
|
-
},
|
|
2141
|
-
catch: wrapError
|
|
2142
|
-
});
|
|
2143
|
-
},
|
|
2144
|
-
syncEnvironmentVariable (owner, repo, envName, name, value) {
|
|
2145
|
-
return Effect.tryPromise({
|
|
2146
|
-
try: async ()=>{
|
|
2147
|
-
const { data } = await octokit.actions.listEnvironmentVariables({
|
|
2148
|
-
owner,
|
|
2149
|
-
repo,
|
|
2150
|
-
environment_name: envName
|
|
2151
|
-
});
|
|
2152
|
-
const exists = data.variables.some((v)=>v.name === name);
|
|
2153
|
-
if (exists) await octokit.actions.updateEnvironmentVariable({
|
|
2154
|
-
owner,
|
|
2155
|
-
repo,
|
|
2156
|
-
environment_name: envName,
|
|
2157
|
-
name,
|
|
2158
|
-
value
|
|
2159
|
-
});
|
|
2160
|
-
else await octokit.actions.createEnvironmentVariable({
|
|
2161
|
-
owner,
|
|
2162
|
-
repo,
|
|
2163
|
-
environment_name: envName,
|
|
2164
|
-
name,
|
|
2165
|
-
value
|
|
2166
|
-
});
|
|
2167
|
-
},
|
|
2168
|
-
catch: wrapError
|
|
2169
|
-
});
|
|
2170
|
-
},
|
|
2171
|
-
listEnvironments (owner, repo) {
|
|
2172
|
-
return Effect.tryPromise({
|
|
2173
|
-
try: async ()=>{
|
|
2174
|
-
const { data } = await octokit.repos.getAllEnvironments({
|
|
2175
|
-
owner,
|
|
2176
|
-
repo
|
|
2177
|
-
});
|
|
2178
|
-
return (data.environments ?? []).map((e)=>({
|
|
2179
|
-
name: e.name
|
|
2180
|
-
}));
|
|
2181
|
-
},
|
|
2182
|
-
catch: wrapError
|
|
2183
|
-
});
|
|
2184
|
-
},
|
|
2185
|
-
listEnvironmentSecrets (owner, repo, envName) {
|
|
2186
|
-
return Effect.tryPromise({
|
|
2187
|
-
try: async ()=>{
|
|
2188
|
-
const { data } = await octokit.actions.listEnvironmentSecrets({
|
|
2189
|
-
owner,
|
|
2190
|
-
repo,
|
|
2191
|
-
environment_name: envName
|
|
2192
|
-
});
|
|
2193
|
-
return data.secrets.map((s)=>({
|
|
2194
|
-
name: s.name
|
|
2195
|
-
}));
|
|
2196
|
-
},
|
|
2197
|
-
catch: wrapError
|
|
2198
|
-
});
|
|
2199
|
-
},
|
|
2200
|
-
listEnvironmentVariables (owner, repo, envName) {
|
|
2201
|
-
return Effect.tryPromise({
|
|
2202
|
-
try: async ()=>{
|
|
2203
|
-
const { data } = await octokit.actions.listEnvironmentVariables({
|
|
2204
|
-
owner,
|
|
2205
|
-
repo,
|
|
2206
|
-
environment_name: envName
|
|
2207
|
-
});
|
|
2208
|
-
return data.variables.map((v)=>({
|
|
2209
|
-
name: v.name
|
|
2210
|
-
}));
|
|
2211
|
-
},
|
|
2212
|
-
catch: wrapError
|
|
2213
|
-
});
|
|
2214
|
-
},
|
|
2215
|
-
deleteEnvironment (owner, repo, name) {
|
|
2216
|
-
return Effect.tryPromise({
|
|
2217
|
-
try: async ()=>{
|
|
2218
|
-
await octokit.repos.deleteAnEnvironment({
|
|
2219
|
-
owner,
|
|
2220
|
-
repo,
|
|
2221
|
-
environment_name: name
|
|
2222
|
-
});
|
|
2223
|
-
},
|
|
2224
|
-
catch: wrapError
|
|
2225
|
-
});
|
|
2226
|
-
},
|
|
2227
|
-
deleteEnvironmentSecret (owner, repo, envName, name) {
|
|
2228
|
-
return Effect.tryPromise({
|
|
2229
|
-
try: async ()=>{
|
|
2230
|
-
await octokit.actions.deleteEnvironmentSecret({
|
|
2231
|
-
owner,
|
|
2232
|
-
repo,
|
|
2233
|
-
environment_name: envName,
|
|
2234
|
-
secret_name: name
|
|
2235
|
-
});
|
|
2236
|
-
},
|
|
2237
|
-
catch: wrapError
|
|
2238
|
-
});
|
|
2239
|
-
},
|
|
2240
|
-
deleteEnvironmentVariable (owner, repo, envName, name) {
|
|
2241
|
-
return Effect.tryPromise({
|
|
2242
|
-
try: async ()=>{
|
|
2243
|
-
await octokit.actions.deleteEnvironmentVariable({
|
|
2244
|
-
owner,
|
|
2245
|
-
repo,
|
|
2246
|
-
environment_name: envName,
|
|
2247
|
-
name
|
|
2248
|
-
});
|
|
2249
|
-
},
|
|
2250
|
-
catch: wrapError
|
|
2251
|
-
});
|
|
2252
|
-
},
|
|
2253
|
-
getVulnerabilityAlerts (owner, repo) {
|
|
2254
|
-
return Effect.tryPromise({
|
|
2255
|
-
try: async ()=>{
|
|
2256
|
-
try {
|
|
2257
|
-
await octokit.request("GET /repos/{owner}/{repo}/vulnerability-alerts", {
|
|
2258
|
-
owner,
|
|
2259
|
-
repo
|
|
2260
|
-
});
|
|
2261
|
-
return true;
|
|
2262
|
-
} catch (error) {
|
|
2263
|
-
const status = error.status;
|
|
2264
|
-
if (404 === status) return false;
|
|
2265
|
-
throw error;
|
|
2266
|
-
}
|
|
2267
|
-
},
|
|
2268
|
-
catch: wrapError
|
|
2269
|
-
});
|
|
2270
|
-
},
|
|
2271
|
-
setVulnerabilityAlerts (owner, repo, enabled) {
|
|
2272
|
-
return Effect.tryPromise({
|
|
2273
|
-
try: async ()=>{
|
|
2274
|
-
if (enabled) await octokit.request("PUT /repos/{owner}/{repo}/vulnerability-alerts", {
|
|
2275
|
-
owner,
|
|
2276
|
-
repo
|
|
2277
|
-
});
|
|
2278
|
-
else await octokit.request("DELETE /repos/{owner}/{repo}/vulnerability-alerts", {
|
|
2279
|
-
owner,
|
|
2280
|
-
repo
|
|
2281
|
-
});
|
|
2282
|
-
},
|
|
2283
|
-
catch: wrapError
|
|
2284
|
-
});
|
|
2285
|
-
},
|
|
2286
|
-
getAutomatedSecurityFixes (owner, repo) {
|
|
2287
|
-
return Effect.tryPromise({
|
|
2288
|
-
try: async ()=>{
|
|
2289
|
-
const { data } = await octokit.request("GET /repos/{owner}/{repo}/automated-security-fixes", {
|
|
2290
|
-
owner,
|
|
2291
|
-
repo
|
|
2292
|
-
});
|
|
2293
|
-
return Boolean(data.enabled);
|
|
2294
|
-
},
|
|
2295
|
-
catch: wrapError
|
|
2296
|
-
});
|
|
2297
|
-
},
|
|
2298
|
-
setAutomatedSecurityFixes (owner, repo, enabled) {
|
|
2299
|
-
return Effect.tryPromise({
|
|
2300
|
-
try: async ()=>{
|
|
2301
|
-
if (enabled) await octokit.request("PUT /repos/{owner}/{repo}/automated-security-fixes", {
|
|
2302
|
-
owner,
|
|
2303
|
-
repo
|
|
2304
|
-
});
|
|
2305
|
-
else await octokit.request("DELETE /repos/{owner}/{repo}/automated-security-fixes", {
|
|
2306
|
-
owner,
|
|
2307
|
-
repo
|
|
2308
|
-
});
|
|
2309
|
-
},
|
|
2310
|
-
catch: wrapError
|
|
2311
|
-
});
|
|
2312
|
-
},
|
|
2313
|
-
getPrivateVulnerabilityReporting (owner, repo) {
|
|
2314
|
-
return Effect.tryPromise({
|
|
2315
|
-
try: async ()=>{
|
|
2316
|
-
const { data } = await octokit.request("GET /repos/{owner}/{repo}/private-vulnerability-reporting", {
|
|
2317
|
-
owner,
|
|
2318
|
-
repo
|
|
2319
|
-
});
|
|
2320
|
-
return Boolean(data.enabled);
|
|
2321
|
-
},
|
|
2322
|
-
catch: wrapError
|
|
2323
|
-
});
|
|
2324
|
-
},
|
|
2325
|
-
setPrivateVulnerabilityReporting (owner, repo, enabled) {
|
|
2326
|
-
return Effect.tryPromise({
|
|
2327
|
-
try: async ()=>{
|
|
2328
|
-
if (enabled) await octokit.request("PUT /repos/{owner}/{repo}/private-vulnerability-reporting", {
|
|
2329
|
-
owner,
|
|
2330
|
-
repo
|
|
2331
|
-
});
|
|
2332
|
-
else await octokit.request("DELETE /repos/{owner}/{repo}/private-vulnerability-reporting", {
|
|
2333
|
-
owner,
|
|
2334
|
-
repo
|
|
2335
|
-
});
|
|
2336
|
-
},
|
|
2337
|
-
catch: wrapError
|
|
2338
|
-
});
|
|
2339
|
-
},
|
|
2340
|
-
updateCodeScanningDefaultSetup (owner, repo, config) {
|
|
2341
|
-
return Effect.tryPromise({
|
|
2342
|
-
try: async ()=>{
|
|
2343
|
-
const body = {};
|
|
2344
|
-
if (void 0 !== config.state) body.state = config.state;
|
|
2345
|
-
if (void 0 !== config.languages) body.languages = [
|
|
2346
|
-
...config.languages
|
|
2347
|
-
];
|
|
2348
|
-
if (void 0 !== config.query_suite) body.query_suite = config.query_suite;
|
|
2349
|
-
if (void 0 !== config.threat_model) body.threat_model = config.threat_model;
|
|
2350
|
-
if (void 0 !== config.runner_type) body.runner_type = config.runner_type;
|
|
2351
|
-
if (void 0 !== config.runner_label) body.runner_label = config.runner_label;
|
|
2352
|
-
await octokit.request("PATCH /repos/{owner}/{repo}/code-scanning/default-setup", {
|
|
2353
|
-
owner,
|
|
2354
|
-
repo,
|
|
2355
|
-
...body
|
|
2356
|
-
});
|
|
2357
|
-
},
|
|
2358
|
-
catch: wrapError
|
|
2359
|
-
});
|
|
2360
|
-
},
|
|
2361
|
-
listRepoLanguages (owner, repo) {
|
|
2362
|
-
return Effect.tryPromise({
|
|
2363
|
-
try: async ()=>{
|
|
2364
|
-
const { data } = await octokit.repos.listLanguages({
|
|
2365
|
-
owner,
|
|
2366
|
-
repo
|
|
2367
|
-
});
|
|
2368
|
-
return Object.keys(data);
|
|
2369
|
-
},
|
|
2370
|
-
catch: wrapError
|
|
2371
|
-
});
|
|
2372
|
-
},
|
|
2373
|
-
resolveTeamId (org, slug) {
|
|
2374
|
-
return Effect.tryPromise({
|
|
2375
|
-
try: async ()=>{
|
|
2376
|
-
const cacheKey = `${org}:${slug}`;
|
|
2377
|
-
const cached = teamIdCache.get(cacheKey);
|
|
2378
|
-
if (void 0 !== cached) return cached;
|
|
2379
|
-
const { data } = await octokit.teams.getByName({
|
|
2380
|
-
org,
|
|
2381
|
-
team_slug: slug
|
|
2382
|
-
});
|
|
2383
|
-
teamIdCache.set(cacheKey, data.id);
|
|
2384
|
-
return data.id;
|
|
2385
|
-
},
|
|
2386
|
-
catch: wrapError
|
|
2387
|
-
});
|
|
2388
|
-
},
|
|
2389
|
-
resolveRoleId (org, name) {
|
|
2390
|
-
return Effect.tryPromise({
|
|
2391
|
-
try: async ()=>{
|
|
2392
|
-
const cacheKey = `${org}:${name}`;
|
|
2393
|
-
const cached = roleIdCache.get(cacheKey);
|
|
2394
|
-
if (void 0 !== cached) return cached;
|
|
2395
|
-
const { data } = await octokit.request("GET /orgs/{org}/organization-roles", {
|
|
2396
|
-
org
|
|
2397
|
-
});
|
|
2398
|
-
const roles = data.roles ?? [];
|
|
2399
|
-
const role = roles.find((r)=>r.name === name);
|
|
2400
|
-
if (!role) throw new Error(`organization role '${name}' not found in '${org}' (available: ${roles.map((r)=>r.name).join(", ") || "none"})`);
|
|
2401
|
-
roleIdCache.set(cacheKey, role.id);
|
|
2402
|
-
return role.id;
|
|
2403
|
-
},
|
|
2404
|
-
catch: wrapError
|
|
2405
|
-
});
|
|
2406
|
-
}
|
|
2407
|
-
};
|
|
2408
|
-
})());
|
|
2409
|
-
}
|
|
2410
|
-
function GitHubClientTest() {
|
|
2411
|
-
const recorded = [];
|
|
2412
|
-
const layer = Layer.succeed(GitHubClient, {
|
|
2413
|
-
getOwnerType (_owner) {
|
|
2414
|
-
return Effect.succeed("User");
|
|
2415
|
-
},
|
|
2416
|
-
syncSecret (owner, repo, name, _value, scope) {
|
|
2417
|
-
recorded.push({
|
|
2418
|
-
method: "syncSecret",
|
|
2419
|
-
args: {
|
|
2420
|
-
owner,
|
|
2421
|
-
repo,
|
|
2422
|
-
name,
|
|
2423
|
-
scope
|
|
2424
|
-
}
|
|
2425
|
-
});
|
|
2426
|
-
return Effect["void"];
|
|
2427
|
-
},
|
|
2428
|
-
syncVariable (owner, repo, name, _value) {
|
|
2429
|
-
recorded.push({
|
|
2430
|
-
method: "syncVariable",
|
|
2431
|
-
args: {
|
|
2432
|
-
owner,
|
|
2433
|
-
repo,
|
|
2434
|
-
name
|
|
2435
|
-
}
|
|
2436
|
-
});
|
|
2437
|
-
return Effect["void"];
|
|
2438
|
-
},
|
|
2439
|
-
syncSettings (owner, repo, settings) {
|
|
2440
|
-
recorded.push({
|
|
2441
|
-
method: "syncSettings",
|
|
2442
|
-
args: {
|
|
2443
|
-
owner,
|
|
2444
|
-
repo,
|
|
2445
|
-
settings
|
|
2446
|
-
}
|
|
2447
|
-
});
|
|
2448
|
-
return Effect["void"];
|
|
2449
|
-
},
|
|
2450
|
-
syncRuleset (owner, repo, name, _payload) {
|
|
2451
|
-
recorded.push({
|
|
2452
|
-
method: "syncRuleset",
|
|
2453
|
-
args: {
|
|
2454
|
-
owner,
|
|
2455
|
-
repo,
|
|
2456
|
-
name
|
|
2457
|
-
}
|
|
2458
|
-
});
|
|
2459
|
-
return Effect["void"];
|
|
2460
|
-
},
|
|
2461
|
-
listSecrets (_owner, _repo, _scope) {
|
|
2462
|
-
return Effect.succeed([]);
|
|
2463
|
-
},
|
|
2464
|
-
listVariables (_owner, _repo) {
|
|
2465
|
-
return Effect.succeed([]);
|
|
2466
|
-
},
|
|
2467
|
-
listRulesets (_owner, _repo) {
|
|
2468
|
-
return Effect.succeed([]);
|
|
2469
|
-
},
|
|
2470
|
-
deleteSecret (owner, repo, name, scope) {
|
|
2471
|
-
recorded.push({
|
|
2472
|
-
method: "deleteSecret",
|
|
2473
|
-
args: {
|
|
2474
|
-
owner,
|
|
2475
|
-
repo,
|
|
2476
|
-
name,
|
|
2477
|
-
scope
|
|
2478
|
-
}
|
|
2479
|
-
});
|
|
2480
|
-
return Effect["void"];
|
|
2481
|
-
},
|
|
2482
|
-
deleteVariable (owner, repo, name) {
|
|
2483
|
-
recorded.push({
|
|
2484
|
-
method: "deleteVariable",
|
|
2485
|
-
args: {
|
|
2486
|
-
owner,
|
|
2487
|
-
repo,
|
|
2488
|
-
name
|
|
2489
|
-
}
|
|
2490
|
-
});
|
|
2491
|
-
return Effect["void"];
|
|
2492
|
-
},
|
|
2493
|
-
deleteRuleset (owner, repo, rulesetId) {
|
|
2494
|
-
recorded.push({
|
|
2495
|
-
method: "deleteRuleset",
|
|
2496
|
-
args: {
|
|
2497
|
-
owner,
|
|
2498
|
-
repo,
|
|
2499
|
-
rulesetId
|
|
2500
|
-
}
|
|
2501
|
-
});
|
|
2502
|
-
return Effect["void"];
|
|
2503
|
-
},
|
|
2504
|
-
syncEnvironment (owner, repo, name, _config) {
|
|
2505
|
-
recorded.push({
|
|
2506
|
-
method: "syncEnvironment",
|
|
2507
|
-
args: {
|
|
2508
|
-
owner,
|
|
2509
|
-
repo,
|
|
2510
|
-
name
|
|
2511
|
-
}
|
|
2512
|
-
});
|
|
2513
|
-
return Effect["void"];
|
|
2514
|
-
},
|
|
2515
|
-
syncEnvironmentSecret (owner, repo, envName, name, _value) {
|
|
2516
|
-
recorded.push({
|
|
2517
|
-
method: "syncEnvironmentSecret",
|
|
2518
|
-
args: {
|
|
2519
|
-
owner,
|
|
2520
|
-
repo,
|
|
2521
|
-
envName,
|
|
2522
|
-
name
|
|
2523
|
-
}
|
|
2524
|
-
});
|
|
2525
|
-
return Effect["void"];
|
|
2526
|
-
},
|
|
2527
|
-
syncEnvironmentVariable (owner, repo, envName, name, _value) {
|
|
2528
|
-
recorded.push({
|
|
2529
|
-
method: "syncEnvironmentVariable",
|
|
2530
|
-
args: {
|
|
2531
|
-
owner,
|
|
2532
|
-
repo,
|
|
2533
|
-
envName,
|
|
2534
|
-
name
|
|
2535
|
-
}
|
|
2536
|
-
});
|
|
2537
|
-
return Effect["void"];
|
|
2538
|
-
},
|
|
2539
|
-
listEnvironments (_owner, _repo) {
|
|
2540
|
-
return Effect.succeed([]);
|
|
2541
|
-
},
|
|
2542
|
-
listEnvironmentSecrets (_owner, _repo, _envName) {
|
|
2543
|
-
return Effect.succeed([]);
|
|
2544
|
-
},
|
|
2545
|
-
listEnvironmentVariables (_owner, _repo, _envName) {
|
|
2546
|
-
return Effect.succeed([]);
|
|
2547
|
-
},
|
|
2548
|
-
deleteEnvironment (owner, repo, name) {
|
|
2549
|
-
recorded.push({
|
|
2550
|
-
method: "deleteEnvironment",
|
|
2551
|
-
args: {
|
|
2552
|
-
owner,
|
|
2553
|
-
repo,
|
|
2554
|
-
name
|
|
2555
|
-
}
|
|
2556
|
-
});
|
|
2557
|
-
return Effect["void"];
|
|
2558
|
-
},
|
|
2559
|
-
deleteEnvironmentSecret (owner, repo, envName, name) {
|
|
2560
|
-
recorded.push({
|
|
2561
|
-
method: "deleteEnvironmentSecret",
|
|
2562
|
-
args: {
|
|
2563
|
-
owner,
|
|
2564
|
-
repo,
|
|
2565
|
-
envName,
|
|
2566
|
-
name
|
|
2567
|
-
}
|
|
2568
|
-
});
|
|
2569
|
-
return Effect["void"];
|
|
2570
|
-
},
|
|
2571
|
-
deleteEnvironmentVariable (owner, repo, envName, name) {
|
|
2572
|
-
recorded.push({
|
|
2573
|
-
method: "deleteEnvironmentVariable",
|
|
2574
|
-
args: {
|
|
2575
|
-
owner,
|
|
2576
|
-
repo,
|
|
2577
|
-
envName,
|
|
2578
|
-
name
|
|
2579
|
-
}
|
|
2580
|
-
});
|
|
2581
|
-
return Effect["void"];
|
|
2582
|
-
},
|
|
2583
|
-
getVulnerabilityAlerts (_owner, _repo) {
|
|
2584
|
-
return Effect.succeed(false);
|
|
2585
|
-
},
|
|
2586
|
-
setVulnerabilityAlerts (owner, repo, enabled) {
|
|
2587
|
-
recorded.push({
|
|
2588
|
-
method: "setVulnerabilityAlerts",
|
|
2589
|
-
args: {
|
|
2590
|
-
owner,
|
|
2591
|
-
repo,
|
|
2592
|
-
enabled
|
|
2593
|
-
}
|
|
2594
|
-
});
|
|
2595
|
-
return Effect["void"];
|
|
2596
|
-
},
|
|
2597
|
-
getAutomatedSecurityFixes (_owner, _repo) {
|
|
2598
|
-
return Effect.succeed(false);
|
|
2599
|
-
},
|
|
2600
|
-
setAutomatedSecurityFixes (owner, repo, enabled) {
|
|
2601
|
-
recorded.push({
|
|
2602
|
-
method: "setAutomatedSecurityFixes",
|
|
2603
|
-
args: {
|
|
2604
|
-
owner,
|
|
2605
|
-
repo,
|
|
2606
|
-
enabled
|
|
2607
|
-
}
|
|
2608
|
-
});
|
|
2609
|
-
return Effect["void"];
|
|
2610
|
-
},
|
|
2611
|
-
getPrivateVulnerabilityReporting (_owner, _repo) {
|
|
2612
|
-
return Effect.succeed(false);
|
|
2613
|
-
},
|
|
2614
|
-
setPrivateVulnerabilityReporting (owner, repo, enabled) {
|
|
2615
|
-
recorded.push({
|
|
2616
|
-
method: "setPrivateVulnerabilityReporting",
|
|
2617
|
-
args: {
|
|
2618
|
-
owner,
|
|
2619
|
-
repo,
|
|
2620
|
-
enabled
|
|
2621
|
-
}
|
|
2622
|
-
});
|
|
2623
|
-
return Effect["void"];
|
|
2624
|
-
},
|
|
2625
|
-
updateCodeScanningDefaultSetup (owner, repo, config) {
|
|
2626
|
-
recorded.push({
|
|
2627
|
-
method: "updateCodeScanningDefaultSetup",
|
|
2628
|
-
args: {
|
|
2629
|
-
owner,
|
|
2630
|
-
repo,
|
|
2631
|
-
config
|
|
2632
|
-
}
|
|
2633
|
-
});
|
|
2634
|
-
return Effect["void"];
|
|
2635
|
-
},
|
|
2636
|
-
listRepoLanguages (_owner, _repo) {
|
|
2637
|
-
return Effect.succeed([]);
|
|
2638
|
-
},
|
|
2639
|
-
resolveTeamId (org, slug) {
|
|
2640
|
-
recorded.push({
|
|
2641
|
-
method: "resolveTeamId",
|
|
2642
|
-
args: {
|
|
2643
|
-
org,
|
|
2644
|
-
slug
|
|
2645
|
-
}
|
|
2646
|
-
});
|
|
2647
|
-
return Effect.succeed(0);
|
|
2648
|
-
},
|
|
2649
|
-
resolveRoleId (org, name) {
|
|
2650
|
-
recorded.push({
|
|
2651
|
-
method: "resolveRoleId",
|
|
2652
|
-
args: {
|
|
2653
|
-
org,
|
|
2654
|
-
name
|
|
2655
|
-
}
|
|
2656
|
-
});
|
|
2657
|
-
return Effect.succeed(0);
|
|
2658
|
-
}
|
|
2659
|
-
});
|
|
2660
|
-
return {
|
|
2661
|
-
layer,
|
|
2662
|
-
calls: ()=>[
|
|
2663
|
-
...recorded
|
|
2664
|
-
]
|
|
2665
|
-
};
|
|
2666
|
-
}
|
|
2667
|
-
class SyncLogger extends Context.Tag("SyncLogger")() {
|
|
2668
|
-
}
|
|
2669
|
-
function pluralize(resource, count) {
|
|
2670
|
-
if (1 === count) return resource;
|
|
2671
|
-
if ("ruleset" === resource) return "rulesets";
|
|
2672
|
-
if ("security feature" === resource) return "security features";
|
|
2673
|
-
if ("code scanning" === resource) return "code scanning";
|
|
2674
|
-
return `${resource}s`;
|
|
2675
|
-
}
|
|
2676
|
-
function SyncLoggerLive(config) {
|
|
2677
|
-
const { dryRun, logLevel, output } = config;
|
|
2678
|
-
return Layer.effect(SyncLogger, Effect.gen(function*() {
|
|
2679
|
-
const errors = yield* Ref.make([]);
|
|
2680
|
-
const currentRepo = yield* Ref.make("");
|
|
2681
|
-
function emit(line) {
|
|
2682
|
-
if (output) return Ref.update(output, (lines)=>[
|
|
2683
|
-
...lines,
|
|
2684
|
-
line
|
|
2685
|
-
]);
|
|
2686
|
-
return Effect.sync(()=>{
|
|
2687
|
-
process.stdout.write(`${line}\n`);
|
|
2688
|
-
});
|
|
2689
|
-
}
|
|
2690
|
-
function isVisible(tier) {
|
|
2691
|
-
if ("silent" === logLevel) return false;
|
|
2692
|
-
const levels = [
|
|
2693
|
-
"info",
|
|
2694
|
-
"verbose",
|
|
2695
|
-
"debug"
|
|
2696
|
-
];
|
|
2697
|
-
return levels.indexOf(logLevel) >= levels.indexOf(tier);
|
|
2698
|
-
}
|
|
2699
|
-
function formatVerb(pastTense, presentTense) {
|
|
2700
|
-
if (dryRun) return `would ${presentTense}`.padEnd(14);
|
|
2701
|
-
return pastTense.padEnd(8);
|
|
2702
|
-
}
|
|
2703
|
-
return {
|
|
2704
|
-
groupStart (name, repoCount) {
|
|
2705
|
-
if (!isVisible("info")) return Effect["void"];
|
|
2706
|
-
return emit(`group: ${name} (${repoCount} ${1 === repoCount ? "repo" : "repos"})`);
|
|
2707
|
-
},
|
|
2708
|
-
repoStart (owner, repo) {
|
|
2709
|
-
const repoSlug = `${owner}/${repo}`;
|
|
2710
|
-
if (!isVisible("info")) return Ref.set(currentRepo, repoSlug);
|
|
2711
|
-
return Effect.gen(function*() {
|
|
2712
|
-
yield* Ref.set(currentRepo, repoSlug);
|
|
2713
|
-
yield* emit(` repo: ${repoSlug}`);
|
|
2714
|
-
});
|
|
2715
|
-
},
|
|
2716
|
-
repoSkip (owner, repo, reason) {
|
|
2717
|
-
if (!isVisible("info")) return Effect["void"];
|
|
2718
|
-
return Effect.gen(function*() {
|
|
2719
|
-
yield* emit(` repo: ${owner}/${repo}`);
|
|
2720
|
-
yield* emit(` skip ${reason}`);
|
|
2721
|
-
});
|
|
2722
|
-
},
|
|
2723
|
-
syncSummary (resource, count, detail) {
|
|
2724
|
-
if (!isVisible("info")) return Effect["void"];
|
|
2725
|
-
const verb = formatVerb("synced", "sync");
|
|
2726
|
-
const noun = pluralize(resource, count);
|
|
2727
|
-
const suffix = detail ? ` (${detail})` : "";
|
|
2728
|
-
return emit(` ${verb}${count} ${noun}${suffix}`);
|
|
2729
|
-
},
|
|
2730
|
-
settingsApplied () {
|
|
2731
|
-
if (!isVisible("info")) return Effect["void"];
|
|
2732
|
-
const verb = formatVerb("applied", "apply");
|
|
2733
|
-
return emit(` ${verb}settings`);
|
|
2734
|
-
},
|
|
2735
|
-
cleanupSummary (resource, count, names) {
|
|
2736
|
-
if (!isVisible("info")) return Effect["void"];
|
|
2737
|
-
const verb = formatVerb("deleted", "delete");
|
|
2738
|
-
const noun = pluralize(resource, count);
|
|
2739
|
-
const suffix = names.length > 0 ? ` (${names.join(", ")})` : "";
|
|
2740
|
-
return emit(` ${verb}${count} ${noun}${suffix}`);
|
|
2741
|
-
},
|
|
2742
|
-
syncOperation (verb, resource, name, detail, source) {
|
|
2743
|
-
if (!isVisible("verbose")) return Effect["void"];
|
|
2744
|
-
const formattedVerb = formatVerb(verb, verb);
|
|
2745
|
-
const nameStr = name ? ` ${name}` : "";
|
|
2746
|
-
const suffix = detail ? ` ${detail}` : "";
|
|
2747
|
-
const sourceSuffix = source && isVisible("debug") ? ` <- ${source}` : "";
|
|
2748
|
-
return emit(` ${formattedVerb}${resource}${nameStr}${suffix}${sourceSuffix}`);
|
|
2749
|
-
},
|
|
2750
|
-
syncError (context, message) {
|
|
2751
|
-
if (!isVisible("info")) return Effect["void"];
|
|
2752
|
-
return Effect.gen(function*() {
|
|
2753
|
-
const repo = yield* Ref.get(currentRepo);
|
|
2754
|
-
yield* Ref.update(errors, (errs)=>[
|
|
2755
|
-
...errs,
|
|
2756
|
-
{
|
|
2757
|
-
repo,
|
|
2758
|
-
context,
|
|
2759
|
-
message
|
|
2760
|
-
}
|
|
2761
|
-
]);
|
|
2762
|
-
yield* emit(` error ${context}: ${message}`);
|
|
2763
|
-
});
|
|
2764
|
-
},
|
|
2765
|
-
finish () {
|
|
2766
|
-
if (!isVisible("info")) return Effect["void"];
|
|
2767
|
-
return Effect.gen(function*() {
|
|
2768
|
-
const errs = yield* Ref.get(errors);
|
|
2769
|
-
if (0 === errs.length) yield* emit("Sync complete!");
|
|
2770
|
-
else {
|
|
2771
|
-
const label = 1 === errs.length ? "error" : "errors";
|
|
2772
|
-
yield* emit(`Sync complete with ${errs.length} ${label}:`);
|
|
2773
|
-
for (const err of errs)yield* emit(` ${err.repo}: ${err.context} \u2014 ${err.message}`);
|
|
2774
|
-
}
|
|
2775
|
-
});
|
|
2776
|
-
}
|
|
2777
|
-
};
|
|
2778
|
-
}));
|
|
2779
|
-
}
|
|
2780
|
-
const ORG_ONLY_SAA_FIELDS = new Set([
|
|
2781
|
-
"secret_scanning_delegated_alert_dismissal",
|
|
2782
|
-
"secret_scanning_delegated_bypass",
|
|
2783
|
-
"delegated_bypass_reviewers"
|
|
2784
|
-
]);
|
|
2785
|
-
const REPO_LANG_TO_CODEQL = {
|
|
2786
|
-
JavaScript: "javascript-typescript",
|
|
2787
|
-
TypeScript: "javascript-typescript",
|
|
2788
|
-
C: "c-cpp",
|
|
2789
|
-
"C++": "c-cpp",
|
|
2790
|
-
"C#": "csharp",
|
|
2791
|
-
Go: "go",
|
|
2792
|
-
Java: "java-kotlin",
|
|
2793
|
-
Kotlin: "java-kotlin",
|
|
2794
|
-
Python: "python",
|
|
2795
|
-
Ruby: "ruby",
|
|
2796
|
-
Swift: "swift"
|
|
2797
|
-
};
|
|
2798
|
-
class SyncEngine extends Context.Tag("SyncEngine")() {
|
|
2799
|
-
}
|
|
2800
|
-
function isCleanupActive(scope) {
|
|
2801
|
-
return false !== scope;
|
|
2802
|
-
}
|
|
2803
|
-
function getPreserveList(scope) {
|
|
2804
|
-
if ("object" == typeof scope && "preserve" in scope) return new Set(scope.preserve);
|
|
2805
|
-
return new Set();
|
|
2806
|
-
}
|
|
2807
|
-
function resolveResourceGroup(group, credentialMap, basePath) {
|
|
2808
|
-
return Effect.gen(function*() {
|
|
2809
|
-
const result = new Map();
|
|
2810
|
-
if ("file" in group) for (const [name, filePath] of Object.entries(group.file)){
|
|
2811
|
-
const fullPath = isAbsolute(filePath) ? filePath : resolve(basePath, filePath);
|
|
2812
|
-
const content = yield* Effect["try"]({
|
|
2813
|
-
try: ()=>readFileSync(fullPath, "utf-8").trim(),
|
|
2814
|
-
catch: (error)=>new ResolveError({
|
|
2815
|
-
message: `Failed to read file for '${name}': ${error instanceof Error ? error.message : String(error)}`
|
|
2816
|
-
})
|
|
2817
|
-
});
|
|
2818
|
-
result.set(name, content);
|
|
2819
|
-
}
|
|
2820
|
-
else if ("value" in group) for (const [name, val] of Object.entries(group.value))result.set(name, "string" == typeof val ? val : JSON.stringify(val));
|
|
2821
|
-
else if ("resolved" in group) for (const [name, label] of Object.entries(group.resolved)){
|
|
2822
|
-
const value = credentialMap.get(label);
|
|
2823
|
-
if (void 0 === value) yield* Effect.fail(new ResolveError({
|
|
2824
|
-
message: `Credential label '${label}' not found for '${name}'`
|
|
2825
|
-
}));
|
|
2826
|
-
else result.set(name, value);
|
|
2827
|
-
}
|
|
2828
|
-
return result;
|
|
2829
|
-
});
|
|
2830
|
-
}
|
|
2831
|
-
function resolveRulesetRefs(ruleset, credentialMap) {
|
|
2832
|
-
const json = JSON.parse(JSON.stringify(ruleset));
|
|
2833
|
-
substituteResolved(json, credentialMap);
|
|
2834
|
-
return json;
|
|
2835
|
-
}
|
|
2836
|
-
function substituteResolved(obj, credentialMap) {
|
|
2837
|
-
for (const [key, value] of Object.entries(obj))if (value && "object" == typeof value && !Array.isArray(value)) {
|
|
2838
|
-
const rec = value;
|
|
2839
|
-
if ("resolved" in rec && "string" == typeof rec.resolved) {
|
|
2840
|
-
const resolved = credentialMap.get(rec.resolved);
|
|
2841
|
-
if (void 0 === resolved) throw new Error(`Credential label '${rec.resolved}' not found for ruleset field '${key}'`);
|
|
2842
|
-
const num = Number(resolved);
|
|
2843
|
-
obj[key] = Number.isNaN(num) ? resolved : num;
|
|
2844
|
-
} else substituteResolved(rec, credentialMap);
|
|
2845
|
-
} else if (Array.isArray(value)) {
|
|
2846
|
-
for (const item of value)if (item && "object" == typeof item) substituteResolved(item, credentialMap);
|
|
2847
|
-
}
|
|
2848
|
-
}
|
|
2849
|
-
function groupEntryNames(group) {
|
|
2850
|
-
if ("file" in group) return Object.keys(group.file);
|
|
2851
|
-
if ("value" in group) return Object.keys(group.value);
|
|
2852
|
-
if ("resolved" in group) return Object.keys(group.resolved);
|
|
2853
|
-
return [];
|
|
2854
|
-
}
|
|
2855
|
-
function mergeSecurityAndAnalysis(blocks) {
|
|
2856
|
-
const merged = {};
|
|
2857
|
-
let hasAny = false;
|
|
2858
|
-
for (const block of blocks)if (block) {
|
|
2859
|
-
hasAny = true;
|
|
2860
|
-
for (const [key, value] of Object.entries(block))if (void 0 !== value) merged[key] = value;
|
|
2861
|
-
}
|
|
2862
|
-
return hasAny ? merged : void 0;
|
|
2863
|
-
}
|
|
2864
|
-
function mergeSecurityGroups(groups) {
|
|
2865
|
-
const merged = {};
|
|
2866
|
-
for (const group of groups)if (group) {
|
|
2867
|
-
for (const [key, value] of Object.entries(group))if ("boolean" == typeof value) merged[key] = value;
|
|
2868
|
-
}
|
|
2869
|
-
return merged;
|
|
2870
|
-
}
|
|
2871
|
-
function mergeCodeScanningGroups(groups) {
|
|
2872
|
-
const merged = {};
|
|
2873
|
-
for (const group of groups)if (group) {
|
|
2874
|
-
for (const [key, value] of Object.entries(group))if (void 0 !== value) merged[key] = value;
|
|
2875
|
-
}
|
|
2876
|
-
return merged;
|
|
2877
|
-
}
|
|
2878
|
-
const SyncEngineLive = Layer.effect(SyncEngine, Effect.gen(function*() {
|
|
2879
|
-
const github = yield* GitHubClient;
|
|
2880
|
-
const credResolver = yield* CredentialResolver;
|
|
2881
|
-
const logger = yield* SyncLogger;
|
|
2882
|
-
return {
|
|
2883
|
-
syncAll (config, credentials, options) {
|
|
2884
|
-
return Effect.gen(function*() {
|
|
2885
|
-
const { dryRun, noCleanup, groupFilter, repoFilter } = options;
|
|
2886
|
-
const profileEntries = Object.entries(credentials.profiles);
|
|
2887
|
-
const defaultProfileName = 1 === profileEntries.length ? profileEntries[0]?.[0] ?? "default" : "default";
|
|
2888
|
-
const defaultProfile = credentials.profiles[defaultProfileName];
|
|
2889
|
-
const groups = Object.entries(config.groups);
|
|
2890
|
-
for (const [groupName, group] of groups){
|
|
2891
|
-
if (groupFilter && groupName !== groupFilter) continue;
|
|
2892
|
-
const owner = group.owner ?? config.owner ?? "";
|
|
2893
|
-
const profileName = group.credentials ?? defaultProfileName;
|
|
2894
|
-
const profile = credentials.profiles[profileName] ?? defaultProfile;
|
|
2895
|
-
yield* logger.groupStart(groupName, group.repos.length);
|
|
2896
|
-
const credentialMap = profile ? yield* credResolver.resolveAll(profile, options.configDir) : new Map();
|
|
2897
|
-
const ownerType = yield* github.getOwnerType(owner).pipe(Effect.catchTag("GitHubApiError", ()=>Effect.succeed("User")));
|
|
2898
|
-
const secretScopes = [
|
|
2899
|
-
"actions",
|
|
2900
|
-
"dependabot",
|
|
2901
|
-
"codespaces"
|
|
2902
|
-
];
|
|
2903
|
-
const resolvedSecrets = new Map();
|
|
2904
|
-
for (const scope of secretScopes){
|
|
2905
|
-
const groupRefs = group.secrets?.[scope] ?? [];
|
|
2906
|
-
for (const groupRef of groupRefs){
|
|
2907
|
-
const secretGroup = config.secrets[groupRef];
|
|
2908
|
-
if (!secretGroup) continue;
|
|
2909
|
-
const entries = yield* resolveResourceGroup(secretGroup, credentialMap, options.configDir);
|
|
2910
|
-
const scopeMap = resolvedSecrets.get(scope) ?? new Map();
|
|
2911
|
-
for (const [name, value] of entries)scopeMap.set(name, value);
|
|
2912
|
-
resolvedSecrets.set(scope, scopeMap);
|
|
2913
|
-
}
|
|
2914
|
-
}
|
|
2915
|
-
const resolvedVariables = new Map();
|
|
2916
|
-
const variableGroupRefs = group.variables?.actions ?? [];
|
|
2917
|
-
for (const groupRef of variableGroupRefs){
|
|
2918
|
-
const variableGroup = config.variables[groupRef];
|
|
2919
|
-
if (!variableGroup) continue;
|
|
2920
|
-
const entries = yield* resolveResourceGroup(variableGroup, credentialMap, options.configDir);
|
|
2921
|
-
for (const [name, value] of entries)resolvedVariables.set(name, value);
|
|
2922
|
-
}
|
|
2923
|
-
const groupRulesetRefs = group.rulesets ?? [];
|
|
2924
|
-
const rulesetMap = new Map();
|
|
2925
|
-
for (const ref of groupRulesetRefs){
|
|
2926
|
-
const ruleset = config.rulesets[ref];
|
|
2927
|
-
if (ruleset) {
|
|
2928
|
-
const resolved = resolveRulesetRefs(ruleset, credentialMap);
|
|
2929
|
-
const payload = buildRulesetPayload(resolved);
|
|
2930
|
-
rulesetMap.set(ref, payload);
|
|
2931
|
-
}
|
|
2932
|
-
}
|
|
2933
|
-
const envRefs = group.environments ?? [];
|
|
2934
|
-
const envSecretMapping = group.secrets?.environments ?? {};
|
|
2935
|
-
const envVariableMapping = group.variables?.environments ?? {};
|
|
2936
|
-
const resolvedEnvSecrets = new Map();
|
|
2937
|
-
for (const [envName, groupRefs] of Object.entries(envSecretMapping)){
|
|
2938
|
-
const envMap = new Map();
|
|
2939
|
-
for (const groupRef of groupRefs){
|
|
2940
|
-
const secretGroup = config.secrets[groupRef];
|
|
2941
|
-
if (!secretGroup) continue;
|
|
2942
|
-
const entries = yield* resolveResourceGroup(secretGroup, credentialMap, options.configDir);
|
|
2943
|
-
for (const [name, value] of entries)envMap.set(name, value);
|
|
2944
|
-
}
|
|
2945
|
-
resolvedEnvSecrets.set(envName, envMap);
|
|
2946
|
-
}
|
|
2947
|
-
const resolvedEnvVariables = new Map();
|
|
2948
|
-
for (const [envName, groupRefs] of Object.entries(envVariableMapping)){
|
|
2949
|
-
const envMap = new Map();
|
|
2950
|
-
for (const groupRef of groupRefs){
|
|
2951
|
-
const variableGroup = config.variables[groupRef];
|
|
2952
|
-
if (!variableGroup) continue;
|
|
2953
|
-
const entries = yield* resolveResourceGroup(variableGroup, credentialMap, options.configDir);
|
|
2954
|
-
for (const [name, value] of entries)envMap.set(name, value);
|
|
2955
|
-
}
|
|
2956
|
-
resolvedEnvVariables.set(envName, envMap);
|
|
2957
|
-
}
|
|
2958
|
-
const effectiveCleanup = group.cleanup ?? {
|
|
2959
|
-
secrets: {
|
|
2960
|
-
actions: false,
|
|
2961
|
-
dependabot: false,
|
|
2962
|
-
codespaces: false,
|
|
2963
|
-
environments: false
|
|
2964
|
-
},
|
|
2965
|
-
variables: {
|
|
2966
|
-
actions: false,
|
|
2967
|
-
environments: false
|
|
2968
|
-
},
|
|
2969
|
-
rulesets: false,
|
|
2970
|
-
environments: false
|
|
2971
|
-
};
|
|
2972
|
-
const configuredSecretNames = (scope)=>{
|
|
2973
|
-
const refs = group.secrets?.[scope] ?? [];
|
|
2974
|
-
const names = new Set();
|
|
2975
|
-
for (const ref of refs){
|
|
2976
|
-
const grp = config.secrets[ref];
|
|
2977
|
-
if (grp) for (const name of groupEntryNames(grp))names.add(name);
|
|
2978
|
-
}
|
|
2979
|
-
return names;
|
|
2980
|
-
};
|
|
2981
|
-
const configuredVariableNames = ()=>{
|
|
2982
|
-
const refs = group.variables?.actions ?? [];
|
|
2983
|
-
const names = new Set();
|
|
2984
|
-
for (const ref of refs){
|
|
2985
|
-
const grp = config.variables[ref];
|
|
2986
|
-
if (grp) for (const name of groupEntryNames(grp))names.add(name);
|
|
2987
|
-
}
|
|
2988
|
-
return names;
|
|
2989
|
-
};
|
|
2990
|
-
const configuredRulesetNames = ()=>{
|
|
2991
|
-
const refs = group.rulesets ?? [];
|
|
2992
|
-
const names = new Set();
|
|
2993
|
-
for (const ref of refs){
|
|
2994
|
-
const ruleset = config.rulesets[ref];
|
|
2995
|
-
if (ruleset) names.add(ruleset.name);
|
|
2996
|
-
}
|
|
2997
|
-
return names;
|
|
2998
|
-
};
|
|
2999
|
-
const settingGroupRefs = group.settings ?? [];
|
|
3000
|
-
const mergedSettings = {};
|
|
3001
|
-
const skippedSettings = [];
|
|
3002
|
-
const saaBlocks = [];
|
|
3003
|
-
for (const ref of settingGroupRefs){
|
|
3004
|
-
const settingGroup = config.settings[ref];
|
|
3005
|
-
if (settingGroup) {
|
|
3006
|
-
const { security_and_analysis, ...rest } = settingGroup;
|
|
3007
|
-
Object.assign(mergedSettings, rest);
|
|
3008
|
-
saaBlocks.push(security_and_analysis);
|
|
3009
|
-
}
|
|
3010
|
-
}
|
|
3011
|
-
if ("User" === ownerType) {
|
|
3012
|
-
for (const key of ORG_ONLY_SETTINGS)if (key in mergedSettings) {
|
|
3013
|
-
delete mergedSettings[key];
|
|
3014
|
-
skippedSettings.push(key);
|
|
3015
|
-
}
|
|
3016
|
-
}
|
|
3017
|
-
const mergedSAA = mergeSecurityAndAnalysis(saaBlocks);
|
|
3018
|
-
const skippedSAA = [];
|
|
3019
|
-
if (mergedSAA) {
|
|
3020
|
-
const saaOut = {
|
|
3021
|
-
...mergedSAA
|
|
3022
|
-
};
|
|
3023
|
-
if ("User" === ownerType) {
|
|
3024
|
-
for (const key of ORG_ONLY_SAA_FIELDS)if (key in saaOut) {
|
|
3025
|
-
delete saaOut[key];
|
|
3026
|
-
skippedSAA.push(key);
|
|
3027
|
-
}
|
|
3028
|
-
} else {
|
|
3029
|
-
const reviewers = saaOut.delegated_bypass_reviewers;
|
|
3030
|
-
if (Array.isArray(reviewers)) {
|
|
3031
|
-
const resolved = [];
|
|
3032
|
-
for (const reviewer of reviewers)if ("string" == typeof reviewer.team) {
|
|
3033
|
-
const teamId = yield* github.resolveTeamId(owner, reviewer.team).pipe(Effect.catchTag("GitHubApiError", (err)=>Effect.gen(function*() {
|
|
3034
|
-
yield* logger.syncError(`resolve team '${reviewer.team}'`, err.message);
|
|
3035
|
-
})));
|
|
3036
|
-
if (void 0 !== teamId) {
|
|
3037
|
-
const entry = {
|
|
3038
|
-
reviewer_id: teamId,
|
|
3039
|
-
reviewer_type: "TEAM"
|
|
3040
|
-
};
|
|
3041
|
-
if (void 0 !== reviewer.mode) entry.mode = reviewer.mode;
|
|
3042
|
-
resolved.push(entry);
|
|
3043
|
-
}
|
|
3044
|
-
} else if ("string" == typeof reviewer.role) {
|
|
3045
|
-
const roleId = yield* github.resolveRoleId(owner, reviewer.role).pipe(Effect.catchTag("GitHubApiError", (err)=>Effect.gen(function*() {
|
|
3046
|
-
yield* logger.syncError(`resolve role '${reviewer.role}'`, err.message);
|
|
3047
|
-
})));
|
|
3048
|
-
if (void 0 !== roleId) {
|
|
3049
|
-
const entry = {
|
|
3050
|
-
reviewer_id: roleId,
|
|
3051
|
-
reviewer_type: "ROLE"
|
|
3052
|
-
};
|
|
3053
|
-
if (void 0 !== reviewer.mode) entry.mode = reviewer.mode;
|
|
3054
|
-
resolved.push(entry);
|
|
3055
|
-
}
|
|
3056
|
-
}
|
|
3057
|
-
saaOut.delegated_bypass_reviewers = resolved;
|
|
3058
|
-
}
|
|
3059
|
-
}
|
|
3060
|
-
if (Object.keys(saaOut).length > 0) mergedSettings.security_and_analysis = saaOut;
|
|
3061
|
-
}
|
|
3062
|
-
const securityGroupRefs = group.security ?? [];
|
|
3063
|
-
const mergedSecurity = mergeSecurityGroups(securityGroupRefs.map((ref)=>config.security[ref]));
|
|
3064
|
-
const codeScanningRefs = group.code_scanning ?? [];
|
|
3065
|
-
const mergedCodeScanning = mergeCodeScanningGroups(codeScanningRefs.map((ref)=>config.code_scanning[ref]));
|
|
3066
|
-
const hasSecurity = Object.keys(mergedSecurity).length > 0;
|
|
3067
|
-
const securityContradiction = true === mergedSecurity.automated_security_fixes && false === mergedSecurity.vulnerability_alerts;
|
|
3068
|
-
const hasCodeScanning = Object.keys(mergedCodeScanning).length > 0;
|
|
3069
|
-
const hasSecrets = secretScopes.some((s)=>(resolvedSecrets.get(s)?.size ?? 0) > 0);
|
|
3070
|
-
const hasVariables = resolvedVariables.size > 0;
|
|
3071
|
-
const hasRulesets = rulesetMap.size > 0;
|
|
3072
|
-
const hasSettings = Object.keys(mergedSettings).length > 0;
|
|
3073
|
-
const hasEnvironments = envRefs.length > 0;
|
|
3074
|
-
const hasEnvSecrets = resolvedEnvSecrets.size > 0;
|
|
3075
|
-
const hasEnvVariables = resolvedEnvVariables.size > 0;
|
|
3076
|
-
const hasCleanup = !noCleanup && (isCleanupActive(effectiveCleanup.secrets.actions) || isCleanupActive(effectiveCleanup.secrets.dependabot) || isCleanupActive(effectiveCleanup.secrets.codespaces) || isCleanupActive(effectiveCleanup.secrets.environments) || isCleanupActive(effectiveCleanup.variables.actions) || isCleanupActive(effectiveCleanup.variables.environments) || isCleanupActive(effectiveCleanup.rulesets) || isCleanupActive(effectiveCleanup.environments));
|
|
3077
|
-
for (const repoName of group.repos)if (!repoFilter || repoName === repoFilter) {
|
|
3078
|
-
if (!hasSecrets && !hasVariables && !hasRulesets && !hasSettings && !hasEnvironments && !hasEnvSecrets && !hasEnvVariables && !hasSecurity && !hasCodeScanning && !hasCleanup) {
|
|
3079
|
-
yield* logger.repoSkip(owner, repoName, "no changes configured");
|
|
3080
|
-
continue;
|
|
3081
|
-
}
|
|
3082
|
-
yield* logger.repoStart(owner, repoName);
|
|
3083
|
-
if (!dryRun) {
|
|
3084
|
-
if (hasSettings) {
|
|
3085
|
-
yield* logger.syncOperation("apply", "settings", "");
|
|
3086
|
-
yield* github.syncSettings(owner, repoName, mergedSettings).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError("settings", err.message)));
|
|
3087
|
-
}
|
|
3088
|
-
for (const key of skippedSettings)yield* logger.syncOperation("skip", "setting", key, "(org-only, owner is a personal account)");
|
|
3089
|
-
for (const key of skippedSAA)yield* logger.syncOperation("skip", "security_and_analysis", key, "(org-only, owner is a personal account)");
|
|
3090
|
-
if (hasSecurity && securityContradiction) yield* logger.syncError("security merge", "automated_security_fixes = true requires vulnerability_alerts to be enabled (or omitted); skipping security sync");
|
|
3091
|
-
else if (hasSecurity) {
|
|
3092
|
-
if (void 0 !== mergedSecurity.vulnerability_alerts) {
|
|
3093
|
-
const desired = mergedSecurity.vulnerability_alerts;
|
|
3094
|
-
const current = yield* github.getVulnerabilityAlerts(owner, repoName).pipe(Effect.catchTag("GitHubApiError", (err)=>Effect.gen(function*() {
|
|
3095
|
-
yield* logger.syncError("get vulnerability_alerts", err.message);
|
|
3096
|
-
return desired;
|
|
3097
|
-
})));
|
|
3098
|
-
if (current !== desired) {
|
|
3099
|
-
yield* logger.syncOperation("sync", "vulnerability_alerts", desired ? "enable" : "disable");
|
|
3100
|
-
yield* github.setVulnerabilityAlerts(owner, repoName, desired).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError("vulnerability_alerts", err.message)));
|
|
3101
|
-
}
|
|
3102
|
-
}
|
|
3103
|
-
if (void 0 !== mergedSecurity.automated_security_fixes) {
|
|
3104
|
-
const desired = mergedSecurity.automated_security_fixes;
|
|
3105
|
-
const current = yield* github.getAutomatedSecurityFixes(owner, repoName).pipe(Effect.catchTag("GitHubApiError", (err)=>Effect.gen(function*() {
|
|
3106
|
-
yield* logger.syncError("get automated_security_fixes", err.message);
|
|
3107
|
-
return desired;
|
|
3108
|
-
})));
|
|
3109
|
-
if (current !== desired) {
|
|
3110
|
-
yield* logger.syncOperation("sync", "automated_security_fixes", desired ? "enable" : "disable");
|
|
3111
|
-
yield* github.setAutomatedSecurityFixes(owner, repoName, desired).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError("automated_security_fixes", err.message)));
|
|
3112
|
-
}
|
|
3113
|
-
}
|
|
3114
|
-
if (void 0 !== mergedSecurity.private_vulnerability_reporting) {
|
|
3115
|
-
const desired = mergedSecurity.private_vulnerability_reporting;
|
|
3116
|
-
const current = yield* github.getPrivateVulnerabilityReporting(owner, repoName).pipe(Effect.catchTag("GitHubApiError", (err)=>Effect.gen(function*() {
|
|
3117
|
-
yield* logger.syncError("get private_vulnerability_reporting", err.message);
|
|
3118
|
-
return desired;
|
|
3119
|
-
})));
|
|
3120
|
-
if (current !== desired) {
|
|
3121
|
-
yield* logger.syncOperation("sync", "private_vulnerability_reporting", desired ? "enable" : "disable");
|
|
3122
|
-
yield* github.setPrivateVulnerabilityReporting(owner, repoName, desired).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError("private_vulnerability_reporting", err.message)));
|
|
3123
|
-
}
|
|
3124
|
-
}
|
|
3125
|
-
}
|
|
3126
|
-
if (hasCodeScanning) {
|
|
3127
|
-
let desiredConfig = mergedCodeScanning;
|
|
3128
|
-
if (void 0 !== mergedCodeScanning.languages) {
|
|
3129
|
-
const detected = yield* github.listRepoLanguages(owner, repoName).pipe(Effect.catchTag("GitHubApiError", (err)=>Effect.gen(function*() {
|
|
3130
|
-
yield* logger.syncError("list repo languages", err.message);
|
|
3131
|
-
return [];
|
|
3132
|
-
})));
|
|
3133
|
-
const detectedCodeQL = new Set();
|
|
3134
|
-
for (const lang of detected){
|
|
3135
|
-
const mapped = REPO_LANG_TO_CODEQL[lang];
|
|
3136
|
-
if (mapped) detectedCodeQL.add(mapped);
|
|
3137
|
-
}
|
|
3138
|
-
const filtered = [];
|
|
3139
|
-
for (const lang of mergedCodeScanning.languages)if ("actions" === lang || detectedCodeQL.has(lang)) filtered.push(lang);
|
|
3140
|
-
else yield* logger.syncOperation("skip", "code_scanning language", lang, "(not detected in repository)");
|
|
3141
|
-
desiredConfig = {
|
|
3142
|
-
...mergedCodeScanning,
|
|
3143
|
-
languages: filtered
|
|
3144
|
-
};
|
|
3145
|
-
}
|
|
3146
|
-
yield* logger.syncOperation("sync", "code_scanning", desiredConfig.state ?? "default-setup");
|
|
3147
|
-
yield* github.updateCodeScanningDefaultSetup(owner, repoName, desiredConfig).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError("code_scanning default setup", err.message)));
|
|
3148
|
-
}
|
|
3149
|
-
for (const envName of envRefs){
|
|
3150
|
-
const envConfig = config.environments[envName];
|
|
3151
|
-
if (envConfig) {
|
|
3152
|
-
yield* logger.syncOperation("sync", "environment", envName);
|
|
3153
|
-
yield* github.syncEnvironment(owner, repoName, envName, envConfig).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`environment ${envName}`, err.message)));
|
|
3154
|
-
}
|
|
3155
|
-
}
|
|
3156
|
-
for (const scope of secretScopes){
|
|
3157
|
-
const scopeMap = resolvedSecrets.get(scope);
|
|
3158
|
-
if (scopeMap) for (const [name, value] of scopeMap){
|
|
3159
|
-
yield* logger.syncOperation("sync", "secret", name, `(${scope})`);
|
|
3160
|
-
yield* github.syncSecret(owner, repoName, name, value, scope).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`secret ${name} (${scope})`, err.message)));
|
|
3161
|
-
}
|
|
3162
|
-
}
|
|
3163
|
-
for (const [envName, envMap] of resolvedEnvSecrets)for (const [name, value] of envMap){
|
|
3164
|
-
yield* logger.syncOperation("sync", "secret", name, `(env: ${envName})`);
|
|
3165
|
-
yield* github.syncEnvironmentSecret(owner, repoName, envName, name, value).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`secret ${name} (env: ${envName})`, err.message)));
|
|
3166
|
-
}
|
|
3167
|
-
for (const [name, value] of resolvedVariables){
|
|
3168
|
-
yield* logger.syncOperation("sync", "variable", name);
|
|
3169
|
-
yield* github.syncVariable(owner, repoName, name, value).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`variable ${name}`, err.message)));
|
|
3170
|
-
}
|
|
3171
|
-
for (const [envName, envMap] of resolvedEnvVariables)for (const [name, value] of envMap){
|
|
3172
|
-
yield* logger.syncOperation("sync", "variable", name, `(env: ${envName})`);
|
|
3173
|
-
yield* github.syncEnvironmentVariable(owner, repoName, envName, name, value).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`variable ${name} (env: ${envName})`, err.message)));
|
|
3174
|
-
}
|
|
3175
|
-
for (const [_key, ruleset] of rulesetMap){
|
|
3176
|
-
yield* logger.syncOperation("sync", "ruleset", ruleset.name);
|
|
3177
|
-
yield* github.syncRuleset(owner, repoName, ruleset.name, ruleset).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`ruleset ${ruleset.name}`, err.message)));
|
|
3178
|
-
}
|
|
3179
|
-
}
|
|
3180
|
-
if (hasSecurity) {
|
|
3181
|
-
const securityCount = Object.values(mergedSecurity).filter((v)=>void 0 !== v).length;
|
|
3182
|
-
yield* logger.syncSummary("security feature", securityCount, "");
|
|
3183
|
-
}
|
|
3184
|
-
if (hasCodeScanning) yield* logger.syncSummary("code scanning", 1, mergedCodeScanning.state ?? "applied");
|
|
3185
|
-
if (hasEnvironments) yield* logger.syncSummary("environment", envRefs.length, "");
|
|
3186
|
-
if (hasSecrets) {
|
|
3187
|
-
const scopeCounts = [];
|
|
3188
|
-
let totalSecrets = 0;
|
|
3189
|
-
for (const scope of secretScopes){
|
|
3190
|
-
const count = resolvedSecrets.get(scope)?.size ?? 0;
|
|
3191
|
-
if (count > 0) {
|
|
3192
|
-
scopeCounts.push(`${scope}: ${count}`);
|
|
3193
|
-
totalSecrets += count;
|
|
3194
|
-
}
|
|
3195
|
-
}
|
|
3196
|
-
yield* logger.syncSummary("secret", totalSecrets, scopeCounts.join(", "));
|
|
3197
|
-
}
|
|
3198
|
-
if (hasVariables) yield* logger.syncSummary("variable", resolvedVariables.size, "");
|
|
3199
|
-
if (hasSettings) yield* logger.settingsApplied();
|
|
3200
|
-
if (rulesetMap.size > 0) yield* logger.syncSummary("ruleset", rulesetMap.size, "");
|
|
3201
|
-
if (!noCleanup) {
|
|
3202
|
-
if (isCleanupActive(effectiveCleanup.secrets.actions)) {
|
|
3203
|
-
const configured = configuredSecretNames("actions");
|
|
3204
|
-
const preserved = getPreserveList(effectiveCleanup.secrets.actions);
|
|
3205
|
-
const existing = yield* github.listSecrets(owner, repoName, "actions").pipe(Effect.catchTag("GitHubApiError", (err)=>Effect.gen(function*() {
|
|
3206
|
-
yield* logger.syncError("list secrets (actions)", err.message);
|
|
3207
|
-
return [];
|
|
3208
|
-
})));
|
|
3209
|
-
const toDelete = [];
|
|
3210
|
-
for (const { name } of existing)if (!configured.has(name) && !preserved.has(name)) {
|
|
3211
|
-
toDelete.push(name);
|
|
3212
|
-
yield* logger.syncOperation("delete", "secret", name, "(actions)");
|
|
3213
|
-
if (!dryRun) yield* github.deleteSecret(owner, repoName, name, "actions").pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`delete secret ${name} (actions)`, err.message)));
|
|
3214
|
-
}
|
|
3215
|
-
if (toDelete.length > 0) yield* logger.cleanupSummary("secret", toDelete.length, toDelete);
|
|
3216
|
-
}
|
|
3217
|
-
if (isCleanupActive(effectiveCleanup.secrets.dependabot)) {
|
|
3218
|
-
const configured = configuredSecretNames("dependabot");
|
|
3219
|
-
const preserved = getPreserveList(effectiveCleanup.secrets.dependabot);
|
|
3220
|
-
const existing = yield* github.listSecrets(owner, repoName, "dependabot").pipe(Effect.catchTag("GitHubApiError", (err)=>Effect.gen(function*() {
|
|
3221
|
-
yield* logger.syncError("list secrets (dependabot)", err.message);
|
|
3222
|
-
return [];
|
|
3223
|
-
})));
|
|
3224
|
-
const toDelete = [];
|
|
3225
|
-
for (const { name } of existing)if (!configured.has(name) && !preserved.has(name)) {
|
|
3226
|
-
toDelete.push(name);
|
|
3227
|
-
yield* logger.syncOperation("delete", "secret", name, "(dependabot)");
|
|
3228
|
-
if (!dryRun) yield* github.deleteSecret(owner, repoName, name, "dependabot").pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`delete secret ${name} (dependabot)`, err.message)));
|
|
3229
|
-
}
|
|
3230
|
-
if (toDelete.length > 0) yield* logger.cleanupSummary("secret", toDelete.length, toDelete);
|
|
3231
|
-
}
|
|
3232
|
-
if (isCleanupActive(effectiveCleanup.secrets.codespaces)) {
|
|
3233
|
-
const configured = configuredSecretNames("codespaces");
|
|
3234
|
-
const preserved = getPreserveList(effectiveCleanup.secrets.codespaces);
|
|
3235
|
-
const existing = yield* github.listSecrets(owner, repoName, "codespaces").pipe(Effect.catchTag("GitHubApiError", (err)=>Effect.gen(function*() {
|
|
3236
|
-
yield* logger.syncError("list secrets (codespaces)", err.message);
|
|
3237
|
-
return [];
|
|
3238
|
-
})));
|
|
3239
|
-
const toDelete = [];
|
|
3240
|
-
for (const { name } of existing)if (!configured.has(name) && !preserved.has(name)) {
|
|
3241
|
-
toDelete.push(name);
|
|
3242
|
-
yield* logger.syncOperation("delete", "secret", name, "(codespaces)");
|
|
3243
|
-
if (!dryRun) yield* github.deleteSecret(owner, repoName, name, "codespaces").pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`delete secret ${name} (codespaces)`, err.message)));
|
|
3244
|
-
}
|
|
3245
|
-
if (toDelete.length > 0) yield* logger.cleanupSummary("secret", toDelete.length, toDelete);
|
|
3246
|
-
}
|
|
3247
|
-
if (isCleanupActive(effectiveCleanup.secrets.environments)) {
|
|
3248
|
-
const preserved = getPreserveList(effectiveCleanup.secrets.environments);
|
|
3249
|
-
const allEnvNames = new Set([
|
|
3250
|
-
...envRefs,
|
|
3251
|
-
...Object.keys(envSecretMapping)
|
|
3252
|
-
]);
|
|
3253
|
-
for (const envName of allEnvNames){
|
|
3254
|
-
const configuredNames = new Set();
|
|
3255
|
-
for (const ref of envSecretMapping[envName] ?? []){
|
|
3256
|
-
const grp = config.secrets[ref];
|
|
3257
|
-
if (grp) for (const name of groupEntryNames(grp))configuredNames.add(name);
|
|
3258
|
-
}
|
|
3259
|
-
const existing = yield* github.listEnvironmentSecrets(owner, repoName, envName).pipe(Effect.catchTag("GitHubApiError", (err)=>Effect.gen(function*() {
|
|
3260
|
-
yield* logger.syncError(`list secrets (env: ${envName})`, err.message);
|
|
3261
|
-
return [];
|
|
3262
|
-
})));
|
|
3263
|
-
const toDelete = [];
|
|
3264
|
-
for (const { name } of existing)if (!configuredNames.has(name) && !preserved.has(name)) {
|
|
3265
|
-
toDelete.push(name);
|
|
3266
|
-
yield* logger.syncOperation("delete", "secret", name, `(env: ${envName})`);
|
|
3267
|
-
if (!dryRun) yield* github.deleteEnvironmentSecret(owner, repoName, envName, name).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`delete secret ${name} (env: ${envName})`, err.message)));
|
|
3268
|
-
}
|
|
3269
|
-
if (toDelete.length > 0) yield* logger.cleanupSummary("secret", toDelete.length, toDelete);
|
|
3270
|
-
}
|
|
3271
|
-
}
|
|
3272
|
-
if (isCleanupActive(effectiveCleanup.variables.actions)) {
|
|
3273
|
-
const configured = configuredVariableNames();
|
|
3274
|
-
const preserved = getPreserveList(effectiveCleanup.variables.actions);
|
|
3275
|
-
const existing = yield* github.listVariables(owner, repoName).pipe(Effect.catchTag("GitHubApiError", (err)=>Effect.gen(function*() {
|
|
3276
|
-
yield* logger.syncError("list variables", err.message);
|
|
3277
|
-
return [];
|
|
3278
|
-
})));
|
|
3279
|
-
const toDelete = [];
|
|
3280
|
-
for (const { name } of existing)if (!configured.has(name) && !preserved.has(name)) {
|
|
3281
|
-
toDelete.push(name);
|
|
3282
|
-
yield* logger.syncOperation("delete", "variable", name);
|
|
3283
|
-
if (!dryRun) yield* github.deleteVariable(owner, repoName, name).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`delete variable ${name}`, err.message)));
|
|
3284
|
-
}
|
|
3285
|
-
if (toDelete.length > 0) yield* logger.cleanupSummary("variable", toDelete.length, toDelete);
|
|
3286
|
-
}
|
|
3287
|
-
if (isCleanupActive(effectiveCleanup.variables.environments)) {
|
|
3288
|
-
const preserved = getPreserveList(effectiveCleanup.variables.environments);
|
|
3289
|
-
const allEnvVarNames = new Set([
|
|
3290
|
-
...envRefs,
|
|
3291
|
-
...Object.keys(envVariableMapping)
|
|
3292
|
-
]);
|
|
3293
|
-
for (const envName of allEnvVarNames){
|
|
3294
|
-
const configuredNames = new Set();
|
|
3295
|
-
for (const ref of envVariableMapping[envName] ?? []){
|
|
3296
|
-
const grp = config.variables[ref];
|
|
3297
|
-
if (grp) for (const name of groupEntryNames(grp))configuredNames.add(name);
|
|
3298
|
-
}
|
|
3299
|
-
const existing = yield* github.listEnvironmentVariables(owner, repoName, envName).pipe(Effect.catchTag("GitHubApiError", (err)=>Effect.gen(function*() {
|
|
3300
|
-
yield* logger.syncError(`list variables (env: ${envName})`, err.message);
|
|
3301
|
-
return [];
|
|
3302
|
-
})));
|
|
3303
|
-
const toDelete = [];
|
|
3304
|
-
for (const { name } of existing)if (!configuredNames.has(name) && !preserved.has(name)) {
|
|
3305
|
-
toDelete.push(name);
|
|
3306
|
-
yield* logger.syncOperation("delete", "variable", name, `(env: ${envName})`);
|
|
3307
|
-
if (!dryRun) yield* github.deleteEnvironmentVariable(owner, repoName, envName, name).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`delete variable ${name} (env: ${envName})`, err.message)));
|
|
3308
|
-
}
|
|
3309
|
-
if (toDelete.length > 0) yield* logger.cleanupSummary("variable", toDelete.length, toDelete);
|
|
3310
|
-
}
|
|
3311
|
-
}
|
|
3312
|
-
if (isCleanupActive(effectiveCleanup.rulesets)) {
|
|
3313
|
-
const configured = configuredRulesetNames();
|
|
3314
|
-
const preserved = getPreserveList(effectiveCleanup.rulesets);
|
|
3315
|
-
const existing = yield* github.listRulesets(owner, repoName).pipe(Effect.catchTag("GitHubApiError", (err)=>Effect.gen(function*() {
|
|
3316
|
-
yield* logger.syncError("list rulesets", err.message);
|
|
3317
|
-
return [];
|
|
3318
|
-
})));
|
|
3319
|
-
const toDelete = [];
|
|
3320
|
-
for (const { name, id, source_type } of existing)if ("Repository" === source_type) {
|
|
3321
|
-
if (!configured.has(name) && !preserved.has(name)) {
|
|
3322
|
-
toDelete.push(name);
|
|
3323
|
-
yield* logger.syncOperation("delete", "ruleset", name);
|
|
3324
|
-
if (!dryRun) yield* github.deleteRuleset(owner, repoName, id).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`delete ruleset ${name}`, err.message)));
|
|
3325
|
-
}
|
|
3326
|
-
}
|
|
3327
|
-
if (toDelete.length > 0) yield* logger.cleanupSummary("ruleset", toDelete.length, toDelete);
|
|
3328
|
-
}
|
|
3329
|
-
if (isCleanupActive(effectiveCleanup.environments)) {
|
|
3330
|
-
const configuredEnvNames = new Set(envRefs);
|
|
3331
|
-
const preserved = getPreserveList(effectiveCleanup.environments);
|
|
3332
|
-
const existing = yield* github.listEnvironments(owner, repoName).pipe(Effect.catchTag("GitHubApiError", (err)=>Effect.gen(function*() {
|
|
3333
|
-
yield* logger.syncError("list environments", err.message);
|
|
3334
|
-
return [];
|
|
3335
|
-
})));
|
|
3336
|
-
const toDelete = [];
|
|
3337
|
-
for (const { name } of existing)if (!configuredEnvNames.has(name) && !preserved.has(name)) {
|
|
3338
|
-
toDelete.push(name);
|
|
3339
|
-
yield* logger.syncOperation("delete", "environment", name);
|
|
3340
|
-
if (!dryRun) yield* github.deleteEnvironment(owner, repoName, name).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`delete environment ${name}`, err.message)));
|
|
3341
|
-
}
|
|
3342
|
-
if (toDelete.length > 0) yield* logger.cleanupSummary("environment", toDelete.length, toDelete);
|
|
3343
|
-
}
|
|
3344
|
-
}
|
|
3345
|
-
}
|
|
3346
|
-
}
|
|
3347
|
-
yield* logger.finish();
|
|
3348
|
-
}).pipe(Effect.catchAll((error)=>Effect.fail(error instanceof SyncError ? error : new SyncError({
|
|
3349
|
-
message: error instanceof Error ? error.message : String(error)
|
|
3350
|
-
}))));
|
|
3351
|
-
}
|
|
3352
|
-
};
|
|
3353
|
-
}));
|
|
3354
|
-
export { BypassActorSchema, CONFIG_FILENAME, CREDENTIALS_FILENAME, CleanupSchema, CleanupScopeSchema, ConfigFilesLive, ConfigSchema, CredentialProfileSchema, CredentialResolver, CredentialResolverLive, CredentialsSchema, GitHubApiError, GitHubClient, GitHubClientLive, GitHubClientTest, GroupSchema, LogLevelSchema, OnePasswordClient, OnePasswordClientLive, OnePasswordClientTest, OnePasswordError, ReposetsConfigFile, ReposetsCredentialsFile, ResolveError, ResolveSectionSchema, ResolvedRefSchema, RulesetSchema, SecretGroupSchema, SyncEngine, SyncEngineLive, SyncError, SyncLogger, SyncLoggerLive, VariableGroupSchema, buildRulesetPayload, encryptSecret, makeConfigFilesLive, validateConfigRefs };
|