reposets 0.4.0 → 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
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { CleanupSchema, SecretGroupSchema, VariableGroupSchema } from "./common.js";
|
|
2
|
+
import { EnvironmentSchema } from "./environment.js";
|
|
3
|
+
import { RulesetSchema } from "./ruleset.js";
|
|
4
|
+
import { Jsonifiable, taplo, tombi } from "xdg-effect";
|
|
5
|
+
import { Schema } from "effect";
|
|
6
|
+
|
|
7
|
+
//#region src/schemas/config.ts
|
|
8
|
+
const SecretScopesSchema = Schema.Struct({
|
|
9
|
+
actions: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
10
|
+
title: "Action secret groups",
|
|
11
|
+
description: "Secret groups to sync as GitHub Actions repository secrets",
|
|
12
|
+
examples: [["deploy", "app"]]
|
|
13
|
+
})),
|
|
14
|
+
dependabot: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
15
|
+
title: "Dependabot secret groups",
|
|
16
|
+
description: "Secret groups to sync as Dependabot secrets",
|
|
17
|
+
examples: [["deploy"]]
|
|
18
|
+
})),
|
|
19
|
+
codespaces: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
20
|
+
title: "Codespaces secret groups",
|
|
21
|
+
description: "Secret groups to sync as Codespaces secrets",
|
|
22
|
+
examples: [["deploy"]]
|
|
23
|
+
})),
|
|
24
|
+
environments: Schema.optional(Schema.Record({
|
|
25
|
+
key: Schema.String,
|
|
26
|
+
value: Schema.Array(Schema.String).annotations({
|
|
27
|
+
title: "Environment secret groups",
|
|
28
|
+
description: "Secret groups to sync as environment secrets"
|
|
29
|
+
})
|
|
30
|
+
}).annotations({
|
|
31
|
+
title: "Environment secret scopes",
|
|
32
|
+
description: "Map of environment names to secret group references",
|
|
33
|
+
jsonSchema: tombi({ additionalKeyLabel: "environment_name" })
|
|
34
|
+
}))
|
|
35
|
+
}).annotations({
|
|
36
|
+
identifier: "SecretScopes",
|
|
37
|
+
title: "Secret scopes",
|
|
38
|
+
description: "Assign secret groups to GitHub secret scopes (actions, dependabot, codespaces, environments)"
|
|
39
|
+
});
|
|
40
|
+
const VariableScopesSchema = Schema.Struct({
|
|
41
|
+
actions: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
42
|
+
title: "Action variable groups",
|
|
43
|
+
description: "Variable groups to sync as GitHub Actions repository variables",
|
|
44
|
+
examples: [["common"]]
|
|
45
|
+
})),
|
|
46
|
+
environments: Schema.optional(Schema.Record({
|
|
47
|
+
key: Schema.String,
|
|
48
|
+
value: Schema.Array(Schema.String).annotations({
|
|
49
|
+
title: "Environment variable groups",
|
|
50
|
+
description: "Variable groups to sync as environment variables"
|
|
51
|
+
})
|
|
52
|
+
}).annotations({
|
|
53
|
+
title: "Environment variable scopes",
|
|
54
|
+
description: "Map of environment names to variable group references",
|
|
55
|
+
jsonSchema: tombi({ additionalKeyLabel: "environment_name" })
|
|
56
|
+
}))
|
|
57
|
+
}).annotations({
|
|
58
|
+
identifier: "VariableScopes",
|
|
59
|
+
title: "Variable scopes",
|
|
60
|
+
description: "Assign variable groups to GitHub variable scopes (actions, environments)"
|
|
61
|
+
});
|
|
62
|
+
const GroupSchema = Schema.Struct({
|
|
63
|
+
owner: Schema.optional(Schema.String.annotations({
|
|
64
|
+
title: "Owner override",
|
|
65
|
+
description: "GitHub user or organization that owns these repos. Overrides the top-level owner.",
|
|
66
|
+
examples: ["savvy-web"]
|
|
67
|
+
})),
|
|
68
|
+
repos: Schema.Array(Schema.String).annotations({
|
|
69
|
+
title: "Repository names",
|
|
70
|
+
description: "List of repository names (without owner prefix) to sync in this group",
|
|
71
|
+
examples: [[
|
|
72
|
+
"repo-one",
|
|
73
|
+
"repo-two",
|
|
74
|
+
"repo-three"
|
|
75
|
+
]],
|
|
76
|
+
jsonSchema: tombi({ arrayValuesOrder: "ascending" })
|
|
77
|
+
}),
|
|
78
|
+
credentials: Schema.optional(Schema.String.annotations({
|
|
79
|
+
title: "Credential profile",
|
|
80
|
+
description: "Name of the credential profile to use. If only one profile exists, it is used automatically.",
|
|
81
|
+
examples: ["personal", "work"]
|
|
82
|
+
})),
|
|
83
|
+
settings: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
84
|
+
title: "Settings groups",
|
|
85
|
+
description: "Names of settings groups to apply to these repos",
|
|
86
|
+
examples: [["oss-defaults"]]
|
|
87
|
+
})),
|
|
88
|
+
environments: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
89
|
+
title: "Environments",
|
|
90
|
+
description: "Names of environment definitions to create/update for these repos",
|
|
91
|
+
examples: [["staging", "production"]]
|
|
92
|
+
})),
|
|
93
|
+
secrets: Schema.optional(SecretScopesSchema),
|
|
94
|
+
variables: Schema.optional(VariableScopesSchema),
|
|
95
|
+
rulesets: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
96
|
+
title: "Rulesets",
|
|
97
|
+
description: "Names of rulesets to apply to these repos",
|
|
98
|
+
examples: [["workflow", "release"]]
|
|
99
|
+
})),
|
|
100
|
+
security: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
101
|
+
title: "Security groups",
|
|
102
|
+
description: "Names of security groups (vulnerability alerts, automated security fixes, private vulnerability reporting) to apply to these repos",
|
|
103
|
+
examples: [["oss-defaults"]]
|
|
104
|
+
})),
|
|
105
|
+
code_scanning: Schema.optional(Schema.Array(Schema.String).annotations({
|
|
106
|
+
title: "Code scanning groups",
|
|
107
|
+
description: "Names of code_scanning groups (CodeQL default setup) to apply to these repos",
|
|
108
|
+
examples: [["oss-defaults"]]
|
|
109
|
+
})),
|
|
110
|
+
cleanup: Schema.optional(CleanupSchema)
|
|
111
|
+
}).annotations({
|
|
112
|
+
identifier: "Group",
|
|
113
|
+
title: "Repository group",
|
|
114
|
+
description: "A named group of repositories with their resource assignments",
|
|
115
|
+
jsonSchema: {
|
|
116
|
+
...tombi({ tableKeysOrder: "schema" }),
|
|
117
|
+
...taplo({
|
|
118
|
+
initKeys: ["repos"],
|
|
119
|
+
links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/configuration.md" }
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
const SquashMergeCommitTitleSchema = Schema.Literal("PR_TITLE", "COMMIT_OR_PR_TITLE").annotations({
|
|
124
|
+
title: "Squash merge commit title",
|
|
125
|
+
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"
|
|
126
|
+
});
|
|
127
|
+
const SquashMergeCommitMessageSchema = Schema.Literal("PR_BODY", "COMMIT_MESSAGES", "BLANK").annotations({
|
|
128
|
+
title: "Squash merge commit message",
|
|
129
|
+
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"
|
|
130
|
+
});
|
|
131
|
+
const MergeCommitTitleSchema = Schema.Literal("PR_TITLE", "MERGE_MESSAGE").annotations({
|
|
132
|
+
title: "Merge commit title",
|
|
133
|
+
description: "Default title for merge commits: PR_TITLE uses the pull request title, MERGE_MESSAGE uses the classic merge message"
|
|
134
|
+
});
|
|
135
|
+
const MergeCommitMessageSchema = Schema.Literal("PR_BODY", "PR_TITLE", "BLANK").annotations({
|
|
136
|
+
title: "Merge commit message",
|
|
137
|
+
description: "Default message body for merge commits: PR_BODY uses the pull request body, PR_TITLE uses the PR title, BLANK leaves it empty"
|
|
138
|
+
});
|
|
139
|
+
const SecurityAndAnalysisStatusSchema = Schema.Literal("enabled", "disabled").annotations({
|
|
140
|
+
identifier: "SecurityAndAnalysisStatus",
|
|
141
|
+
title: "Security feature status",
|
|
142
|
+
description: "Whether the security feature is \"enabled\" or \"disabled\""
|
|
143
|
+
});
|
|
144
|
+
const DelegatedBypassReviewerModeSchema = Schema.Literal("ALWAYS", "EXEMPT").annotations({
|
|
145
|
+
identifier: "DelegatedBypassReviewerMode",
|
|
146
|
+
title: "Delegated bypass reviewer mode",
|
|
147
|
+
description: "ALWAYS: reviewer is always required to approve bypass; EXEMPT: reviewer can bypass without review"
|
|
148
|
+
});
|
|
149
|
+
const DelegatedBypassReviewerSchema = Schema.Union(Schema.Struct({
|
|
150
|
+
team: Schema.String.annotations({
|
|
151
|
+
title: "Team slug",
|
|
152
|
+
description: "GitHub team slug (e.g., \"security-team\"); resolved to numeric reviewer_id at sync time",
|
|
153
|
+
examples: ["security-team"]
|
|
154
|
+
}),
|
|
155
|
+
mode: Schema.optional(DelegatedBypassReviewerModeSchema)
|
|
156
|
+
}), Schema.Struct({
|
|
157
|
+
role: Schema.String.annotations({
|
|
158
|
+
title: "Organization role name",
|
|
159
|
+
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.",
|
|
160
|
+
examples: [
|
|
161
|
+
"all_repo_admin",
|
|
162
|
+
"all_repo_maintain",
|
|
163
|
+
"security_manager"
|
|
164
|
+
]
|
|
165
|
+
}),
|
|
166
|
+
mode: Schema.optional(DelegatedBypassReviewerModeSchema)
|
|
167
|
+
})).annotations({
|
|
168
|
+
identifier: "DelegatedBypassReviewer",
|
|
169
|
+
title: "Delegated bypass reviewer",
|
|
170
|
+
description: "A reviewer who can approve secret-scanning push-protection bypass requests. Must specify exactly one of team or role."
|
|
171
|
+
});
|
|
172
|
+
const SecurityAndAnalysisSchema = Schema.Struct({
|
|
173
|
+
advanced_security: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
174
|
+
title: "GitHub Advanced Security",
|
|
175
|
+
description: "(GHAS-licensed) Master toggle for GitHub Advanced Security features. Free on public repos; requires a GHAS license on private repos."
|
|
176
|
+
})),
|
|
177
|
+
code_security: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
178
|
+
title: "GitHub Code Security",
|
|
179
|
+
description: "(GHAS-licensed) Toggle GitHub Code Security functionality."
|
|
180
|
+
})),
|
|
181
|
+
secret_scanning: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
182
|
+
title: "Secret scanning",
|
|
183
|
+
description: "Detect exposed credentials and sensitive data committed to the repository."
|
|
184
|
+
})),
|
|
185
|
+
secret_scanning_push_protection: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
186
|
+
title: "Secret scanning push protection",
|
|
187
|
+
description: "Block git pushes that contain detected secrets."
|
|
188
|
+
})),
|
|
189
|
+
secret_scanning_ai_detection: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
190
|
+
title: "Secret scanning AI detection",
|
|
191
|
+
description: "(GHAS-licensed) AI-powered detection of generic secrets beyond standard provider patterns."
|
|
192
|
+
})),
|
|
193
|
+
secret_scanning_non_provider_patterns: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
194
|
+
title: "Secret scanning non-provider patterns",
|
|
195
|
+
description: "(GHAS-licensed) Detect custom secret patterns beyond the standard provider list."
|
|
196
|
+
})),
|
|
197
|
+
secret_scanning_delegated_alert_dismissal: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
198
|
+
title: "Delegated alert dismissal",
|
|
199
|
+
description: "(org-only) Allow delegated dismissal of secret scanning alerts."
|
|
200
|
+
})),
|
|
201
|
+
secret_scanning_delegated_bypass: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
202
|
+
title: "Delegated push protection bypass",
|
|
203
|
+
description: "(org-only) Allow delegated approval of secret scanning push protection bypass requests."
|
|
204
|
+
})),
|
|
205
|
+
delegated_bypass_reviewers: Schema.optional(Schema.Array(DelegatedBypassReviewerSchema).annotations({
|
|
206
|
+
title: "Delegated bypass reviewers",
|
|
207
|
+
description: "(org-only) Reviewers authorized to approve push protection bypass requests. Each entry must specify a team slug or role name."
|
|
208
|
+
})),
|
|
209
|
+
dependabot_security_updates: Schema.optional(SecurityAndAnalysisStatusSchema.annotations({
|
|
210
|
+
title: "Dependabot security updates",
|
|
211
|
+
description: "Automatically open pull requests to patch known dependency vulnerabilities."
|
|
212
|
+
}))
|
|
213
|
+
}).annotations({
|
|
214
|
+
identifier: "SecurityAndAnalysis",
|
|
215
|
+
title: "Security and analysis",
|
|
216
|
+
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.",
|
|
217
|
+
jsonSchema: {
|
|
218
|
+
...tombi({ tableKeysOrder: "schema" }),
|
|
219
|
+
...taplo({ links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/configuration.md" } })
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
const SettingsGroupSchema = Schema.Struct({
|
|
223
|
+
is_template: Schema.optional(Schema.Boolean.annotations({
|
|
224
|
+
title: "Template repository",
|
|
225
|
+
description: "Whether the repository is a template that can be used to generate new repositories"
|
|
226
|
+
})),
|
|
227
|
+
has_wiki: Schema.optional(Schema.Boolean.annotations({
|
|
228
|
+
title: "Wikis",
|
|
229
|
+
description: "Enable the wiki feature for the repository"
|
|
230
|
+
})),
|
|
231
|
+
has_issues: Schema.optional(Schema.Boolean.annotations({
|
|
232
|
+
title: "Issues",
|
|
233
|
+
description: "Enable the issues feature for the repository"
|
|
234
|
+
})),
|
|
235
|
+
has_projects: Schema.optional(Schema.Boolean.annotations({
|
|
236
|
+
title: "Projects",
|
|
237
|
+
description: "Enable the projects feature for the repository"
|
|
238
|
+
})),
|
|
239
|
+
has_discussions: Schema.optional(Schema.Boolean.annotations({
|
|
240
|
+
title: "Discussions",
|
|
241
|
+
description: "Enable the discussions feature for the repository"
|
|
242
|
+
})),
|
|
243
|
+
has_sponsorships: Schema.optional(Schema.Boolean.annotations({
|
|
244
|
+
title: "Sponsorships",
|
|
245
|
+
description: "Display a Sponsor button for the repository (synced via GraphQL)"
|
|
246
|
+
})),
|
|
247
|
+
has_pull_requests: Schema.optional(Schema.Boolean.annotations({
|
|
248
|
+
title: "Pull requests",
|
|
249
|
+
description: "Enable the pull requests feature for the repository (synced via GraphQL)"
|
|
250
|
+
})),
|
|
251
|
+
allow_forking: Schema.optional(Schema.Boolean.annotations({
|
|
252
|
+
title: "Allow forking",
|
|
253
|
+
description: "Allow forking of the repository"
|
|
254
|
+
})),
|
|
255
|
+
allow_merge_commit: Schema.optional(Schema.Boolean.annotations({
|
|
256
|
+
title: "Allow merge commits",
|
|
257
|
+
description: "Allow merge commits when merging pull requests"
|
|
258
|
+
})),
|
|
259
|
+
allow_squash_merge: Schema.optional(Schema.Boolean.annotations({
|
|
260
|
+
title: "Allow squash merging",
|
|
261
|
+
description: "Allow squash merging when merging pull requests"
|
|
262
|
+
})),
|
|
263
|
+
allow_rebase_merge: Schema.optional(Schema.Boolean.annotations({
|
|
264
|
+
title: "Allow rebase merging",
|
|
265
|
+
description: "Allow rebase merging when merging pull requests"
|
|
266
|
+
})),
|
|
267
|
+
allow_auto_merge: Schema.optional(Schema.Boolean.annotations({
|
|
268
|
+
title: "Allow auto-merge",
|
|
269
|
+
description: "Allow pull requests to be automatically merged once all requirements are met"
|
|
270
|
+
})),
|
|
271
|
+
allow_update_branch: Schema.optional(Schema.Boolean.annotations({
|
|
272
|
+
title: "Always suggest updating pull request branches",
|
|
273
|
+
description: "Show the update branch button on pull requests"
|
|
274
|
+
})),
|
|
275
|
+
squash_merge_commit_title: Schema.optional(SquashMergeCommitTitleSchema),
|
|
276
|
+
squash_merge_commit_message: Schema.optional(SquashMergeCommitMessageSchema),
|
|
277
|
+
merge_commit_title: Schema.optional(MergeCommitTitleSchema),
|
|
278
|
+
merge_commit_message: Schema.optional(MergeCommitMessageSchema),
|
|
279
|
+
delete_branch_on_merge: Schema.optional(Schema.Boolean.annotations({
|
|
280
|
+
title: "Automatically delete head branches",
|
|
281
|
+
description: "Automatically delete head branches after pull requests are merged"
|
|
282
|
+
})),
|
|
283
|
+
web_commit_signoff_required: Schema.optional(Schema.Boolean.annotations({
|
|
284
|
+
title: "Require commit signoff",
|
|
285
|
+
description: "Require contributors to sign off on web-based commits"
|
|
286
|
+
})),
|
|
287
|
+
security_and_analysis: Schema.optional(SecurityAndAnalysisSchema)
|
|
288
|
+
}, {
|
|
289
|
+
key: Schema.String,
|
|
290
|
+
value: Jsonifiable
|
|
291
|
+
}).annotations({
|
|
292
|
+
identifier: "SettingsGroup",
|
|
293
|
+
title: "Settings group",
|
|
294
|
+
description: "GitHub repository settings to apply. Known fields are typed; additional fields are passed through to the API.",
|
|
295
|
+
jsonSchema: {
|
|
296
|
+
...tombi({ tableKeysOrder: "schema" }),
|
|
297
|
+
...taplo({ links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/configuration.md" } })
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
const SecurityGroupSchema = Schema.Struct({
|
|
301
|
+
vulnerability_alerts: Schema.optional(Schema.Boolean.annotations({
|
|
302
|
+
title: "Vulnerability alerts",
|
|
303
|
+
description: "Enable Dependabot vulnerability alerts (PUT/DELETE /repos/{o}/{r}/vulnerability-alerts)."
|
|
304
|
+
})),
|
|
305
|
+
automated_security_fixes: Schema.optional(Schema.Boolean.annotations({
|
|
306
|
+
title: "Automated security fixes",
|
|
307
|
+
description: "Enable Dependabot security pull requests (PUT/DELETE /repos/{o}/{r}/automated-security-fixes). Requires vulnerability_alerts to also be enabled."
|
|
308
|
+
})),
|
|
309
|
+
private_vulnerability_reporting: Schema.optional(Schema.Boolean.annotations({
|
|
310
|
+
title: "Private vulnerability reporting",
|
|
311
|
+
description: "Enable the private vulnerability reporting inbox (PUT/DELETE /repos/{o}/{r}/private-vulnerability-reporting)."
|
|
312
|
+
}))
|
|
313
|
+
}).pipe(Schema.filter((group) => !(group.automated_security_fixes === true && group.vulnerability_alerts === false), {
|
|
314
|
+
identifier: "SecurityGroup",
|
|
315
|
+
message: () => "automated_security_fixes = true requires vulnerability_alerts to be enabled (or omitted to leave the existing setting in place)"
|
|
316
|
+
})).annotations({
|
|
317
|
+
identifier: "SecurityGroup",
|
|
318
|
+
title: "Security group",
|
|
319
|
+
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.",
|
|
320
|
+
jsonSchema: {
|
|
321
|
+
...tombi({ tableKeysOrder: "schema" }),
|
|
322
|
+
...taplo({ links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/configuration.md" } })
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
const CodeScanningLanguageSchema = Schema.Literal("actions", "c-cpp", "csharp", "go", "java-kotlin", "javascript-typescript", "python", "ruby", "swift").annotations({
|
|
326
|
+
identifier: "CodeScanningLanguage",
|
|
327
|
+
title: "CodeQL default-setup language",
|
|
328
|
+
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)."
|
|
329
|
+
});
|
|
330
|
+
const CodeScanningStateSchema = Schema.Literal("configured", "not-configured").annotations({
|
|
331
|
+
identifier: "CodeScanningState",
|
|
332
|
+
title: "Default setup state",
|
|
333
|
+
description: "\"configured\" enables CodeQL default setup; \"not-configured\" disables it."
|
|
334
|
+
});
|
|
335
|
+
const CodeScanningQuerySuiteSchema = Schema.Literal("default", "extended").annotations({
|
|
336
|
+
identifier: "CodeScanningQuerySuite",
|
|
337
|
+
title: "Query suite",
|
|
338
|
+
description: "\"default\" runs the standard query set; \"extended\" includes additional security queries."
|
|
339
|
+
});
|
|
340
|
+
const CodeScanningThreatModelSchema = Schema.Literal("remote", "remote_and_local").annotations({
|
|
341
|
+
identifier: "CodeScanningThreatModel",
|
|
342
|
+
title: "Threat model",
|
|
343
|
+
description: "\"remote\" analyzes network sources only; \"remote_and_local\" also includes filesystem and environment access."
|
|
344
|
+
});
|
|
345
|
+
const CodeScanningRunnerTypeSchema = Schema.Literal("standard", "labeled").annotations({
|
|
346
|
+
identifier: "CodeScanningRunnerType",
|
|
347
|
+
title: "Runner type",
|
|
348
|
+
description: "\"standard\" uses GitHub-hosted runners; \"labeled\" uses runners matching runner_label."
|
|
349
|
+
});
|
|
350
|
+
const CodeScanningGroupSchema = Schema.Struct({
|
|
351
|
+
state: Schema.optional(CodeScanningStateSchema),
|
|
352
|
+
languages: Schema.optional(Schema.Array(CodeScanningLanguageSchema).annotations({
|
|
353
|
+
title: "Languages",
|
|
354
|
+
description: "CodeQL languages to analyze. Languages not detected in the repository are skipped with a warning at sync time.",
|
|
355
|
+
examples: [["javascript-typescript", "python"]]
|
|
356
|
+
})),
|
|
357
|
+
query_suite: Schema.optional(CodeScanningQuerySuiteSchema),
|
|
358
|
+
threat_model: Schema.optional(CodeScanningThreatModelSchema),
|
|
359
|
+
runner_type: Schema.optional(CodeScanningRunnerTypeSchema),
|
|
360
|
+
runner_label: Schema.optional(Schema.String.annotations({
|
|
361
|
+
title: "Runner label",
|
|
362
|
+
description: "Self-hosted runner label. Required when runner_type = \"labeled\"."
|
|
363
|
+
}))
|
|
364
|
+
}).pipe(Schema.filter((group) => group.runner_type !== "labeled" || group.runner_label !== void 0, {
|
|
365
|
+
identifier: "CodeScanningGroup",
|
|
366
|
+
message: () => "runner_label is required when runner_type = \"labeled\""
|
|
367
|
+
})).annotations({
|
|
368
|
+
identifier: "CodeScanningGroup",
|
|
369
|
+
title: "Code scanning group",
|
|
370
|
+
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.",
|
|
371
|
+
jsonSchema: {
|
|
372
|
+
...tombi({ tableKeysOrder: "schema" }),
|
|
373
|
+
...taplo({ links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/configuration.md" } })
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
const LogLevelSchema = Schema.Literal("silent", "info", "verbose", "debug").annotations({
|
|
377
|
+
identifier: "LogLevel",
|
|
378
|
+
title: "Log level",
|
|
379
|
+
description: "Controls output verbosity: silent (none), info (summaries), verbose (per-operation), debug (with sources)"
|
|
380
|
+
});
|
|
381
|
+
const ConfigSchema = Schema.Struct({
|
|
382
|
+
owner: Schema.optional(Schema.String.annotations({
|
|
383
|
+
title: "Default owner",
|
|
384
|
+
description: "Default GitHub user or organization for all groups. Can be overridden per group.",
|
|
385
|
+
examples: ["spencerbeggs", "savvy-web"]
|
|
386
|
+
})),
|
|
387
|
+
log_level: Schema.optionalWith(LogLevelSchema, { default: () => "info" }).annotations({
|
|
388
|
+
title: "Log level",
|
|
389
|
+
description: "Default output verbosity. Can be overridden with --log-level CLI flag."
|
|
390
|
+
}),
|
|
391
|
+
settings: Schema.optionalWith(Schema.Record({
|
|
392
|
+
key: Schema.String,
|
|
393
|
+
value: SettingsGroupSchema
|
|
394
|
+
}).annotations({
|
|
395
|
+
title: "Settings groups",
|
|
396
|
+
description: "Named groups of GitHub repository settings to apply",
|
|
397
|
+
jsonSchema: tombi({ additionalKeyLabel: "setting_group" })
|
|
398
|
+
}), { default: () => ({}) }),
|
|
399
|
+
secrets: Schema.optionalWith(Schema.Record({
|
|
400
|
+
key: Schema.String,
|
|
401
|
+
value: SecretGroupSchema
|
|
402
|
+
}).annotations({
|
|
403
|
+
title: "Secret groups",
|
|
404
|
+
description: "Named groups of secrets. Each group is one kind: file, value, or resolved.",
|
|
405
|
+
jsonSchema: tombi({ additionalKeyLabel: "secret_group" })
|
|
406
|
+
}), { default: () => ({}) }),
|
|
407
|
+
variables: Schema.optionalWith(Schema.Record({
|
|
408
|
+
key: Schema.String,
|
|
409
|
+
value: VariableGroupSchema
|
|
410
|
+
}).annotations({
|
|
411
|
+
title: "Variable groups",
|
|
412
|
+
description: "Named groups of variables. Each group is one kind: file, value, or resolved.",
|
|
413
|
+
jsonSchema: tombi({ additionalKeyLabel: "variable_group" })
|
|
414
|
+
}), { default: () => ({}) }),
|
|
415
|
+
rulesets: Schema.optionalWith(Schema.Record({
|
|
416
|
+
key: Schema.String,
|
|
417
|
+
value: RulesetSchema
|
|
418
|
+
}).annotations({
|
|
419
|
+
title: "Rulesets",
|
|
420
|
+
description: "Named rulesets defining branch and tag protection rules",
|
|
421
|
+
jsonSchema: tombi({ additionalKeyLabel: "ruleset_name" })
|
|
422
|
+
}), { default: () => ({}) }),
|
|
423
|
+
environments: Schema.optionalWith(Schema.Record({
|
|
424
|
+
key: Schema.String,
|
|
425
|
+
value: EnvironmentSchema
|
|
426
|
+
}).annotations({
|
|
427
|
+
title: "Environments",
|
|
428
|
+
description: "Named deployment environment configurations",
|
|
429
|
+
jsonSchema: tombi({ additionalKeyLabel: "environment_name" })
|
|
430
|
+
}), { default: () => ({}) }),
|
|
431
|
+
security: Schema.optionalWith(Schema.Record({
|
|
432
|
+
key: Schema.String,
|
|
433
|
+
value: SecurityGroupSchema
|
|
434
|
+
}).annotations({
|
|
435
|
+
title: "Security groups",
|
|
436
|
+
description: "Named security groups for vulnerability alerts, automated security fixes, and private vulnerability reporting",
|
|
437
|
+
jsonSchema: tombi({ additionalKeyLabel: "security_group" })
|
|
438
|
+
}), { default: () => ({}) }),
|
|
439
|
+
code_scanning: Schema.optionalWith(Schema.Record({
|
|
440
|
+
key: Schema.String,
|
|
441
|
+
value: CodeScanningGroupSchema
|
|
442
|
+
}).annotations({
|
|
443
|
+
title: "Code scanning groups",
|
|
444
|
+
description: "Named code scanning groups for CodeQL default setup configuration",
|
|
445
|
+
jsonSchema: tombi({ additionalKeyLabel: "code_scanning_group" })
|
|
446
|
+
}), { default: () => ({}) }),
|
|
447
|
+
groups: Schema.Record({
|
|
448
|
+
key: Schema.String,
|
|
449
|
+
value: GroupSchema
|
|
450
|
+
}).annotations({
|
|
451
|
+
title: "Groups",
|
|
452
|
+
description: "Named groups of repositories with their settings, secrets, variables, rulesets, environments, security, and code scanning assignments",
|
|
453
|
+
jsonSchema: tombi({ additionalKeyLabel: "group_name" })
|
|
454
|
+
})
|
|
455
|
+
}).annotations({
|
|
456
|
+
identifier: "Config",
|
|
457
|
+
title: "reposets Configuration",
|
|
458
|
+
description: "Configuration for syncing GitHub repository settings, secrets, variables, rulesets, deployment environments, advanced security toggles, and CodeQL default setup",
|
|
459
|
+
jsonSchema: {
|
|
460
|
+
...tombi({ tableKeysOrder: "schema" }),
|
|
461
|
+
...taplo({
|
|
462
|
+
initKeys: ["owner", "groups"],
|
|
463
|
+
links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/configuration.md" }
|
|
464
|
+
})
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
//#endregion
|
|
469
|
+
export { ConfigSchema, GroupSchema, LogLevelSchema };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Jsonifiable, taplo, tombi } from "xdg-effect";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/schemas/credentials.ts
|
|
5
|
+
const ResolveSectionSchema = Schema.Struct({
|
|
6
|
+
op: Schema.optional(Schema.Record({
|
|
7
|
+
key: Schema.String,
|
|
8
|
+
value: Schema.String
|
|
9
|
+
}).annotations({
|
|
10
|
+
title: "1Password references",
|
|
11
|
+
description: "Named values resolved via 1Password SDK. Values are op:// reference strings.",
|
|
12
|
+
jsonSchema: tombi({ additionalKeyLabel: "label" })
|
|
13
|
+
})),
|
|
14
|
+
file: Schema.optional(Schema.Record({
|
|
15
|
+
key: Schema.String,
|
|
16
|
+
value: Schema.String
|
|
17
|
+
}).annotations({
|
|
18
|
+
title: "File references",
|
|
19
|
+
description: "Named values read from files. Values are file paths relative to the credentials directory.",
|
|
20
|
+
jsonSchema: tombi({ additionalKeyLabel: "label" })
|
|
21
|
+
})),
|
|
22
|
+
value: Schema.optional(Schema.Record({
|
|
23
|
+
key: Schema.String,
|
|
24
|
+
value: Schema.Union(Schema.String, Schema.Record({
|
|
25
|
+
key: Schema.String,
|
|
26
|
+
value: Jsonifiable
|
|
27
|
+
}))
|
|
28
|
+
}).annotations({
|
|
29
|
+
title: "Inline values",
|
|
30
|
+
description: "Named inline values. Strings are used as-is, objects are JSON-stringified.",
|
|
31
|
+
jsonSchema: tombi({ additionalKeyLabel: "label" })
|
|
32
|
+
}))
|
|
33
|
+
}).annotations({
|
|
34
|
+
identifier: "ResolveSection",
|
|
35
|
+
title: "Resolve section",
|
|
36
|
+
description: "Named values resolved from 1Password, files, or inline. Referenced by resolved entries in secret and variable groups.",
|
|
37
|
+
jsonSchema: taplo({ links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/credentials.md" } })
|
|
38
|
+
});
|
|
39
|
+
const CredentialProfileSchema = Schema.Struct({
|
|
40
|
+
github_token: Schema.String.annotations({
|
|
41
|
+
title: "GitHub token",
|
|
42
|
+
description: "A GitHub personal access token (fine-grained) with administration, secrets, variables, environments, and GPG keys permissions",
|
|
43
|
+
examples: ["ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]
|
|
44
|
+
}),
|
|
45
|
+
op_service_account_token: Schema.optional(Schema.String.annotations({
|
|
46
|
+
title: "1Password service account token",
|
|
47
|
+
description: "A 1Password service account token for resolving op:// secret references",
|
|
48
|
+
examples: ["ops_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]
|
|
49
|
+
})),
|
|
50
|
+
resolve: Schema.optional(ResolveSectionSchema)
|
|
51
|
+
}).annotations({
|
|
52
|
+
identifier: "CredentialProfile",
|
|
53
|
+
title: "Credential profile",
|
|
54
|
+
description: "Authentication credentials for a GitHub account with optional named values for secret and variable resolution",
|
|
55
|
+
jsonSchema: taplo({
|
|
56
|
+
initKeys: ["github_token"],
|
|
57
|
+
links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/credentials.md" }
|
|
58
|
+
})
|
|
59
|
+
});
|
|
60
|
+
const CredentialsSchema = Schema.Struct({ profiles: Schema.optionalWith(Schema.Record({
|
|
61
|
+
key: Schema.String,
|
|
62
|
+
value: CredentialProfileSchema
|
|
63
|
+
}).annotations({
|
|
64
|
+
title: "Credential profiles",
|
|
65
|
+
description: "Named credential profiles. If only one profile is defined, it is used automatically for all repo groups.",
|
|
66
|
+
jsonSchema: tombi({ additionalKeyLabel: "profile_name" })
|
|
67
|
+
}), { default: () => ({}) }) }).annotations({
|
|
68
|
+
identifier: "Credentials",
|
|
69
|
+
title: "reposets Credentials",
|
|
70
|
+
description: "Authentication profiles for reposets. This file should be gitignored.",
|
|
71
|
+
jsonSchema: taplo({
|
|
72
|
+
initKeys: ["profiles"],
|
|
73
|
+
links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/credentials.md" }
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
export { CredentialProfileSchema, CredentialsSchema, ResolveSectionSchema };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { taplo, tombi } from "xdg-effect";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/schemas/environment.ts
|
|
5
|
+
const ReviewerTypeSchema = Schema.Literal("User", "Team").annotations({
|
|
6
|
+
title: "Reviewer type",
|
|
7
|
+
description: "Whether the reviewer is an individual user or a team"
|
|
8
|
+
});
|
|
9
|
+
const ReviewerSchema = Schema.Struct({
|
|
10
|
+
type: ReviewerTypeSchema,
|
|
11
|
+
id: Schema.Int.annotations({
|
|
12
|
+
title: "Reviewer ID",
|
|
13
|
+
description: "The numeric GitHub ID of the user or team"
|
|
14
|
+
})
|
|
15
|
+
}).annotations({
|
|
16
|
+
identifier: "Reviewer",
|
|
17
|
+
title: "Reviewer",
|
|
18
|
+
description: "A user or team required to review deployments"
|
|
19
|
+
});
|
|
20
|
+
const DeploymentBranchPolicySchema = Schema.Struct({
|
|
21
|
+
name: Schema.String.annotations({
|
|
22
|
+
title: "Pattern",
|
|
23
|
+
description: "The name pattern (branch name, tag name, or glob) to allow deployments from"
|
|
24
|
+
}),
|
|
25
|
+
type: Schema.optionalWith(Schema.Literal("branch", "tag").annotations({
|
|
26
|
+
title: "Policy type",
|
|
27
|
+
description: "Whether this policy matches branches or tags. Defaults to \"branch\"."
|
|
28
|
+
}), { default: () => "branch" })
|
|
29
|
+
}).annotations({
|
|
30
|
+
identifier: "DeploymentBranchPolicy",
|
|
31
|
+
title: "Deployment branch policy",
|
|
32
|
+
description: "A custom branch or tag pattern that deployments are allowed from"
|
|
33
|
+
});
|
|
34
|
+
const DeploymentBranchesSchema = Schema.Union(Schema.Literal("all", "protected").annotations({
|
|
35
|
+
title: "Deployment branch preset",
|
|
36
|
+
description: "\"all\" allows any branch, \"protected\" allows only protected branches"
|
|
37
|
+
}), Schema.Array(DeploymentBranchPolicySchema).annotations({
|
|
38
|
+
title: "Custom deployment policies",
|
|
39
|
+
description: "Array of branch or tag name patterns allowed to deploy to this environment"
|
|
40
|
+
})).annotations({
|
|
41
|
+
identifier: "DeploymentBranches",
|
|
42
|
+
title: "Deployment branches",
|
|
43
|
+
description: "Controls which branches can deploy. Use \"all\", \"protected\", or a list of custom policies."
|
|
44
|
+
});
|
|
45
|
+
const EnvironmentSchema = Schema.Struct({
|
|
46
|
+
wait_timer: Schema.optional(Schema.Int.pipe(Schema.between(0, 43200)).annotations({
|
|
47
|
+
title: "Wait timer (minutes)",
|
|
48
|
+
description: "Number of minutes to wait before allowing deployments to proceed (0-43200)"
|
|
49
|
+
})),
|
|
50
|
+
prevent_self_review: Schema.optional(Schema.Boolean.annotations({
|
|
51
|
+
title: "Prevent self-review",
|
|
52
|
+
description: "Prevent the user who triggered the deployment from approving it"
|
|
53
|
+
})),
|
|
54
|
+
reviewers: Schema.optional(Schema.Array(ReviewerSchema).annotations({
|
|
55
|
+
title: "Required reviewers",
|
|
56
|
+
description: "Users or teams required to approve deployments to this environment"
|
|
57
|
+
})),
|
|
58
|
+
deployment_branches: Schema.optional(DeploymentBranchesSchema)
|
|
59
|
+
}).annotations({
|
|
60
|
+
identifier: "Environment",
|
|
61
|
+
title: "Deployment environment",
|
|
62
|
+
description: "Configuration for a GitHub deployment environment",
|
|
63
|
+
jsonSchema: {
|
|
64
|
+
...tombi({ tableKeysOrder: "schema" }),
|
|
65
|
+
...taplo({ links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/environments.md" } })
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
export { EnvironmentSchema };
|