reposets 0.3.0 → 0.4.1
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/500.js +667 -5
- package/README.md +11 -3
- package/bin/reposets.js +41 -0
- package/index.d.ts +153 -1
- package/package.json +6 -6
package/500.js
CHANGED
|
@@ -1037,6 +1037,24 @@ const GroupSchema = Schema.Struct({
|
|
|
1037
1037
|
]
|
|
1038
1038
|
]
|
|
1039
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
|
+
})),
|
|
1040
1058
|
cleanup: Schema.optional(CleanupSchema)
|
|
1041
1059
|
}).annotations({
|
|
1042
1060
|
identifier: "Group",
|
|
@@ -1072,6 +1090,97 @@ const MergeCommitMessageSchema = Schema.Literal("PR_BODY", "PR_TITLE", "BLANK").
|
|
|
1072
1090
|
title: "Merge commit message",
|
|
1073
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"
|
|
1074
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
|
+
});
|
|
1075
1184
|
const SettingsGroupSchema = Schema.Struct({
|
|
1076
1185
|
is_template: Schema.optional(Schema.Boolean.annotations({
|
|
1077
1186
|
title: "Template repository",
|
|
@@ -1136,7 +1245,8 @@ const SettingsGroupSchema = Schema.Struct({
|
|
|
1136
1245
|
web_commit_signoff_required: Schema.optional(Schema.Boolean.annotations({
|
|
1137
1246
|
title: "Require commit signoff",
|
|
1138
1247
|
description: "Require contributors to sign off on web-based commits"
|
|
1139
|
-
}))
|
|
1248
|
+
})),
|
|
1249
|
+
security_and_analysis: Schema.optional(SecurityAndAnalysisSchema)
|
|
1140
1250
|
}, {
|
|
1141
1251
|
key: Schema.String,
|
|
1142
1252
|
value: Jsonifiable
|
|
@@ -1155,6 +1265,99 @@ const SettingsGroupSchema = Schema.Struct({
|
|
|
1155
1265
|
})
|
|
1156
1266
|
}
|
|
1157
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
|
+
});
|
|
1158
1361
|
const LogLevelSchema = Schema.Literal("silent", "info", "verbose", "debug").annotations({
|
|
1159
1362
|
identifier: "LogLevel",
|
|
1160
1363
|
title: "Log level",
|
|
@@ -1235,12 +1438,36 @@ const ConfigSchema = Schema.Struct({
|
|
|
1235
1438
|
}), {
|
|
1236
1439
|
default: ()=>({})
|
|
1237
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
|
+
}),
|
|
1238
1465
|
groups: Schema.Record({
|
|
1239
1466
|
key: Schema.String,
|
|
1240
1467
|
value: GroupSchema
|
|
1241
1468
|
}).annotations({
|
|
1242
1469
|
title: "Groups",
|
|
1243
|
-
description: "Named groups of repositories with their settings, secrets, variables, rulesets, and
|
|
1470
|
+
description: "Named groups of repositories with their settings, secrets, variables, rulesets, environments, security, and code scanning assignments",
|
|
1244
1471
|
jsonSchema: tombi({
|
|
1245
1472
|
additionalKeyLabel: "group_name"
|
|
1246
1473
|
})
|
|
@@ -1248,7 +1475,7 @@ const ConfigSchema = Schema.Struct({
|
|
|
1248
1475
|
}).annotations({
|
|
1249
1476
|
identifier: "Config",
|
|
1250
1477
|
title: "reposets Configuration",
|
|
1251
|
-
description: "Configuration for syncing GitHub repository settings, secrets, variables, rulesets, and
|
|
1478
|
+
description: "Configuration for syncing GitHub repository settings, secrets, variables, rulesets, deployment environments, advanced security toggles, and CodeQL default setup",
|
|
1252
1479
|
jsonSchema: {
|
|
1253
1480
|
...tombi({
|
|
1254
1481
|
tableKeysOrder: "schema"
|
|
@@ -1374,6 +1601,8 @@ function validateConfigRefs(config) {
|
|
|
1374
1601
|
const definedVariables = new Set(Object.keys(config.variables));
|
|
1375
1602
|
const definedRulesets = new Set(Object.keys(config.rulesets));
|
|
1376
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));
|
|
1377
1606
|
for (const [groupName, group] of Object.entries(config.groups)){
|
|
1378
1607
|
if (group.settings) {
|
|
1379
1608
|
for (const ref of group.settings)if (!definedSettings.has(ref)) errors.push(`group '${groupName}': unknown settings group '${ref}'`);
|
|
@@ -1384,6 +1613,12 @@ function validateConfigRefs(config) {
|
|
|
1384
1613
|
if (group.environments) {
|
|
1385
1614
|
for (const ref of group.environments)if (!definedEnvironments.has(ref)) errors.push(`group '${groupName}': unknown environment '${ref}'`);
|
|
1386
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
|
+
}
|
|
1387
1622
|
if (group.secrets) {
|
|
1388
1623
|
if (group.secrets.actions) {
|
|
1389
1624
|
for (const ref of group.secrets.actions)if (!definedSecrets.has(ref)) errors.push(`group '${groupName}': unknown secrets group '${ref}'`);
|
|
@@ -1537,6 +1772,31 @@ class GitHubClient extends Context.Tag("GitHubClient")() {
|
|
|
1537
1772
|
const ORG_ONLY_SETTINGS = new Set([
|
|
1538
1773
|
"allow_forking"
|
|
1539
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
|
+
}
|
|
1540
1800
|
const GRAPHQL_SETTINGS = {
|
|
1541
1801
|
has_sponsorships: "hasSponsorshipsEnabled",
|
|
1542
1802
|
has_pull_requests: "hasPullRequestsEnabled"
|
|
@@ -1563,6 +1823,8 @@ function GitHubClientLive(token) {
|
|
|
1563
1823
|
});
|
|
1564
1824
|
}
|
|
1565
1825
|
const ownerTypeCache = new Map();
|
|
1826
|
+
const teamIdCache = new Map();
|
|
1827
|
+
const roleIdCache = new Map();
|
|
1566
1828
|
return {
|
|
1567
1829
|
getOwnerType (owner) {
|
|
1568
1830
|
return Effect.tryPromise({
|
|
@@ -1656,6 +1918,11 @@ function GitHubClientLive(token) {
|
|
|
1656
1918
|
const restSettings = {};
|
|
1657
1919
|
const graphqlInput = {};
|
|
1658
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
|
+
}
|
|
1659
1926
|
const graphqlField = GRAPHQL_SETTINGS[key];
|
|
1660
1927
|
if (void 0 !== graphqlField) graphqlInput[graphqlField] = value;
|
|
1661
1928
|
else restSettings[key] = value;
|
|
@@ -1982,6 +2249,160 @@ function GitHubClientLive(token) {
|
|
|
1982
2249
|
},
|
|
1983
2250
|
catch: wrapError
|
|
1984
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
|
+
});
|
|
1985
2406
|
}
|
|
1986
2407
|
};
|
|
1987
2408
|
})());
|
|
@@ -2158,6 +2579,82 @@ function GitHubClientTest() {
|
|
|
2158
2579
|
}
|
|
2159
2580
|
});
|
|
2160
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);
|
|
2161
2658
|
}
|
|
2162
2659
|
});
|
|
2163
2660
|
return {
|
|
@@ -2172,6 +2669,8 @@ class SyncLogger extends Context.Tag("SyncLogger")() {
|
|
|
2172
2669
|
function pluralize(resource, count) {
|
|
2173
2670
|
if (1 === count) return resource;
|
|
2174
2671
|
if ("ruleset" === resource) return "rulesets";
|
|
2672
|
+
if ("security feature" === resource) return "security features";
|
|
2673
|
+
if ("code scanning" === resource) return "code scanning";
|
|
2175
2674
|
return `${resource}s`;
|
|
2176
2675
|
}
|
|
2177
2676
|
function SyncLoggerLive(config) {
|
|
@@ -2278,6 +2777,24 @@ function SyncLoggerLive(config) {
|
|
|
2278
2777
|
};
|
|
2279
2778
|
}));
|
|
2280
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
|
+
};
|
|
2281
2798
|
class SyncEngine extends Context.Tag("SyncEngine")() {
|
|
2282
2799
|
}
|
|
2283
2800
|
function isCleanupActive(scope) {
|
|
@@ -2335,6 +2852,29 @@ function groupEntryNames(group) {
|
|
|
2335
2852
|
if ("resolved" in group) return Object.keys(group.resolved);
|
|
2336
2853
|
return [];
|
|
2337
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
|
+
}
|
|
2338
2878
|
const SyncEngineLive = Layer.effect(SyncEngine, Effect.gen(function*() {
|
|
2339
2879
|
const github = yield* GitHubClient;
|
|
2340
2880
|
const credResolver = yield* CredentialResolver;
|
|
@@ -2459,9 +2999,14 @@ const SyncEngineLive = Layer.effect(SyncEngine, Effect.gen(function*() {
|
|
|
2459
2999
|
const settingGroupRefs = group.settings ?? [];
|
|
2460
3000
|
const mergedSettings = {};
|
|
2461
3001
|
const skippedSettings = [];
|
|
3002
|
+
const saaBlocks = [];
|
|
2462
3003
|
for (const ref of settingGroupRefs){
|
|
2463
3004
|
const settingGroup = config.settings[ref];
|
|
2464
|
-
if (settingGroup)
|
|
3005
|
+
if (settingGroup) {
|
|
3006
|
+
const { security_and_analysis, ...rest } = settingGroup;
|
|
3007
|
+
Object.assign(mergedSettings, rest);
|
|
3008
|
+
saaBlocks.push(security_and_analysis);
|
|
3009
|
+
}
|
|
2465
3010
|
}
|
|
2466
3011
|
if ("User" === ownerType) {
|
|
2467
3012
|
for (const key of ORG_ONLY_SETTINGS)if (key in mergedSettings) {
|
|
@@ -2469,6 +3014,58 @@ const SyncEngineLive = Layer.effect(SyncEngine, Effect.gen(function*() {
|
|
|
2469
3014
|
skippedSettings.push(key);
|
|
2470
3015
|
}
|
|
2471
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;
|
|
2472
3069
|
const hasSecrets = secretScopes.some((s)=>(resolvedSecrets.get(s)?.size ?? 0) > 0);
|
|
2473
3070
|
const hasVariables = resolvedVariables.size > 0;
|
|
2474
3071
|
const hasRulesets = rulesetMap.size > 0;
|
|
@@ -2478,7 +3075,7 @@ const SyncEngineLive = Layer.effect(SyncEngine, Effect.gen(function*() {
|
|
|
2478
3075
|
const hasEnvVariables = resolvedEnvVariables.size > 0;
|
|
2479
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));
|
|
2480
3077
|
for (const repoName of group.repos)if (!repoFilter || repoName === repoFilter) {
|
|
2481
|
-
if (!hasSecrets && !hasVariables && !hasRulesets && !hasSettings && !hasEnvironments && !hasEnvSecrets && !hasEnvVariables && !hasCleanup) {
|
|
3078
|
+
if (!hasSecrets && !hasVariables && !hasRulesets && !hasSettings && !hasEnvironments && !hasEnvSecrets && !hasEnvVariables && !hasSecurity && !hasCodeScanning && !hasCleanup) {
|
|
2482
3079
|
yield* logger.repoSkip(owner, repoName, "no changes configured");
|
|
2483
3080
|
continue;
|
|
2484
3081
|
}
|
|
@@ -2489,6 +3086,66 @@ const SyncEngineLive = Layer.effect(SyncEngine, Effect.gen(function*() {
|
|
|
2489
3086
|
yield* github.syncSettings(owner, repoName, mergedSettings).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError("settings", err.message)));
|
|
2490
3087
|
}
|
|
2491
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
|
+
}
|
|
2492
3149
|
for (const envName of envRefs){
|
|
2493
3150
|
const envConfig = config.environments[envName];
|
|
2494
3151
|
if (envConfig) {
|
|
@@ -2520,6 +3177,11 @@ const SyncEngineLive = Layer.effect(SyncEngine, Effect.gen(function*() {
|
|
|
2520
3177
|
yield* github.syncRuleset(owner, repoName, ruleset.name, ruleset).pipe(Effect.catchTag("GitHubApiError", (err)=>logger.syncError(`ruleset ${ruleset.name}`, err.message)));
|
|
2521
3178
|
}
|
|
2522
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");
|
|
2523
3185
|
if (hasEnvironments) yield* logger.syncSummary("environment", envRefs.length, "");
|
|
2524
3186
|
if (hasSecrets) {
|
|
2525
3187
|
const scopeCounts = [];
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://www.typescriptlang.org/)
|
|
6
6
|
|
|
7
|
-
Declarative GitHub repository management. Define your repo settings, secrets, variables, rulesets,
|
|
7
|
+
Declarative GitHub repository management. Define your repo settings, secrets, variables, rulesets, deployment environments, advanced security toggles, and CodeQL default setup in a TOML config file, then apply them across all your repositories with a single command.
|
|
8
8
|
|
|
9
9
|
## Why reposets
|
|
10
10
|
|
|
@@ -17,7 +17,8 @@ Managing repository settings by hand doesn't scale. When you have dozens of repo
|
|
|
17
17
|
- **Multi-scope secret and variable management** — Assign the same secret group to Actions, Dependabot, Codespaces, and deployment environments with scoped targeting.
|
|
18
18
|
- **Ruleset shorthand syntax** — Define branch and tag rulesets with compact inline syntax for pull request rules, status checks, and boolean flags instead of verbose API payloads.
|
|
19
19
|
- **Deployment environment management** — Configure wait timers, reviewers, and branch policies for deployment environments alongside your other settings.
|
|
20
|
-
- **
|
|
20
|
+
- **Advanced security and CodeQL** — Toggle secret scanning, push protection, vulnerability alerts, automated security fixes, private vulnerability reporting, and CodeQL default setup. License- and ownership-aware: GHAS-licensed fields warn instead of failing on private repos without a license, and org-only fields are silently skipped on personal accounts.
|
|
21
|
+
- **Group-based targeting** — Organize repos into groups that share settings, secrets, variables, rulesets, environments, security toggles, and code scanning configuration. Change the group config, sync once, and every repo updates.
|
|
21
22
|
- **Cleanup policies** — Automatically remove undeclared resources per scope with optional preserve lists, so your repos converge to the declared state.
|
|
22
23
|
- **Dry-run and validation** — Preview changes before applying, validate config locally without touching the GitHub API, and catch typos with built-in diagnostics.
|
|
23
24
|
|
|
@@ -93,7 +94,7 @@ All commands accept `--log-level silent|info|verbose|debug`.
|
|
|
93
94
|
|
|
94
95
|
reposets uses two TOML files:
|
|
95
96
|
|
|
96
|
-
- `reposets.config.toml` — defines settings, secrets, variables, rulesets, environments, and groups
|
|
97
|
+
- `reposets.config.toml` — defines settings, secrets, variables, rulesets, environments, security, code_scanning, and groups
|
|
97
98
|
- `reposets.credentials.toml` — stores GitHub tokens and optional resolve sections for named values
|
|
98
99
|
|
|
99
100
|
Config lookup order (first match wins):
|
|
@@ -112,8 +113,14 @@ reposets requires a fine-grained personal access token with:
|
|
|
112
113
|
- Repository > Secrets (Read and write)
|
|
113
114
|
- Repository > Variables (Read and write)
|
|
114
115
|
- Repository > Environments (Read and write)
|
|
116
|
+
- Repository > Code scanning alerts (Read and write) — for `[code_scanning.*]`
|
|
117
|
+
- Repository > Dependabot alerts (Read and write) — for `[security.*]`
|
|
118
|
+
- Repository > Secret scanning alerts (Read and write) — for `security_and_analysis`
|
|
119
|
+
- Organization > Members (Read) — for `delegated_bypass_reviewers` team slugs on org-owned repos
|
|
115
120
|
- Account > GPG keys (Read and write)
|
|
116
121
|
|
|
122
|
+
The four security-related scopes are only required if you use the corresponding config sections.
|
|
123
|
+
|
|
117
124
|
## Documentation
|
|
118
125
|
|
|
119
126
|
Full reference guides are available in the [`docs/`](https://github.com/spencerbeggs/reposets/tree/main/docs) folder:
|
|
@@ -124,6 +131,7 @@ Full reference guides are available in the [`docs/`](https://github.com/spencerb
|
|
|
124
131
|
- [Secrets and Variables](https://github.com/spencerbeggs/reposets/blob/main/docs/secrets-and-variables.md) - resource groups, three kinds (file/value/resolved), and scoping
|
|
125
132
|
- [Rulesets](https://github.com/spencerbeggs/reposets/blob/main/docs/rulesets.md) - branch and tag ruleset configuration
|
|
126
133
|
- [Environments](https://github.com/spencerbeggs/reposets/blob/main/docs/environments.md) - deployment environment setup
|
|
134
|
+
- [Advanced Security](https://github.com/spencerbeggs/reposets/blob/main/docs/configuration.md#security-and-analysis-nested-block) - secret scanning, vulnerability alerts, automated security fixes, private vulnerability reporting, and CodeQL default setup
|
|
127
135
|
- [Cleanup](https://github.com/spencerbeggs/reposets/blob/main/docs/cleanup.md) - automatic cleanup of undeclared resources
|
|
128
136
|
- [Token Permissions](https://github.com/spencerbeggs/reposets/blob/main/docs/token-permissions.md) - GitHub PAT setup guide
|
|
129
137
|
|
package/bin/reposets.js
CHANGED
|
@@ -81,6 +81,8 @@ const KNOWN_CONFIG_KEYS = new Set([
|
|
|
81
81
|
"variables",
|
|
82
82
|
"rulesets",
|
|
83
83
|
"environments",
|
|
84
|
+
"security",
|
|
85
|
+
"code_scanning",
|
|
84
86
|
"groups"
|
|
85
87
|
]);
|
|
86
88
|
const KNOWN_GROUP_KEYS = new Set([
|
|
@@ -92,6 +94,8 @@ const KNOWN_GROUP_KEYS = new Set([
|
|
|
92
94
|
"variables",
|
|
93
95
|
"rulesets",
|
|
94
96
|
"environments",
|
|
97
|
+
"security",
|
|
98
|
+
"code_scanning",
|
|
95
99
|
"cleanup"
|
|
96
100
|
]);
|
|
97
101
|
const KNOWN_CLEANUP_KEYS = new Set([
|
|
@@ -206,7 +210,11 @@ const doctorCommand = Command.make("doctor", {
|
|
|
206
210
|
yield* Effect.log(" Repository permissions > Secrets (Read and write) -- Actions secrets");
|
|
207
211
|
yield* Effect.log(" Repository permissions > Variables (Read and write) -- Actions variables");
|
|
208
212
|
yield* Effect.log(" Repository permissions > Environments (Read and write) -- environment sync");
|
|
213
|
+
yield* Effect.log(" Repository permissions > Code scanning alerts (Read and write) -- code_scanning sync");
|
|
214
|
+
yield* Effect.log(" Repository permissions > Dependabot alerts (Read and write) -- security feature sync");
|
|
215
|
+
yield* Effect.log(" Repository permissions > Secret scanning alerts (Read and write) -- secret scanning delegation");
|
|
209
216
|
yield* Effect.log(" Account permissions > GPG keys (Read and write) -- secrets encryption key");
|
|
217
|
+
yield* Effect.log(" Organization permissions > Members (Read) -- resolve team slugs (org-level only)");
|
|
210
218
|
if (0 === warnings) yield* Effect.log("\nNo unknown keys detected.");
|
|
211
219
|
else yield* Effect.log(`\n${warnings} warning(s) found.`);
|
|
212
220
|
}).pipe(Effect.provide(makeConfigFilesLive(config)))).pipe(Command.withDescription("Deep config diagnostics with typo detection"));
|
|
@@ -256,6 +264,35 @@ const CONFIG_TEMPLATE = `# reposets configuration
|
|
|
256
264
|
# [[rulesets.default-branch.rules]]
|
|
257
265
|
# type = "deletion"
|
|
258
266
|
|
|
267
|
+
# --- Advanced security ---
|
|
268
|
+
# Nested inside a settings group (folded into the same PATCH /repos call).
|
|
269
|
+
# Some fields are GHAS-licensed and only work on public repos or
|
|
270
|
+
# private repos with a GHAS subscription. Org-only fields are silently
|
|
271
|
+
# skipped on personal accounts.
|
|
272
|
+
#
|
|
273
|
+
# [settings.defaults.security_and_analysis]
|
|
274
|
+
# secret_scanning = "enabled"
|
|
275
|
+
# secret_scanning_push_protection = "enabled"
|
|
276
|
+
# dependabot_security_updates = "enabled"
|
|
277
|
+
|
|
278
|
+
# --- Security feature toggles ---
|
|
279
|
+
# Dedicated PUT/DELETE endpoints; omit a key to leave it untouched.
|
|
280
|
+
#
|
|
281
|
+
# [security.oss-defaults]
|
|
282
|
+
# vulnerability_alerts = true
|
|
283
|
+
# automated_security_fixes = true
|
|
284
|
+
# private_vulnerability_reporting = true
|
|
285
|
+
|
|
286
|
+
# --- CodeQL default setup ---
|
|
287
|
+
# Applies via PATCH /repos/{o}/{r}/code-scanning/default-setup.
|
|
288
|
+
# Languages not detected in the repo are skipped with a warning.
|
|
289
|
+
#
|
|
290
|
+
# [code_scanning.oss-defaults]
|
|
291
|
+
# state = "configured"
|
|
292
|
+
# languages = ["javascript-typescript", "python"]
|
|
293
|
+
# query_suite = "extended"
|
|
294
|
+
# threat_model = "remote"
|
|
295
|
+
|
|
259
296
|
# --- Cleanup defaults ---
|
|
260
297
|
# [cleanup]
|
|
261
298
|
# secrets = false
|
|
@@ -269,6 +306,8 @@ const CONFIG_TEMPLATE = `# reposets configuration
|
|
|
269
306
|
# secrets = { actions = ["from-files", "from-creds"] }
|
|
270
307
|
# variables = { actions = ["turbo", "bot"] }
|
|
271
308
|
# rulesets = ["default-branch"]
|
|
309
|
+
# security = ["oss-defaults"]
|
|
310
|
+
# code_scanning = ["oss-defaults"]
|
|
272
311
|
`;
|
|
273
312
|
const CREDENTIALS_TEMPLATE = `# reposets credentials (keep this file private)
|
|
274
313
|
# See: https://github.com/spencerbeggs/reposets
|
|
@@ -356,6 +395,8 @@ const listCommand = Command.make("list", {
|
|
|
356
395
|
if (parts.length) yield* Effect.log(` variables: ${parts.join(", ")}`);
|
|
357
396
|
}
|
|
358
397
|
if (group.rulesets?.length) yield* Effect.log(` rulesets: ${group.rulesets.join(", ")}`);
|
|
398
|
+
if (group.security?.length) yield* Effect.log(` security: ${group.security.join(", ")}`);
|
|
399
|
+
if (group.code_scanning?.length) yield* Effect.log(` code_scanning: ${group.code_scanning.join(", ")}`);
|
|
359
400
|
if (group.credentials) yield* Effect.log(` credentials: ${group.credentials}`);
|
|
360
401
|
yield* Effect.log("");
|
|
361
402
|
}
|
package/index.d.ts
CHANGED
|
@@ -111,6 +111,24 @@ export declare const CleanupScopeSchema: Schema.Union<[typeof Schema.Boolean, Sc
|
|
|
111
111
|
preserve: Schema.Array$<typeof Schema.String>;
|
|
112
112
|
}>]>;
|
|
113
113
|
|
|
114
|
+
declare type CodeScanningGroup = typeof CodeScanningGroupSchema.Type;
|
|
115
|
+
|
|
116
|
+
declare const CodeScanningGroupSchema: Schema.refine<{
|
|
117
|
+
readonly state?: "configured" | "not-configured" | undefined;
|
|
118
|
+
readonly languages?: readonly ("actions" | "c-cpp" | "csharp" | "go" | "java-kotlin" | "javascript-typescript" | "python" | "ruby" | "swift")[] | undefined;
|
|
119
|
+
readonly query_suite?: "default" | "extended" | undefined;
|
|
120
|
+
readonly threat_model?: "remote" | "remote_and_local" | undefined;
|
|
121
|
+
readonly runner_type?: "labeled" | "standard" | undefined;
|
|
122
|
+
readonly runner_label?: string | undefined;
|
|
123
|
+
}, Schema.Struct<{
|
|
124
|
+
state: Schema.optional<Schema.Literal<["configured", "not-configured"]>>;
|
|
125
|
+
languages: Schema.optional<Schema.Array$<Schema.Literal<["actions", "c-cpp", "csharp", "go", "java-kotlin", "javascript-typescript", "python", "ruby", "swift"]>>>;
|
|
126
|
+
query_suite: Schema.optional<Schema.Literal<["default", "extended"]>>;
|
|
127
|
+
threat_model: Schema.optional<Schema.Literal<["remote", "remote_and_local"]>>;
|
|
128
|
+
runner_type: Schema.optional<Schema.Literal<["standard", "labeled"]>>;
|
|
129
|
+
runner_label: Schema.optional<Schema.SchemaClass<string, string, never>>;
|
|
130
|
+
}>>;
|
|
131
|
+
|
|
114
132
|
export declare type Config = typeof ConfigSchema.Type;
|
|
115
133
|
|
|
116
134
|
export declare const CONFIG_FILENAME = "reposets.config.toml";
|
|
@@ -148,6 +166,24 @@ export declare const ConfigSchema: Schema.Struct<{
|
|
|
148
166
|
merge_commit_message: Schema.optional<Schema.Literal<["PR_BODY", "PR_TITLE", "BLANK"]>>;
|
|
149
167
|
delete_branch_on_merge: Schema.optional<Schema.SchemaClass<boolean, boolean, never>>;
|
|
150
168
|
web_commit_signoff_required: Schema.optional<Schema.SchemaClass<boolean, boolean, never>>;
|
|
169
|
+
security_and_analysis: Schema.optional<Schema.Struct<{
|
|
170
|
+
advanced_security: Schema.optional<Schema.Literal<["enabled", "disabled"]>>;
|
|
171
|
+
code_security: Schema.optional<Schema.Literal<["enabled", "disabled"]>>;
|
|
172
|
+
secret_scanning: Schema.optional<Schema.Literal<["enabled", "disabled"]>>;
|
|
173
|
+
secret_scanning_push_protection: Schema.optional<Schema.Literal<["enabled", "disabled"]>>;
|
|
174
|
+
secret_scanning_ai_detection: Schema.optional<Schema.Literal<["enabled", "disabled"]>>;
|
|
175
|
+
secret_scanning_non_provider_patterns: Schema.optional<Schema.Literal<["enabled", "disabled"]>>;
|
|
176
|
+
secret_scanning_delegated_alert_dismissal: Schema.optional<Schema.Literal<["enabled", "disabled"]>>;
|
|
177
|
+
secret_scanning_delegated_bypass: Schema.optional<Schema.Literal<["enabled", "disabled"]>>;
|
|
178
|
+
delegated_bypass_reviewers: Schema.optional<Schema.Array$<Schema.Union<[Schema.Struct<{
|
|
179
|
+
team: Schema.SchemaClass<string, string, never>;
|
|
180
|
+
mode: Schema.optional<Schema.Literal<["ALWAYS", "EXEMPT"]>>;
|
|
181
|
+
}>, Schema.Struct<{
|
|
182
|
+
role: Schema.SchemaClass<string, string, never>;
|
|
183
|
+
mode: Schema.optional<Schema.Literal<["ALWAYS", "EXEMPT"]>>;
|
|
184
|
+
}>]>>>;
|
|
185
|
+
dependabot_security_updates: Schema.optional<Schema.Literal<["enabled", "disabled"]>>;
|
|
186
|
+
}>>;
|
|
151
187
|
}, readonly [{
|
|
152
188
|
readonly key: typeof Schema.String;
|
|
153
189
|
readonly value: Schema.SchemaClass<unknown, unknown, never>;
|
|
@@ -390,6 +426,34 @@ export declare const ConfigSchema: Schema.Struct<{
|
|
|
390
426
|
}>>, {
|
|
391
427
|
default: () => {};
|
|
392
428
|
}>;
|
|
429
|
+
security: Schema.optionalWith<Schema.Record$<typeof Schema.String, Schema.refine<{
|
|
430
|
+
readonly vulnerability_alerts?: boolean | undefined;
|
|
431
|
+
readonly automated_security_fixes?: boolean | undefined;
|
|
432
|
+
readonly private_vulnerability_reporting?: boolean | undefined;
|
|
433
|
+
}, Schema.Struct<{
|
|
434
|
+
vulnerability_alerts: Schema.optional<Schema.SchemaClass<boolean, boolean, never>>;
|
|
435
|
+
automated_security_fixes: Schema.optional<Schema.SchemaClass<boolean, boolean, never>>;
|
|
436
|
+
private_vulnerability_reporting: Schema.optional<Schema.SchemaClass<boolean, boolean, never>>;
|
|
437
|
+
}>>>, {
|
|
438
|
+
default: () => {};
|
|
439
|
+
}>;
|
|
440
|
+
code_scanning: Schema.optionalWith<Schema.Record$<typeof Schema.String, Schema.refine<{
|
|
441
|
+
readonly state?: "configured" | "not-configured" | undefined;
|
|
442
|
+
readonly languages?: readonly ("actions" | "c-cpp" | "csharp" | "go" | "java-kotlin" | "javascript-typescript" | "python" | "ruby" | "swift")[] | undefined;
|
|
443
|
+
readonly query_suite?: "default" | "extended" | undefined;
|
|
444
|
+
readonly threat_model?: "remote" | "remote_and_local" | undefined;
|
|
445
|
+
readonly runner_type?: "labeled" | "standard" | undefined;
|
|
446
|
+
readonly runner_label?: string | undefined;
|
|
447
|
+
}, Schema.Struct<{
|
|
448
|
+
state: Schema.optional<Schema.Literal<["configured", "not-configured"]>>;
|
|
449
|
+
languages: Schema.optional<Schema.Array$<Schema.Literal<["actions", "c-cpp", "csharp", "go", "java-kotlin", "javascript-typescript", "python", "ruby", "swift"]>>>;
|
|
450
|
+
query_suite: Schema.optional<Schema.Literal<["default", "extended"]>>;
|
|
451
|
+
threat_model: Schema.optional<Schema.Literal<["remote", "remote_and_local"]>>;
|
|
452
|
+
runner_type: Schema.optional<Schema.Literal<["standard", "labeled"]>>;
|
|
453
|
+
runner_label: Schema.optional<Schema.SchemaClass<string, string, never>>;
|
|
454
|
+
}>>>, {
|
|
455
|
+
default: () => {};
|
|
456
|
+
}>;
|
|
393
457
|
groups: Schema.Record$<typeof Schema.String, Schema.Struct<{
|
|
394
458
|
owner: Schema.optional<Schema.SchemaClass<string, string, never>>;
|
|
395
459
|
repos: Schema.Array$<typeof Schema.String>;
|
|
@@ -407,6 +471,8 @@ export declare const ConfigSchema: Schema.Struct<{
|
|
|
407
471
|
environments: Schema.optional<Schema.Record$<typeof Schema.String, Schema.Array$<typeof Schema.String>>>;
|
|
408
472
|
}>>;
|
|
409
473
|
rulesets: Schema.optional<Schema.Array$<typeof Schema.String>>;
|
|
474
|
+
security: Schema.optional<Schema.Array$<typeof Schema.String>>;
|
|
475
|
+
code_scanning: Schema.optional<Schema.Array$<typeof Schema.String>>;
|
|
410
476
|
cleanup: Schema.optional<Schema.Struct<{
|
|
411
477
|
secrets: Schema.optionalWith<Schema.Struct<{
|
|
412
478
|
actions: Schema.optionalWith<Schema.Union<[typeof Schema.Boolean, Schema.Struct<{
|
|
@@ -559,6 +625,16 @@ declare interface GitHubClientService {
|
|
|
559
625
|
readonly deleteEnvironment: (owner: string, repo: string, name: string) => Effect.Effect<void, GitHubApiError>;
|
|
560
626
|
readonly deleteEnvironmentSecret: (owner: string, repo: string, envName: string, name: string) => Effect.Effect<void, GitHubApiError>;
|
|
561
627
|
readonly deleteEnvironmentVariable: (owner: string, repo: string, envName: string, name: string) => Effect.Effect<void, GitHubApiError>;
|
|
628
|
+
readonly getVulnerabilityAlerts: (owner: string, repo: string) => Effect.Effect<boolean, GitHubApiError>;
|
|
629
|
+
readonly setVulnerabilityAlerts: (owner: string, repo: string, enabled: boolean) => Effect.Effect<void, GitHubApiError>;
|
|
630
|
+
readonly getAutomatedSecurityFixes: (owner: string, repo: string) => Effect.Effect<boolean, GitHubApiError>;
|
|
631
|
+
readonly setAutomatedSecurityFixes: (owner: string, repo: string, enabled: boolean) => Effect.Effect<void, GitHubApiError>;
|
|
632
|
+
readonly getPrivateVulnerabilityReporting: (owner: string, repo: string) => Effect.Effect<boolean, GitHubApiError>;
|
|
633
|
+
readonly setPrivateVulnerabilityReporting: (owner: string, repo: string, enabled: boolean) => Effect.Effect<void, GitHubApiError>;
|
|
634
|
+
readonly updateCodeScanningDefaultSetup: (owner: string, repo: string, config: CodeScanningGroup) => Effect.Effect<void, GitHubApiError>;
|
|
635
|
+
readonly listRepoLanguages: (owner: string, repo: string) => Effect.Effect<ReadonlyArray<string>, GitHubApiError>;
|
|
636
|
+
readonly resolveTeamId: (org: string, slug: string) => Effect.Effect<number, GitHubApiError>;
|
|
637
|
+
readonly resolveRoleId: (org: string, name: string) => Effect.Effect<number, GitHubApiError>;
|
|
562
638
|
}
|
|
563
639
|
|
|
564
640
|
export declare function GitHubClientTest(): {
|
|
@@ -585,6 +661,8 @@ export declare const GroupSchema: Schema.Struct<{
|
|
|
585
661
|
environments: Schema.optional<Schema.Record$<typeof Schema.String, Schema.Array$<typeof Schema.String>>>;
|
|
586
662
|
}>>;
|
|
587
663
|
rulesets: Schema.optional<Schema.Array$<typeof Schema.String>>;
|
|
664
|
+
security: Schema.optional<Schema.Array$<typeof Schema.String>>;
|
|
665
|
+
code_scanning: Schema.optional<Schema.Array$<typeof Schema.String>>;
|
|
588
666
|
cleanup: Schema.optional<Schema.Struct<{
|
|
589
667
|
secrets: Schema.optionalWith<Schema.Struct<{
|
|
590
668
|
actions: Schema.optionalWith<Schema.Union<[typeof Schema.Boolean, Schema.Struct<{
|
|
@@ -712,6 +790,24 @@ readonly merge_commit_title?: "MERGE_MESSAGE" | "PR_TITLE" | undefined;
|
|
|
712
790
|
readonly merge_commit_message?: "BLANK" | "PR_BODY" | "PR_TITLE" | undefined;
|
|
713
791
|
readonly delete_branch_on_merge?: boolean | undefined;
|
|
714
792
|
readonly web_commit_signoff_required?: boolean | undefined;
|
|
793
|
+
readonly security_and_analysis?: {
|
|
794
|
+
readonly advanced_security?: "disabled" | "enabled" | undefined;
|
|
795
|
+
readonly code_security?: "disabled" | "enabled" | undefined;
|
|
796
|
+
readonly secret_scanning?: "disabled" | "enabled" | undefined;
|
|
797
|
+
readonly secret_scanning_push_protection?: "disabled" | "enabled" | undefined;
|
|
798
|
+
readonly secret_scanning_ai_detection?: "disabled" | "enabled" | undefined;
|
|
799
|
+
readonly secret_scanning_non_provider_patterns?: "disabled" | "enabled" | undefined;
|
|
800
|
+
readonly secret_scanning_delegated_alert_dismissal?: "disabled" | "enabled" | undefined;
|
|
801
|
+
readonly secret_scanning_delegated_bypass?: "disabled" | "enabled" | undefined;
|
|
802
|
+
readonly delegated_bypass_reviewers?: readonly ({
|
|
803
|
+
readonly team: string;
|
|
804
|
+
readonly mode?: "ALWAYS" | "EXEMPT" | undefined;
|
|
805
|
+
} | {
|
|
806
|
+
readonly role: string;
|
|
807
|
+
readonly mode?: "ALWAYS" | "EXEMPT" | undefined;
|
|
808
|
+
})[] | undefined;
|
|
809
|
+
readonly dependabot_security_updates?: "disabled" | "enabled" | undefined;
|
|
810
|
+
} | undefined;
|
|
715
811
|
};
|
|
716
812
|
};
|
|
717
813
|
readonly secrets: {
|
|
@@ -942,6 +1038,23 @@ readonly type: "branch" | "tag";
|
|
|
942
1038
|
}[] | undefined;
|
|
943
1039
|
};
|
|
944
1040
|
};
|
|
1041
|
+
readonly security: {
|
|
1042
|
+
readonly [x: string]: {
|
|
1043
|
+
readonly vulnerability_alerts?: boolean | undefined;
|
|
1044
|
+
readonly automated_security_fixes?: boolean | undefined;
|
|
1045
|
+
readonly private_vulnerability_reporting?: boolean | undefined;
|
|
1046
|
+
};
|
|
1047
|
+
};
|
|
1048
|
+
readonly code_scanning: {
|
|
1049
|
+
readonly [x: string]: {
|
|
1050
|
+
readonly state?: "configured" | "not-configured" | undefined;
|
|
1051
|
+
readonly languages?: readonly ("actions" | "c-cpp" | "csharp" | "go" | "java-kotlin" | "javascript-typescript" | "python" | "ruby" | "swift")[] | undefined;
|
|
1052
|
+
readonly query_suite?: "default" | "extended" | undefined;
|
|
1053
|
+
readonly threat_model?: "remote" | "remote_and_local" | undefined;
|
|
1054
|
+
readonly runner_type?: "labeled" | "standard" | undefined;
|
|
1055
|
+
readonly runner_label?: string | undefined;
|
|
1056
|
+
};
|
|
1057
|
+
};
|
|
945
1058
|
readonly groups: {
|
|
946
1059
|
readonly [x: string]: {
|
|
947
1060
|
readonly owner?: string | undefined;
|
|
@@ -964,6 +1077,8 @@ readonly [x: string]: readonly string[];
|
|
|
964
1077
|
} | undefined;
|
|
965
1078
|
} | undefined;
|
|
966
1079
|
readonly rulesets?: readonly string[] | undefined;
|
|
1080
|
+
readonly security?: readonly string[] | undefined;
|
|
1081
|
+
readonly code_scanning?: readonly string[] | undefined;
|
|
967
1082
|
readonly cleanup?: {
|
|
968
1083
|
readonly secrets: {
|
|
969
1084
|
readonly actions: boolean | {
|
|
@@ -1021,6 +1136,24 @@ readonly merge_commit_title?: "MERGE_MESSAGE" | "PR_TITLE" | undefined;
|
|
|
1021
1136
|
readonly merge_commit_message?: "BLANK" | "PR_BODY" | "PR_TITLE" | undefined;
|
|
1022
1137
|
readonly delete_branch_on_merge?: boolean | undefined;
|
|
1023
1138
|
readonly web_commit_signoff_required?: boolean | undefined;
|
|
1139
|
+
readonly security_and_analysis?: {
|
|
1140
|
+
readonly advanced_security?: "disabled" | "enabled" | undefined;
|
|
1141
|
+
readonly code_security?: "disabled" | "enabled" | undefined;
|
|
1142
|
+
readonly secret_scanning?: "disabled" | "enabled" | undefined;
|
|
1143
|
+
readonly secret_scanning_push_protection?: "disabled" | "enabled" | undefined;
|
|
1144
|
+
readonly secret_scanning_ai_detection?: "disabled" | "enabled" | undefined;
|
|
1145
|
+
readonly secret_scanning_non_provider_patterns?: "disabled" | "enabled" | undefined;
|
|
1146
|
+
readonly secret_scanning_delegated_alert_dismissal?: "disabled" | "enabled" | undefined;
|
|
1147
|
+
readonly secret_scanning_delegated_bypass?: "disabled" | "enabled" | undefined;
|
|
1148
|
+
readonly delegated_bypass_reviewers?: readonly ({
|
|
1149
|
+
readonly team: string;
|
|
1150
|
+
readonly mode?: "ALWAYS" | "EXEMPT" | undefined;
|
|
1151
|
+
} | {
|
|
1152
|
+
readonly role: string;
|
|
1153
|
+
readonly mode?: "ALWAYS" | "EXEMPT" | undefined;
|
|
1154
|
+
})[] | undefined;
|
|
1155
|
+
readonly dependabot_security_updates?: "disabled" | "enabled" | undefined;
|
|
1156
|
+
} | undefined;
|
|
1024
1157
|
};
|
|
1025
1158
|
};
|
|
1026
1159
|
readonly secrets: {
|
|
@@ -1251,6 +1384,23 @@ readonly type: "branch" | "tag";
|
|
|
1251
1384
|
}[] | undefined;
|
|
1252
1385
|
};
|
|
1253
1386
|
};
|
|
1387
|
+
readonly security: {
|
|
1388
|
+
readonly [x: string]: {
|
|
1389
|
+
readonly vulnerability_alerts?: boolean | undefined;
|
|
1390
|
+
readonly automated_security_fixes?: boolean | undefined;
|
|
1391
|
+
readonly private_vulnerability_reporting?: boolean | undefined;
|
|
1392
|
+
};
|
|
1393
|
+
};
|
|
1394
|
+
readonly code_scanning: {
|
|
1395
|
+
readonly [x: string]: {
|
|
1396
|
+
readonly state?: "configured" | "not-configured" | undefined;
|
|
1397
|
+
readonly languages?: readonly ("actions" | "c-cpp" | "csharp" | "go" | "java-kotlin" | "javascript-typescript" | "python" | "ruby" | "swift")[] | undefined;
|
|
1398
|
+
readonly query_suite?: "default" | "extended" | undefined;
|
|
1399
|
+
readonly threat_model?: "remote" | "remote_and_local" | undefined;
|
|
1400
|
+
readonly runner_type?: "labeled" | "standard" | undefined;
|
|
1401
|
+
readonly runner_label?: string | undefined;
|
|
1402
|
+
};
|
|
1403
|
+
};
|
|
1254
1404
|
readonly groups: {
|
|
1255
1405
|
readonly [x: string]: {
|
|
1256
1406
|
readonly owner?: string | undefined;
|
|
@@ -1273,6 +1423,8 @@ readonly [x: string]: readonly string[];
|
|
|
1273
1423
|
} | undefined;
|
|
1274
1424
|
} | undefined;
|
|
1275
1425
|
readonly rulesets?: readonly string[] | undefined;
|
|
1426
|
+
readonly security?: readonly string[] | undefined;
|
|
1427
|
+
readonly code_scanning?: readonly string[] | undefined;
|
|
1276
1428
|
readonly cleanup?: {
|
|
1277
1429
|
readonly secrets: {
|
|
1278
1430
|
readonly actions: boolean | {
|
|
@@ -1644,7 +1796,7 @@ declare interface SyncLoggerService {
|
|
|
1644
1796
|
readonly groupStart: (name: string, repoCount: number) => Effect.Effect<void>;
|
|
1645
1797
|
readonly repoStart: (owner: string, repo: string) => Effect.Effect<void>;
|
|
1646
1798
|
readonly repoSkip: (owner: string, repo: string, reason: string) => Effect.Effect<void>;
|
|
1647
|
-
readonly syncSummary: (resource: "secret" | "variable" | "ruleset" | "environment", count: number, detail: string) => Effect.Effect<void>;
|
|
1799
|
+
readonly syncSummary: (resource: "secret" | "variable" | "ruleset" | "environment" | "security feature" | "code scanning", count: number, detail: string) => Effect.Effect<void>;
|
|
1648
1800
|
readonly settingsApplied: () => Effect.Effect<void>;
|
|
1649
1801
|
readonly cleanupSummary: (resource: string, count: number, names: string[]) => Effect.Effect<void>;
|
|
1650
1802
|
readonly syncOperation: (verb: "sync" | "apply" | "delete" | "skip", resource: string, name: string, detail?: string, source?: string) => Effect.Effect<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reposets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "CLI tool to sync GitHub repo settings, secrets and rulesets across personal repositories",
|
|
6
6
|
"keywords": [
|
|
@@ -42,19 +42,19 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"bin": {
|
|
45
|
-
"reposets": "
|
|
45
|
+
"reposets": "bin/reposets.js"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@1password/sdk": "^0.4.0",
|
|
49
49
|
"@effect/cli": "^0.75.1",
|
|
50
|
-
"@effect/platform": "^0.96.
|
|
50
|
+
"@effect/platform": "^0.96.1",
|
|
51
51
|
"@effect/platform-node": "^0.106.0",
|
|
52
52
|
"@octokit/rest": "^22.0.1",
|
|
53
53
|
"blakejs": "^1.2.1",
|
|
54
|
-
"effect": "^3.21.
|
|
55
|
-
"smol-toml": "
|
|
54
|
+
"effect": "^3.21.2",
|
|
55
|
+
"smol-toml": "^1.6.1",
|
|
56
56
|
"tweetnacl": "^1.0.3",
|
|
57
|
-
"xdg-effect": "^1.0.
|
|
57
|
+
"xdg-effect": "^1.0.1"
|
|
58
58
|
},
|
|
59
59
|
"engines": {
|
|
60
60
|
"node": ">=20.0.0"
|