@empire-builder-kit/nx 1.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +5 -0
  3. package/README.md +34 -0
  4. package/executors.json +74 -0
  5. package/generators.json +40 -0
  6. package/migrations/standard-pre-v1-to-v1.md +11 -0
  7. package/package.json +80 -0
  8. package/src/blueprints/execution.d.ts +31 -0
  9. package/src/blueprints/execution.js +166 -0
  10. package/src/blueprints/execution.js.map +1 -0
  11. package/src/blueprints/index.d.ts +5 -0
  12. package/src/blueprints/index.js +9 -0
  13. package/src/blueprints/index.js.map +1 -0
  14. package/src/blueprints/inputs.d.ts +43 -0
  15. package/src/blueprints/inputs.js +103 -0
  16. package/src/blueprints/inputs.js.map +1 -0
  17. package/src/blueprints/manifest.d.ts +281 -0
  18. package/src/blueprints/manifest.js +441 -0
  19. package/src/blueprints/manifest.js.map +1 -0
  20. package/src/blueprints/resolver.d.ts +28 -0
  21. package/src/blueprints/resolver.js +218 -0
  22. package/src/blueprints/resolver.js.map +1 -0
  23. package/src/blueprints/transitions.d.ts +20 -0
  24. package/src/blueprints/transitions.js +61 -0
  25. package/src/blueprints/transitions.js.map +1 -0
  26. package/src/executors/deploy/deploy.d.ts +4 -0
  27. package/src/executors/deploy/deploy.js +6 -0
  28. package/src/executors/deploy/deploy.js.map +1 -0
  29. package/src/executors/deployment-plan/deployment-plan.d.ts +9 -0
  30. package/src/executors/deployment-plan/deployment-plan.js +50 -0
  31. package/src/executors/deployment-plan/deployment-plan.js.map +1 -0
  32. package/src/executors/deployment-plan/schema.json +24 -0
  33. package/src/executors/dev/dev.d.ts +4 -0
  34. package/src/executors/dev/dev.js +6 -0
  35. package/src/executors/dev/dev.js.map +1 -0
  36. package/src/executors/dev-doctor/dev-doctor.d.ts +31 -0
  37. package/src/executors/dev-doctor/dev-doctor.js +260 -0
  38. package/src/executors/dev-doctor/dev-doctor.js.map +1 -0
  39. package/src/executors/dev-doctor/schema.json +17 -0
  40. package/src/executors/dev-setup/dev-setup.d.ts +11 -0
  41. package/src/executors/dev-setup/dev-setup.js +454 -0
  42. package/src/executors/dev-setup/dev-setup.js.map +1 -0
  43. package/src/executors/dev-setup/schema.json +16 -0
  44. package/src/executors/fleet/fleet.d.ts +62 -0
  45. package/src/executors/fleet/fleet.js +613 -0
  46. package/src/executors/fleet/fleet.js.map +1 -0
  47. package/src/executors/fleet/schema.json +37 -0
  48. package/src/executors/foundation-preflight/foundation-preflight.d.ts +15 -0
  49. package/src/executors/foundation-preflight/foundation-preflight.js +116 -0
  50. package/src/executors/foundation-preflight/foundation-preflight.js.map +1 -0
  51. package/src/executors/foundation-preflight/schema.json +31 -0
  52. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.d.ts +8 -0
  53. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js +41 -0
  54. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js.map +1 -0
  55. package/src/executors/foundation-profile-preflight/schema.json +21 -0
  56. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.d.ts +13 -0
  57. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js +69 -0
  58. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js.map +1 -0
  59. package/src/executors/foundation-remove-preflight/schema.json +17 -0
  60. package/src/executors/github-bootstrap/github-bootstrap.d.ts +8 -0
  61. package/src/executors/github-bootstrap/github-bootstrap.js +30 -0
  62. package/src/executors/github-bootstrap/github-bootstrap.js.map +1 -0
  63. package/src/executors/github-bootstrap/schema.json +25 -0
  64. package/src/executors/refresh/refresh.d.ts +4 -0
  65. package/src/executors/refresh/refresh.js +6 -0
  66. package/src/executors/refresh/refresh.js.map +1 -0
  67. package/src/executors/remove/remove.d.ts +4 -0
  68. package/src/executors/remove/remove.js +6 -0
  69. package/src/executors/remove/remove.js.map +1 -0
  70. package/src/executors/sst-lifecycle/run-sst-command.d.ts +31 -0
  71. package/src/executors/sst-lifecycle/run-sst-command.js +226 -0
  72. package/src/executors/sst-lifecycle/run-sst-command.js.map +1 -0
  73. package/src/executors/sst-lifecycle/schema.json +45 -0
  74. package/src/executors/unlock/unlock.d.ts +4 -0
  75. package/src/executors/unlock/unlock.js +6 -0
  76. package/src/executors/unlock/unlock.js.map +1 -0
  77. package/src/executors/work-markers/schema.json +14 -0
  78. package/src/executors/work-markers/work-markers.d.ts +6 -0
  79. package/src/executors/work-markers/work-markers.js +20 -0
  80. package/src/executors/work-markers/work-markers.js.map +1 -0
  81. package/src/foundation/aurora-logical-database.d.ts +76 -0
  82. package/src/foundation/aurora-logical-database.js +419 -0
  83. package/src/foundation/aurora-logical-database.js.map +1 -0
  84. package/src/foundation/discovery.d.ts +55 -0
  85. package/src/foundation/discovery.js +55 -0
  86. package/src/foundation/discovery.js.map +1 -0
  87. package/src/foundation/index.d.ts +6 -0
  88. package/src/foundation/index.js +10 -0
  89. package/src/foundation/index.js.map +1 -0
  90. package/src/foundation/local-database-contract.d.ts +30 -0
  91. package/src/foundation/local-database-contract.js +75 -0
  92. package/src/foundation/local-database-contract.js.map +1 -0
  93. package/src/foundation/preflight.d.ts +31 -0
  94. package/src/foundation/preflight.js +114 -0
  95. package/src/foundation/preflight.js.map +1 -0
  96. package/src/foundation/provision-database-bindings.d.ts +30 -0
  97. package/src/foundation/provision-database-bindings.js +113 -0
  98. package/src/foundation/provision-database-bindings.js.map +1 -0
  99. package/src/foundation/provision-logical-database.d.ts +26 -0
  100. package/src/foundation/provision-logical-database.js +163 -0
  101. package/src/foundation/provision-logical-database.js.map +1 -0
  102. package/src/foundation/requirements.d.ts +37 -0
  103. package/src/foundation/requirements.js +141 -0
  104. package/src/foundation/requirements.js.map +1 -0
  105. package/src/foundation/stages.d.ts +14 -0
  106. package/src/foundation/stages.js +51 -0
  107. package/src/foundation/stages.js.map +1 -0
  108. package/src/generators/function/function.d.ts +13 -0
  109. package/src/generators/function/function.js +71 -0
  110. package/src/generators/function/function.js.map +1 -0
  111. package/src/generators/function/schema.d.ts +5 -0
  112. package/src/generators/function/schema.json +20 -0
  113. package/src/generators/job/job.d.ts +4 -0
  114. package/src/generators/job/job.js +5 -0
  115. package/src/generators/job/job.js.map +1 -0
  116. package/src/generators/preset/generator.d.ts +5 -0
  117. package/src/generators/preset/generator.js +41 -0
  118. package/src/generators/preset/generator.js.map +1 -0
  119. package/src/generators/preset/schema.d.ts +4 -0
  120. package/src/generators/preset/schema.json +22 -0
  121. package/src/generators/preset/workspace-files.d.ts +18 -0
  122. package/src/generators/preset/workspace-files.js +1605 -0
  123. package/src/generators/preset/workspace-files.js.map +1 -0
  124. package/src/generators/repository-policy/repository-policy.d.ts +4 -0
  125. package/src/generators/repository-policy/repository-policy.js +107 -0
  126. package/src/generators/repository-policy/repository-policy.js.map +1 -0
  127. package/src/generators/repository-policy/schema.d.ts +18 -0
  128. package/src/generators/repository-policy/schema.json +46 -0
  129. package/src/generators/service/service.d.ts +4 -0
  130. package/src/generators/service/service.js +5 -0
  131. package/src/generators/service/service.js.map +1 -0
  132. package/src/generators/slice/schema.d.ts +7 -0
  133. package/src/generators/slice/schema.json +38 -0
  134. package/src/generators/slice/slice.d.ts +26 -0
  135. package/src/generators/slice/slice.js +1698 -0
  136. package/src/generators/slice/slice.js.map +1 -0
  137. package/src/generators/upgrade/schema.d.ts +8 -0
  138. package/src/generators/upgrade/schema.json +31 -0
  139. package/src/generators/upgrade/upgrade.d.ts +27 -0
  140. package/src/generators/upgrade/upgrade.js +682 -0
  141. package/src/generators/upgrade/upgrade.js.map +1 -0
  142. package/src/generators/workload/schema.d.ts +6 -0
  143. package/src/generators/workload/schema.json +21 -0
  144. package/src/generators/workload/workload.d.ts +16 -0
  145. package/src/generators/workload/workload.js +157 -0
  146. package/src/generators/workload/workload.js.map +1 -0
  147. package/src/git-policy/action-pins.d.ts +28 -0
  148. package/src/git-policy/action-pins.js +35 -0
  149. package/src/git-policy/action-pins.js.map +1 -0
  150. package/src/git-policy/github-bootstrap.d.ts +23 -0
  151. package/src/git-policy/github-bootstrap.js +759 -0
  152. package/src/git-policy/github-bootstrap.js.map +1 -0
  153. package/src/git-policy/index.d.ts +3 -0
  154. package/src/git-policy/index.js +7 -0
  155. package/src/git-policy/index.js.map +1 -0
  156. package/src/git-policy/routing.d.ts +15 -0
  157. package/src/git-policy/routing.js +77 -0
  158. package/src/git-policy/routing.js.map +1 -0
  159. package/src/index.d.ts +8 -0
  160. package/src/index.js +12 -0
  161. package/src/index.js.map +1 -0
  162. package/src/local-dev/contracts.d.ts +31 -0
  163. package/src/local-dev/contracts.js +126 -0
  164. package/src/local-dev/contracts.js.map +1 -0
  165. package/src/local-dev/effects.d.ts +16 -0
  166. package/src/local-dev/effects.js +63 -0
  167. package/src/local-dev/effects.js.map +1 -0
  168. package/src/local-dev/gateway-daemon.d.ts +4 -0
  169. package/src/local-dev/gateway-daemon.js +39 -0
  170. package/src/local-dev/gateway-daemon.js.map +1 -0
  171. package/src/local-dev/gateway.d.ts +60 -0
  172. package/src/local-dev/gateway.js +368 -0
  173. package/src/local-dev/gateway.js.map +1 -0
  174. package/src/local-dev/identity.d.ts +16 -0
  175. package/src/local-dev/identity.js +101 -0
  176. package/src/local-dev/identity.js.map +1 -0
  177. package/src/local-dev/index.d.ts +7 -0
  178. package/src/local-dev/index.js +11 -0
  179. package/src/local-dev/index.js.map +1 -0
  180. package/src/local-dev/portless.d.ts +15 -0
  181. package/src/local-dev/portless.js +115 -0
  182. package/src/local-dev/portless.js.map +1 -0
  183. package/src/local-dev/ports.d.ts +10 -0
  184. package/src/local-dev/ports.js +39 -0
  185. package/src/local-dev/ports.js.map +1 -0
  186. package/src/local-dev/proxy.d.ts +37 -0
  187. package/src/local-dev/proxy.js +271 -0
  188. package/src/local-dev/proxy.js.map +1 -0
  189. package/src/local-dev/tls.d.ts +24 -0
  190. package/src/local-dev/tls.js +294 -0
  191. package/src/local-dev/tls.js.map +1 -0
  192. package/src/ownership/composition.d.ts +16 -0
  193. package/src/ownership/composition.js +125 -0
  194. package/src/ownership/composition.js.map +1 -0
  195. package/src/ownership/foundation-record.d.ts +66 -0
  196. package/src/ownership/foundation-record.js +108 -0
  197. package/src/ownership/foundation-record.js.map +1 -0
  198. package/src/ownership/hash.d.ts +2 -0
  199. package/src/ownership/hash.js +10 -0
  200. package/src/ownership/hash.js.map +1 -0
  201. package/src/ownership/index.d.ts +6 -0
  202. package/src/ownership/index.js +10 -0
  203. package/src/ownership/index.js.map +1 -0
  204. package/src/ownership/json-ownership.d.ts +13 -0
  205. package/src/ownership/json-ownership.js +147 -0
  206. package/src/ownership/json-ownership.js.map +1 -0
  207. package/src/ownership/record.d.ts +186 -0
  208. package/src/ownership/record.js +336 -0
  209. package/src/ownership/record.js.map +1 -0
  210. package/src/ownership/workspace-record.d.ts +66 -0
  211. package/src/ownership/workspace-record.js +108 -0
  212. package/src/ownership/workspace-record.js.map +1 -0
  213. package/src/repository-policy/aurora-preflight.d.ts +15 -0
  214. package/src/repository-policy/aurora-preflight.js +45 -0
  215. package/src/repository-policy/aurora-preflight.js.map +1 -0
  216. package/src/repository-policy/deployment-plan.d.ts +24 -0
  217. package/src/repository-policy/deployment-plan.js +124 -0
  218. package/src/repository-policy/deployment-plan.js.map +1 -0
  219. package/src/repository-policy/foundation-profile.d.ts +20 -0
  220. package/src/repository-policy/foundation-profile.js +45 -0
  221. package/src/repository-policy/foundation-profile.js.map +1 -0
  222. package/src/repository-policy/index.d.ts +4 -0
  223. package/src/repository-policy/index.js +8 -0
  224. package/src/repository-policy/index.js.map +1 -0
  225. package/src/repository-policy/policy.d.ts +59 -0
  226. package/src/repository-policy/policy.js +274 -0
  227. package/src/repository-policy/policy.js.map +1 -0
  228. package/src/schemas/blueprint-input-schema-v1.json +23 -0
  229. package/src/schemas/blueprint-v1.json +186 -0
  230. package/src/schemas/foundation-ownership-v2.json +42 -0
  231. package/src/schemas/foundation-requirement-v1.json +32 -0
  232. package/src/schemas/generated-ownership-v2.json +117 -0
  233. package/src/schemas/repository-policy-v1.json +58 -0
  234. package/src/schemas/workspace-ownership-v2.json +42 -0
  235. package/src/validation.d.ts +14 -0
  236. package/src/validation.js +14 -0
  237. package/src/validation.js.map +1 -0
  238. package/src/work-markers/index.d.ts +1 -0
  239. package/src/work-markers/index.js +5 -0
  240. package/src/work-markers/index.js.map +1 -0
  241. package/src/work-markers/work-markers.d.ts +39 -0
  242. package/src/work-markers/work-markers.js +359 -0
  243. package/src/work-markers/work-markers.js.map +1 -0
@@ -0,0 +1,759 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EBK_CODE_OWNER_PLACEHOLDER = exports.EBK_PROTECTED_ENVIRONMENT_VARIABLES = exports.EBK_DEPLOYMENT_ROLE_SECRET = exports.EBK_REQUIRED_CHECKS = exports.EBK_PROTECTED_BRANCH_RULESET_NAME = void 0;
4
+ exports.runGitHubBootstrap = runGitHubBootstrap;
5
+ const index_js_1 = require("../repository-policy/index.js");
6
+ exports.EBK_PROTECTED_BRANCH_RULESET_NAME = 'Empire Builder Kit protected branches';
7
+ exports.EBK_REQUIRED_CHECKS = ['policy', 'verify'];
8
+ exports.EBK_DEPLOYMENT_ROLE_SECRET = 'AWS_DEPLOY_ROLE_ARN';
9
+ exports.EBK_PROTECTED_ENVIRONMENT_VARIABLES = [
10
+ 'AWS_EXPECTED_ACCOUNT_ID',
11
+ 'AWS_REGION',
12
+ 'EBK_ALLOW_PRODUCTION_REMOVE',
13
+ ];
14
+ exports.EBK_CODE_OWNER_PLACEHOLDER = '@REPLACE_WITH_GITHUB_CODE_OWNER';
15
+ const GITHUB_API_VERSION = '2022-11-28';
16
+ const PROTECTED_BRANCHES = ['develop', 'main'];
17
+ const ENVIRONMENT_BRANCHES = {
18
+ production: 'main',
19
+ staging: 'develop',
20
+ };
21
+ const PRIVATE_ENVIRONMENT_REVIEWER_PLANS = new Set([
22
+ 'business_plus',
23
+ 'enterprise',
24
+ 'enterprise_cloud',
25
+ ]);
26
+ class GitHubApiError extends Error {
27
+ status;
28
+ constructor(status, message) {
29
+ super(message);
30
+ this.status = status;
31
+ }
32
+ }
33
+ class GitHubApi {
34
+ token;
35
+ baseUrl;
36
+ constructor(baseUrl, token) {
37
+ this.token = token;
38
+ this.baseUrl = baseUrl.replace(/\/$/, '');
39
+ }
40
+ async request(method, path, body) {
41
+ const response = await fetch(`${this.baseUrl}${path}`, {
42
+ body: body === undefined ? undefined : JSON.stringify(body),
43
+ headers: {
44
+ Accept: 'application/vnd.github+json',
45
+ Authorization: `Bearer ${this.token}`,
46
+ 'X-GitHub-Api-Version': GITHUB_API_VERSION,
47
+ ...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
48
+ },
49
+ method,
50
+ });
51
+ if (!response.ok) {
52
+ let detail = response.statusText;
53
+ try {
54
+ const payload = (await response.json());
55
+ detail = payload.message ?? detail;
56
+ }
57
+ catch {
58
+ // The status and endpoint are sufficient for a safe diagnostic.
59
+ }
60
+ throw new GitHubApiError(response.status, `GitHub ${method} ${path} failed (${response.status}): ${detail}`);
61
+ }
62
+ if (response.status === 204)
63
+ return undefined;
64
+ return (await response.json());
65
+ }
66
+ }
67
+ function parseRepository(repository) {
68
+ const match = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(repository.trim());
69
+ if (!match) {
70
+ throw new Error('GitHub repository must be explicit and formatted as owner/repository.');
71
+ }
72
+ return { owner: match[1], repo: match[2] };
73
+ }
74
+ function asObjects(value) {
75
+ return Array.isArray(value)
76
+ ? value.filter((item) => typeof item === 'object' && item !== null && !Array.isArray(item))
77
+ : [];
78
+ }
79
+ function asStrings(value) {
80
+ return Array.isArray(value)
81
+ ? value.filter((item) => typeof item === 'string')
82
+ : [];
83
+ }
84
+ function requiresPrivateEnvironmentCapabilities(repo) {
85
+ if (typeof repo.visibility === 'string') {
86
+ return repo.visibility !== 'public';
87
+ }
88
+ if (typeof repo.private === 'boolean')
89
+ return repo.private;
90
+ return true;
91
+ }
92
+ function namedValues(value, property = 'name') {
93
+ return asObjects(value)
94
+ .map((item) => item[property])
95
+ .filter((item) => typeof item === 'string')
96
+ .sort();
97
+ }
98
+ async function readPagedObjects(api, path, collectionProperty) {
99
+ const values = [];
100
+ for (let page = 1; page <= 100; page += 1) {
101
+ const separator = path.includes('?') ? '&' : '?';
102
+ const payload = await api.request('GET', `${path}${separator}per_page=100&page=${page}`);
103
+ const pageValues = collectionProperty
104
+ ? asObjects(typeof payload === 'object' && payload !== null
105
+ ? payload[collectionProperty]
106
+ : undefined)
107
+ : asObjects(payload);
108
+ values.push(...pageValues);
109
+ if (pageValues.length < 100)
110
+ return values;
111
+ }
112
+ throw new Error(`GitHub pagination exceeded 10,000 records for ${path}.`);
113
+ }
114
+ function rulesetRules(ruleset) {
115
+ return asObjects(ruleset.rules);
116
+ }
117
+ function ruleParameters(rule) {
118
+ return typeof rule.parameters === 'object' && rule.parameters !== null
119
+ ? rule.parameters
120
+ : {};
121
+ }
122
+ function activeBranchRuleset(ruleset) {
123
+ return ruleset.target === 'branch' && ruleset.enforcement === 'active';
124
+ }
125
+ function refPatternMatches(pattern, ref) {
126
+ if (pattern === '~ALL')
127
+ return true;
128
+ if (pattern === '~DEFAULT_BRANCH')
129
+ return ref === 'refs/heads/main';
130
+ const expression = pattern
131
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
132
+ .replace(/\*\*/g, '*')
133
+ .replace(/\*/g, '.*')
134
+ .replace(/\?/g, '.');
135
+ return new RegExp(`^${expression}$`).test(ref);
136
+ }
137
+ function rulesetTouchesProtectedBranches(ruleset) {
138
+ const conditions = typeof ruleset.conditions === 'object' && ruleset.conditions !== null
139
+ ? ruleset.conditions
140
+ : {};
141
+ const refName = typeof conditions.ref_name === 'object' && conditions.ref_name !== null
142
+ ? conditions.ref_name
143
+ : undefined;
144
+ if (!refName)
145
+ return true;
146
+ const include = asStrings(refName.include);
147
+ const exclude = asStrings(refName.exclude);
148
+ return PROTECTED_BRANCHES.some((branch) => {
149
+ const ref = `refs/heads/${branch}`;
150
+ const included = include.length === 0 ||
151
+ include.some((pattern) => refPatternMatches(pattern, ref));
152
+ const excluded = exclude.some((pattern) => refPatternMatches(pattern, ref));
153
+ return included && !excluded;
154
+ });
155
+ }
156
+ function conflictsWithMergeCommitContract(ruleset) {
157
+ if (!activeBranchRuleset(ruleset) ||
158
+ !rulesetTouchesProtectedBranches(ruleset)) {
159
+ return [];
160
+ }
161
+ const conflicts = [];
162
+ for (const rule of rulesetRules(ruleset)) {
163
+ if (rule.type === 'required_linear_history') {
164
+ conflicts.push('requires linear history');
165
+ }
166
+ if (rule.type === 'pull_request') {
167
+ const methods = asStrings(ruleParameters(rule).allowed_merge_methods);
168
+ if (methods.length > 0 && !methods.includes('merge')) {
169
+ conflicts.push('does not permit merge commits');
170
+ }
171
+ }
172
+ }
173
+ return conflicts;
174
+ }
175
+ function reviewerState(environment) {
176
+ const rule = asObjects(environment.protection_rules).find((candidate) => candidate.type === 'required_reviewers');
177
+ if (!rule)
178
+ return { preventSelfReview: false, reviewers: [] };
179
+ const reviewers = asObjects(rule.reviewers).flatMap((entry) => {
180
+ const reviewer = typeof entry.reviewer === 'object' && entry.reviewer !== null
181
+ ? entry.reviewer
182
+ : {};
183
+ const id = reviewer.id;
184
+ const candidateType = entry.type ?? reviewer.type;
185
+ if (typeof id !== 'number' ||
186
+ !Number.isSafeInteger(id) ||
187
+ (candidateType !== 'Team' && candidateType !== 'User')) {
188
+ return [];
189
+ }
190
+ return [{ id, type: candidateType }];
191
+ });
192
+ reviewers.sort((left, right) => left.type === right.type
193
+ ? left.id - right.id
194
+ : left.type.localeCompare(right.type));
195
+ return {
196
+ preventSelfReview: rule.prevent_self_review === true,
197
+ reviewers,
198
+ };
199
+ }
200
+ function environmentWaitTimer(environment) {
201
+ const rule = asObjects(environment.protection_rules).find((candidate) => candidate.type === 'wait_timer');
202
+ const waitTimer = rule?.wait_timer;
203
+ return typeof waitTimer === 'number' && Number.isSafeInteger(waitTimer)
204
+ ? waitTimer
205
+ : 0;
206
+ }
207
+ function hasValidCatchAllCodeOwner(contents) {
208
+ const catchAllOwners = contents.split(/\r?\n/).flatMap((line) => {
209
+ const withoutComment = line.split('#', 1)[0]?.trim() ?? '';
210
+ if (!withoutComment)
211
+ return [];
212
+ const [pattern, ...owners] = withoutComment.split(/\s+/);
213
+ return pattern === '*' ? [owners] : [];
214
+ });
215
+ const effectiveOwners = catchAllOwners.at(-1);
216
+ return (effectiveOwners !== undefined &&
217
+ effectiveOwners.length > 0 &&
218
+ effectiveOwners.every((owner) => owner.startsWith('@') && owner !== exports.EBK_CODE_OWNER_PLACEHOLDER));
219
+ }
220
+ function desiredRuleset() {
221
+ return {
222
+ bypass_actors: [],
223
+ conditions: {
224
+ ref_name: {
225
+ exclude: [],
226
+ include: PROTECTED_BRANCHES.map((branch) => `refs/heads/${branch}`),
227
+ },
228
+ },
229
+ enforcement: 'active',
230
+ name: exports.EBK_PROTECTED_BRANCH_RULESET_NAME,
231
+ rules: [
232
+ { type: 'deletion' },
233
+ { type: 'non_fast_forward' },
234
+ {
235
+ parameters: {
236
+ allowed_merge_methods: ['merge'],
237
+ dismiss_stale_reviews_on_push: true,
238
+ require_code_owner_review: true,
239
+ require_last_push_approval: true,
240
+ required_approving_review_count: 1,
241
+ required_review_thread_resolution: true,
242
+ },
243
+ type: 'pull_request',
244
+ },
245
+ {
246
+ parameters: {
247
+ required_status_checks: exports.EBK_REQUIRED_CHECKS.map((context) => ({
248
+ context,
249
+ })),
250
+ strict_required_status_checks_policy: true,
251
+ },
252
+ type: 'required_status_checks',
253
+ },
254
+ ],
255
+ target: 'branch',
256
+ };
257
+ }
258
+ async function inspectEnvironment(api, repositoryPath, name) {
259
+ const encoded = encodeURIComponent(name);
260
+ let environment;
261
+ try {
262
+ environment = await api.request('GET', `${repositoryPath}/environments/${encoded}`);
263
+ }
264
+ catch (error) {
265
+ if (error instanceof GitHubApiError && error.status === 404) {
266
+ return {
267
+ branchPolicies: [],
268
+ customBranchPolicies: false,
269
+ exists: false,
270
+ preventSelfReview: false,
271
+ reviewers: [],
272
+ secretNames: [],
273
+ variableNames: [],
274
+ waitTimer: 0,
275
+ };
276
+ }
277
+ throw error;
278
+ }
279
+ const customBranchPolicies = typeof environment.deployment_branch_policy === 'object' &&
280
+ environment.deployment_branch_policy !== null &&
281
+ environment.deployment_branch_policy
282
+ .custom_branch_policies === true;
283
+ let branchPolicyValues = [];
284
+ try {
285
+ const branchPolicies = await readPagedObjects(api, `${repositoryPath}/environments/${encoded}/deployment-branch-policies`, 'branch_policies');
286
+ branchPolicyValues = namedValues(branchPolicies);
287
+ }
288
+ catch (error) {
289
+ if (!(error instanceof GitHubApiError &&
290
+ error.status === 404 &&
291
+ !customBranchPolicies)) {
292
+ throw error;
293
+ }
294
+ }
295
+ const secrets = await readPagedObjects(api, `${repositoryPath}/environments/${encoded}/secrets`, 'secrets');
296
+ const variables = await readPagedObjects(api, `${repositoryPath}/environments/${encoded}/variables`, 'variables');
297
+ const reviewers = reviewerState(environment);
298
+ return {
299
+ branchPolicies: branchPolicyValues,
300
+ customBranchPolicies,
301
+ exists: true,
302
+ preventSelfReview: reviewers.preventSelfReview,
303
+ reviewers: reviewers.reviewers,
304
+ secretNames: namedValues(secrets),
305
+ variableNames: namedValues(variables),
306
+ waitTimer: environmentWaitTimer(environment),
307
+ };
308
+ }
309
+ async function inspect(api, repositoryPath, productionApprovalMode) {
310
+ const blockers = [];
311
+ const repo = await api.request('GET', repositoryPath);
312
+ let ownerPlanName;
313
+ if (productionApprovalMode === 'environment-reviewer' &&
314
+ requiresPrivateEnvironmentCapabilities(repo)) {
315
+ const owner = typeof repo.owner === 'object' && repo.owner !== null
316
+ ? repo.owner
317
+ : {};
318
+ const login = typeof owner.login === 'string' ? owner.login : undefined;
319
+ const ownerType = owner.type;
320
+ if (!login || (ownerType !== 'Organization' && ownerType !== 'User')) {
321
+ blockers.push('Private-repository GitHub plan capability could not be resolved from the repository owner.');
322
+ }
323
+ else {
324
+ const ownerPath = `${ownerType === 'Organization' ? '/orgs' : '/users'}/${encodeURIComponent(login)}`;
325
+ try {
326
+ const ownerState = await api.request('GET', ownerPath);
327
+ const plan = typeof ownerState.plan === 'object' && ownerState.plan !== null
328
+ ? ownerState.plan
329
+ : {};
330
+ ownerPlanName =
331
+ typeof plan.name === 'string' ? plan.name.toLowerCase() : undefined;
332
+ if (!ownerPlanName) {
333
+ blockers.push('Private-repository GitHub plan capability could not be verified; environment-reviewer mode fails closed.');
334
+ }
335
+ }
336
+ catch (error) {
337
+ blockers.push(`Private-repository GitHub plan capability could not be inspected: ${error instanceof Error ? error.message : String(error)}`);
338
+ }
339
+ }
340
+ }
341
+ for (const branch of PROTECTED_BRANCHES) {
342
+ try {
343
+ const codeOwners = await api.request('GET', `${repositoryPath}/contents/.github/CODEOWNERS?ref=${branch}`);
344
+ const content = codeOwners.content;
345
+ const encoding = codeOwners.encoding;
346
+ if (typeof content !== 'string' ||
347
+ encoding !== 'base64' ||
348
+ !hasValidCatchAllCodeOwner(Buffer.from(content, 'base64').toString('utf8'))) {
349
+ blockers.push(`Committed .github/CODEOWNERS on ${branch} must contain a valid effective final non-placeholder catch-all owner before code-owner review can be enforced.`);
350
+ }
351
+ const errors = await api.request('GET', `${repositoryPath}/codeowners/errors?ref=${branch}`);
352
+ const codeOwnerErrors = asObjects(errors.errors);
353
+ if (codeOwnerErrors.length > 0) {
354
+ blockers.push(`GitHub reports ${codeOwnerErrors.length} CODEOWNERS error${codeOwnerErrors.length === 1 ? '' : 's'} on ${branch}.`);
355
+ }
356
+ }
357
+ catch (error) {
358
+ blockers.push(`CODEOWNERS enforcement on ${branch} could not be verified: ${error instanceof Error ? error.message : String(error)}`);
359
+ }
360
+ }
361
+ let rulesets = [];
362
+ try {
363
+ const summaries = await readPagedObjects(api, `${repositoryPath}/rulesets?includes_parents=true`);
364
+ rulesets = await Promise.all(summaries.map(async (summary) => {
365
+ const id = summary.id;
366
+ return typeof id === 'number'
367
+ ? api.request('GET', `${repositoryPath}/rulesets/${id}`)
368
+ : summary;
369
+ }));
370
+ }
371
+ catch (error) {
372
+ blockers.push(`Ruleset enforcement could not be inspected: ${error instanceof Error ? error.message : String(error)}`);
373
+ }
374
+ for (const branch of PROTECTED_BRANCHES) {
375
+ try {
376
+ const branchState = await api.request('GET', `${repositoryPath}/branches/${branch}`);
377
+ const commit = typeof branchState.commit === 'object' && branchState.commit !== null
378
+ ? branchState.commit
379
+ : {};
380
+ const sha = typeof commit.sha === 'string' ? commit.sha : branch;
381
+ const checks = await readPagedObjects(api, `${repositoryPath}/commits/${encodeURIComponent(sha)}/check-runs`, 'check_runs');
382
+ const names = new Set(namedValues(checks));
383
+ for (const required of exports.EBK_REQUIRED_CHECKS) {
384
+ if (!names.has(required)) {
385
+ blockers.push(`${branch} has not emitted the required ${required} check context.`);
386
+ }
387
+ }
388
+ }
389
+ catch (error) {
390
+ blockers.push(`${branch} branch/check discovery failed: ${error instanceof Error ? error.message : String(error)}`);
391
+ }
392
+ }
393
+ let repositorySecretNames = [];
394
+ try {
395
+ const secrets = await readPagedObjects(api, `${repositoryPath}/actions/secrets`, 'secrets');
396
+ repositorySecretNames = namedValues(secrets);
397
+ }
398
+ catch (error) {
399
+ blockers.push(`Repository secret inventory could not be inspected: ${error instanceof Error ? error.message : String(error)}`);
400
+ }
401
+ let repositoryVariableNames = [];
402
+ try {
403
+ const variables = await readPagedObjects(api, `${repositoryPath}/actions/variables`, 'variables');
404
+ repositoryVariableNames = namedValues(variables);
405
+ }
406
+ catch (error) {
407
+ blockers.push(`Repository variable inventory could not be inspected: ${error instanceof Error ? error.message : String(error)}`);
408
+ }
409
+ let orgSecretNames;
410
+ let orgVariableNames;
411
+ const owner = typeof repo.owner === 'object' && repo.owner !== null
412
+ ? repo.owner
413
+ : undefined;
414
+ if (owner?.type === 'User') {
415
+ orgSecretNames = [];
416
+ orgVariableNames = [];
417
+ }
418
+ else {
419
+ try {
420
+ const secrets = await readPagedObjects(api, `${repositoryPath}/actions/organization-secrets`, 'secrets');
421
+ orgSecretNames = namedValues(secrets);
422
+ }
423
+ catch (error) {
424
+ blockers.push(`Organization secret exposure is unverified and requires a credential with inspection access: ${error instanceof Error ? error.message : String(error)}`);
425
+ }
426
+ try {
427
+ const variables = await readPagedObjects(api, `${repositoryPath}/actions/organization-variables`, 'variables');
428
+ orgVariableNames = namedValues(variables);
429
+ }
430
+ catch (error) {
431
+ blockers.push(`Organization variable exposure is unverified and requires a credential with inspection access: ${error instanceof Error ? error.message : String(error)}`);
432
+ }
433
+ }
434
+ let actionsCanApprovePullRequests;
435
+ let actionsDefaultWorkflowPermissions;
436
+ try {
437
+ const permissions = await api.request('GET', `${repositoryPath}/actions/permissions/workflow`);
438
+ actionsCanApprovePullRequests =
439
+ permissions.can_approve_pull_request_reviews === true;
440
+ actionsDefaultWorkflowPermissions =
441
+ typeof permissions.default_workflow_permissions === 'string'
442
+ ? permissions.default_workflow_permissions
443
+ : undefined;
444
+ }
445
+ catch (error) {
446
+ blockers.push(`Workflow pull-request permission could not be inspected: ${error instanceof Error ? error.message : String(error)}`);
447
+ }
448
+ const inspectNamedEnvironment = async (name) => {
449
+ try {
450
+ return await inspectEnvironment(api, repositoryPath, name);
451
+ }
452
+ catch (error) {
453
+ blockers.push(`${name} environment inventory could not be inspected: ${error instanceof Error ? error.message : String(error)}`);
454
+ return {
455
+ branchPolicies: [],
456
+ customBranchPolicies: false,
457
+ exists: false,
458
+ preventSelfReview: false,
459
+ reviewers: [],
460
+ secretNames: [],
461
+ variableNames: [],
462
+ waitTimer: 0,
463
+ };
464
+ }
465
+ };
466
+ const [production, staging] = await Promise.all([
467
+ inspectNamedEnvironment('production'),
468
+ inspectNamedEnvironment('staging'),
469
+ ]);
470
+ return {
471
+ blockers,
472
+ state: {
473
+ actionsCanApprovePullRequests,
474
+ actionsDefaultWorkflowPermissions,
475
+ environments: { production, staging },
476
+ orgSecretNames,
477
+ orgVariableNames,
478
+ ownerPlanName,
479
+ ownedRuleset: rulesets.find((ruleset) => ruleset.name === exports.EBK_PROTECTED_BRANCH_RULESET_NAME &&
480
+ ruleset.source_type !== 'Organization'),
481
+ repo,
482
+ repositorySecretNames,
483
+ repositoryVariableNames,
484
+ rulesets,
485
+ },
486
+ };
487
+ }
488
+ function evaluate(repository, state, inspectionBlockers, productionApprovalMode, productionReviewerIds) {
489
+ const blockers = [...inspectionBlockers];
490
+ const changes = [];
491
+ const warnings = [];
492
+ const privateRepository = requiresPrivateEnvironmentCapabilities(state.repo);
493
+ if (productionApprovalMode === 'environment-reviewer' &&
494
+ privateRepository &&
495
+ state.ownerPlanName !== undefined &&
496
+ !PRIVATE_ENVIRONMENT_REVIEWER_PLANS.has(state.ownerPlanName)) {
497
+ blockers.push(`Committed environment-reviewer mode requires GitHub Enterprise support for a private repository; the inspected owner plan is ${state.ownerPlanName}.`);
498
+ }
499
+ if (state.repo.allow_merge_commit !== true) {
500
+ changes.push('Enable repository merge commits.');
501
+ }
502
+ if (state.repo.allow_squash_merge !== false) {
503
+ changes.push('Disable repository squash merging.');
504
+ }
505
+ if (state.repo.allow_rebase_merge !== false) {
506
+ changes.push('Disable repository rebase merging.');
507
+ }
508
+ if (state.actionsCanApprovePullRequests !== true) {
509
+ blockers.push('GitHub Actions is not permitted to create or approve pull requests for managed sync-back.');
510
+ }
511
+ if (state.actionsDefaultWorkflowPermissions !== 'read') {
512
+ blockers.push('GitHub Actions default workflow permissions must be read-only; write access must remain job-scoped.');
513
+ }
514
+ if (state.repositorySecretNames.includes(exports.EBK_DEPLOYMENT_ROLE_SECRET)) {
515
+ blockers.push(`${exports.EBK_DEPLOYMENT_ROLE_SECRET} exists at repository scope and would bypass environment protection.`);
516
+ }
517
+ if (state.orgSecretNames?.includes(exports.EBK_DEPLOYMENT_ROLE_SECRET)) {
518
+ blockers.push(`${exports.EBK_DEPLOYMENT_ROLE_SECRET} is exposed from organization scope and would bypass environment protection.`);
519
+ }
520
+ for (const variable of exports.EBK_PROTECTED_ENVIRONMENT_VARIABLES) {
521
+ if (state.repositoryVariableNames.includes(variable)) {
522
+ blockers.push(`${variable} exists at repository variable scope instead of its protected environment.`);
523
+ }
524
+ if (state.orgVariableNames?.includes(variable)) {
525
+ blockers.push(`${variable} is exposed from organization variable scope instead of its protected environment.`);
526
+ }
527
+ }
528
+ for (const ruleset of state.rulesets) {
529
+ const conflicts = conflictsWithMergeCommitContract(ruleset);
530
+ if (conflicts.length > 0) {
531
+ blockers.push(`Active ruleset ${String(ruleset.name ?? ruleset.id ?? 'unknown')} ${conflicts.join(' and ')}.`);
532
+ }
533
+ }
534
+ if (!state.ownedRuleset) {
535
+ changes.push(`Create ruleset ${exports.EBK_PROTECTED_BRANCH_RULESET_NAME}.`);
536
+ }
537
+ else if (JSON.stringify(normalizeRuleset(state.ownedRuleset)) !==
538
+ JSON.stringify(normalizeRuleset(desiredRuleset()))) {
539
+ changes.push(`Update ruleset ${exports.EBK_PROTECTED_BRANCH_RULESET_NAME}.`);
540
+ }
541
+ for (const [environment, branch] of Object.entries(ENVIRONMENT_BRANCHES)) {
542
+ const current = state.environments[environment];
543
+ if (!current.exists)
544
+ changes.push(`Create ${environment} environment.`);
545
+ if (!current.customBranchPolicies) {
546
+ changes.push(`Enable custom deployment branch policy for ${environment}.`);
547
+ }
548
+ const extras = current.branchPolicies.filter((item) => item !== branch);
549
+ if (extras.length > 0) {
550
+ blockers.push(`${environment} permits unexpected deployment branches: ${extras.join(', ')}.`);
551
+ }
552
+ if (!current.branchPolicies.includes(branch)) {
553
+ changes.push(`Restrict ${environment} deployment to ${branch}.`);
554
+ }
555
+ if (!current.secretNames.includes(exports.EBK_DEPLOYMENT_ROLE_SECRET)) {
556
+ blockers.push(`${environment} environment is missing ${exports.EBK_DEPLOYMENT_ROLE_SECRET}; bootstrap never creates secret values.`);
557
+ }
558
+ const requiredVariables = environment === 'production'
559
+ ? exports.EBK_PROTECTED_ENVIRONMENT_VARIABLES
560
+ : exports.EBK_PROTECTED_ENVIRONMENT_VARIABLES.filter((variable) => variable !== 'EBK_ALLOW_PRODUCTION_REMOVE');
561
+ for (const variable of requiredVariables) {
562
+ if (!current.variableNames.includes(variable)) {
563
+ blockers.push(`${environment} environment is missing required variable ${variable}; bootstrap never creates variable values.`);
564
+ }
565
+ }
566
+ }
567
+ const production = state.environments.production;
568
+ if (productionApprovalMode === 'promotion-pr') {
569
+ warnings.push('Production authorization is the required non-author CODEOWNER approval on the develop-to-main promotion pull request; its merge starts deployment without an EBK-managed environment approval.');
570
+ warnings.push('The promotion approver may also be the human whose merge triggers deployment; rollback uses a reviewed protected revert and redeploy.');
571
+ warnings.push('A repository write actor who can run Actions may manually dispatch deploy, refresh, or unlock against the approved main tip without another human gate; remove remains separately variable-gated.');
572
+ if (production.reviewers.length > 0 || production.waitTimer > 0) {
573
+ warnings.push('Production retains stronger environment protection outside promotion-pr mode; bootstrap preserves it, so merge may wait for that additional gate.');
574
+ }
575
+ }
576
+ else {
577
+ const requestedProductionReviewers = existingReviewerPayload(productionReviewerIds);
578
+ const effectiveReviewers = productionReviewerIds.length > 0
579
+ ? requestedProductionReviewers
580
+ : production.reviewers;
581
+ if (effectiveReviewers.length === 0) {
582
+ blockers.push('Environment-reviewer mode requires at least one existing or explicitly supplied production reviewer ID.');
583
+ }
584
+ else {
585
+ if (productionReviewerIds.length > 0 &&
586
+ JSON.stringify(requestedProductionReviewers) !==
587
+ JSON.stringify(production.reviewers)) {
588
+ changes.push('Set the explicit production environment reviewers.');
589
+ }
590
+ if (!production.preventSelfReview) {
591
+ changes.push('Enable production environment self-review prevention.');
592
+ }
593
+ }
594
+ }
595
+ if (state.environments.staging.reviewers.length > 0) {
596
+ warnings.push('Staging has additional reviewer protection; bootstrap will preserve it.');
597
+ }
598
+ return {
599
+ blockers: [...new Set(blockers)].sort(),
600
+ changes: [...new Set(changes)].sort(),
601
+ ready: blockers.length === 0 && changes.length === 0,
602
+ repository,
603
+ productionApprovalMode,
604
+ warnings: [...new Set(warnings)].sort(),
605
+ };
606
+ }
607
+ function normalizeRuleset(ruleset) {
608
+ const conditions = typeof ruleset.conditions === 'object' && ruleset.conditions !== null
609
+ ? ruleset.conditions
610
+ : {};
611
+ const refName = typeof conditions.ref_name === 'object' && conditions.ref_name !== null
612
+ ? conditions.ref_name
613
+ : {};
614
+ const rules = rulesetRules(ruleset)
615
+ .map((rule) => {
616
+ const type = String(rule.type ?? '');
617
+ const parameters = ruleParameters(rule);
618
+ if (type === 'pull_request') {
619
+ return {
620
+ parameters: {
621
+ allowed_merge_methods: asStrings(parameters.allowed_merge_methods).sort(),
622
+ dismiss_stale_reviews_on_push: parameters.dismiss_stale_reviews_on_push === true,
623
+ require_code_owner_review: parameters.require_code_owner_review === true,
624
+ require_last_push_approval: parameters.require_last_push_approval === true,
625
+ required_approving_review_count: parameters.required_approving_review_count,
626
+ required_review_thread_resolution: parameters.required_review_thread_resolution === true,
627
+ },
628
+ type,
629
+ };
630
+ }
631
+ if (type === 'required_status_checks') {
632
+ return {
633
+ parameters: {
634
+ required_status_checks: asObjects(parameters.required_status_checks)
635
+ .map(({ context }) => ({ context }))
636
+ .sort((left, right) => String(left.context).localeCompare(String(right.context))),
637
+ strict_required_status_checks_policy: parameters.strict_required_status_checks_policy === true,
638
+ },
639
+ type,
640
+ };
641
+ }
642
+ return { type };
643
+ })
644
+ .sort((left, right) => String(left.type).localeCompare(String(right.type)));
645
+ return {
646
+ bypass_actors: Array.isArray(ruleset.bypass_actors)
647
+ ? ruleset.bypass_actors
648
+ : [],
649
+ conditions: {
650
+ ref_name: {
651
+ exclude: asStrings(refName.exclude).sort(),
652
+ include: asStrings(refName.include).sort(),
653
+ },
654
+ },
655
+ enforcement: ruleset.enforcement,
656
+ name: ruleset.name,
657
+ rules,
658
+ target: ruleset.target,
659
+ };
660
+ }
661
+ function existingReviewerPayload(ids) {
662
+ return ids.map((id) => ({ id, type: 'User' }));
663
+ }
664
+ function rulesetMatchesDesired(ruleset) {
665
+ return (JSON.stringify(normalizeRuleset(ruleset)) ===
666
+ JSON.stringify(normalizeRuleset(desiredRuleset())));
667
+ }
668
+ async function applyChanges(api, repositoryPath, state, productionApprovalMode, productionReviewerIds) {
669
+ if (state.repo.allow_merge_commit !== true ||
670
+ state.repo.allow_rebase_merge !== false ||
671
+ state.repo.allow_squash_merge !== false) {
672
+ await api.request('PATCH', repositoryPath, {
673
+ allow_merge_commit: true,
674
+ allow_rebase_merge: false,
675
+ allow_squash_merge: false,
676
+ });
677
+ }
678
+ const desired = desiredRuleset();
679
+ if (state.ownedRuleset &&
680
+ typeof state.ownedRuleset.id === 'number' &&
681
+ !rulesetMatchesDesired(state.ownedRuleset)) {
682
+ await api.request('PUT', `${repositoryPath}/rulesets/${state.ownedRuleset.id}`, desired);
683
+ }
684
+ else if (!state.ownedRuleset) {
685
+ await api.request('POST', `${repositoryPath}/rulesets`, desired);
686
+ }
687
+ for (const [environment, branch] of Object.entries(ENVIRONMENT_BRANCHES)) {
688
+ const current = state.environments[environment];
689
+ const reviewers = environment === 'production' &&
690
+ productionApprovalMode === 'environment-reviewer' &&
691
+ productionReviewerIds.length > 0
692
+ ? existingReviewerPayload(productionReviewerIds)
693
+ : current.reviewers;
694
+ const desiredPreventSelfReview = environment === 'production' &&
695
+ productionApprovalMode === 'environment-reviewer'
696
+ ? true
697
+ : current.preventSelfReview;
698
+ const reviewersChanged = JSON.stringify(reviewers) !== JSON.stringify(current.reviewers);
699
+ if (!current.exists ||
700
+ !current.customBranchPolicies ||
701
+ reviewersChanged ||
702
+ desiredPreventSelfReview !== current.preventSelfReview) {
703
+ const configureProductionReviewers = environment === 'production' &&
704
+ productionApprovalMode === 'environment-reviewer';
705
+ const preserveEnvironmentProtection = configureProductionReviewers ||
706
+ reviewers.length > 0 ||
707
+ current.waitTimer > 0;
708
+ await api.request('PUT', `${repositoryPath}/environments/${encodeURIComponent(environment)}`, {
709
+ deployment_branch_policy: {
710
+ custom_branch_policies: true,
711
+ protected_branches: false,
712
+ },
713
+ ...(preserveEnvironmentProtection
714
+ ? {
715
+ prevent_self_review: desiredPreventSelfReview,
716
+ reviewers,
717
+ wait_timer: current.waitTimer,
718
+ }
719
+ : {}),
720
+ });
721
+ }
722
+ if (!current.branchPolicies.includes(branch)) {
723
+ await api.request('POST', `${repositoryPath}/environments/${encodeURIComponent(environment)}/deployment-branch-policies`, { name: branch, type: 'branch' });
724
+ }
725
+ }
726
+ }
727
+ async function runGitHubBootstrap(options) {
728
+ const repository = options.repository.trim();
729
+ const { owner, repo } = parseRepository(repository);
730
+ if (!options.token.trim()) {
731
+ throw new Error('GH_TOKEN or GITHUB_TOKEN is required for GitHub bootstrap.');
732
+ }
733
+ if (!index_js_1.PRODUCTION_APPROVAL_MODES.includes(options.productionApprovalMode)) {
734
+ throw new Error('Production approval mode must be the explicit committed value promotion-pr or environment-reviewer.');
735
+ }
736
+ const productionReviewerIds = [
737
+ ...new Set(options.productionReviewerIds ?? []),
738
+ ].sort((left, right) => left - right);
739
+ if (productionReviewerIds.some((id) => !Number.isSafeInteger(id) || id <= 0)) {
740
+ throw new Error('Production reviewer IDs must be positive integers.');
741
+ }
742
+ if (options.productionApprovalMode === 'promotion-pr' &&
743
+ productionReviewerIds.length > 0) {
744
+ throw new Error('Production reviewer IDs are valid only when committed policy selects environment-reviewer mode.');
745
+ }
746
+ const api = new GitHubApi(options.apiBaseUrl?.trim() || 'https://api.github.com', options.token);
747
+ const repositoryPath = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
748
+ const initial = await inspect(api, repositoryPath, options.productionApprovalMode);
749
+ const report = evaluate(repository, initial.state, initial.blockers, options.productionApprovalMode, productionReviewerIds);
750
+ if (!options.apply)
751
+ return report;
752
+ if (report.blockers.length > 0) {
753
+ throw new Error(`GitHub bootstrap apply is blocked:\n- ${report.blockers.join('\n- ')}`);
754
+ }
755
+ await applyChanges(api, repositoryPath, initial.state, options.productionApprovalMode, productionReviewerIds);
756
+ const completed = await inspect(api, repositoryPath, options.productionApprovalMode);
757
+ return evaluate(repository, completed.state, completed.blockers, options.productionApprovalMode, productionReviewerIds);
758
+ }
759
+ //# sourceMappingURL=github-bootstrap.js.map