@standards-kit/conform 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/artifactregistry-QQWBMEQN.js +38 -0
  2. package/dist/artifactregistry-QQWBMEQN.js.map +1 -0
  3. package/dist/chunk-J5S6GRGW.js +314 -0
  4. package/dist/chunk-J5S6GRGW.js.map +1 -0
  5. package/dist/chunk-KHO6NIAI.js +1367 -0
  6. package/dist/chunk-KHO6NIAI.js.map +1 -0
  7. package/dist/chunk-M7G73Q6P.js +662 -0
  8. package/dist/chunk-M7G73Q6P.js.map +1 -0
  9. package/dist/chunk-P7TIZJ4C.js +85 -0
  10. package/dist/chunk-P7TIZJ4C.js.map +1 -0
  11. package/dist/chunk-RXA4FO7L.js +279 -0
  12. package/dist/chunk-RXA4FO7L.js.map +1 -0
  13. package/dist/cli.js +7432 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/cloudrun-O36R23SH.js +31 -0
  16. package/dist/cloudrun-O36R23SH.js.map +1 -0
  17. package/dist/cloudwatch-KSZ4A256.js +56 -0
  18. package/dist/cloudwatch-KSZ4A256.js.map +1 -0
  19. package/dist/dynamodb-5KVESCVJ.js +51 -0
  20. package/dist/dynamodb-5KVESCVJ.js.map +1 -0
  21. package/dist/ec2-HKPE6GZV.js +151 -0
  22. package/dist/ec2-HKPE6GZV.js.map +1 -0
  23. package/dist/ecs-OS3NJZTA.js +141 -0
  24. package/dist/ecs-OS3NJZTA.js.map +1 -0
  25. package/dist/elasticache-7TCRHYYM.js +151 -0
  26. package/dist/elasticache-7TCRHYYM.js.map +1 -0
  27. package/dist/elb-PEDLXW5R.js +151 -0
  28. package/dist/elb-PEDLXW5R.js.map +1 -0
  29. package/dist/generate-D4MFMOHP.js +28 -0
  30. package/dist/generate-D4MFMOHP.js.map +1 -0
  31. package/dist/iam-7H5HFWVQ.js +96 -0
  32. package/dist/iam-7H5HFWVQ.js.map +1 -0
  33. package/dist/iam-DJI64AGK.js +39 -0
  34. package/dist/iam-DJI64AGK.js.map +1 -0
  35. package/dist/index.js +7980 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/infra-UXM5XQX3.js +566 -0
  38. package/dist/infra-UXM5XQX3.js.map +1 -0
  39. package/dist/lambda-NFB5UILT.js +60 -0
  40. package/dist/lambda-NFB5UILT.js.map +1 -0
  41. package/dist/manifest-7AIL2FK2.js +23 -0
  42. package/dist/manifest-7AIL2FK2.js.map +1 -0
  43. package/dist/mcp-O5O7XVFG.js +204 -0
  44. package/dist/mcp-O5O7XVFG.js.map +1 -0
  45. package/dist/rds-KLG5O5SI.js +151 -0
  46. package/dist/rds-KLG5O5SI.js.map +1 -0
  47. package/dist/registry-V65CC7IN.js +15 -0
  48. package/dist/registry-V65CC7IN.js.map +1 -0
  49. package/dist/s3-2DH7PRVR.js +49 -0
  50. package/dist/s3-2DH7PRVR.js.map +1 -0
  51. package/dist/scan-EELS42BP.js +593 -0
  52. package/dist/scan-EELS42BP.js.map +1 -0
  53. package/dist/secretmanager-RDL62EFW.js +31 -0
  54. package/dist/secretmanager-RDL62EFW.js.map +1 -0
  55. package/dist/secretsmanager-MOOIHLAO.js +50 -0
  56. package/dist/secretsmanager-MOOIHLAO.js.map +1 -0
  57. package/dist/sns-Y36LVTWA.js +50 -0
  58. package/dist/sns-Y36LVTWA.js.map +1 -0
  59. package/dist/sqs-RRS3GRHK.js +61 -0
  60. package/dist/sqs-RRS3GRHK.js.map +1 -0
  61. package/dist/src-KZRTG3EU.js +45 -0
  62. package/dist/src-KZRTG3EU.js.map +1 -0
  63. package/dist/standards-RXK5G4IG.js +37 -0
  64. package/dist/standards-RXK5G4IG.js.map +1 -0
  65. package/dist/sync-RLYBGYNY.js +877 -0
  66. package/dist/sync-RLYBGYNY.js.map +1 -0
  67. package/dist/validate-AABLVQJS.js +327 -0
  68. package/dist/validate-AABLVQJS.js.map +1 -0
  69. package/dist/validator-6PL5I5EC.js +156 -0
  70. package/dist/validator-6PL5I5EC.js.map +1 -0
  71. package/package.json +91 -0
@@ -0,0 +1,877 @@
1
+ import "./chunk-P7TIZJ4C.js";
2
+ import {
3
+ getProjectRoot,
4
+ loadConfig
5
+ } from "./chunk-KHO6NIAI.js";
6
+
7
+ // src/process/sync/applier.ts
8
+ import { execa } from "execa";
9
+ var ApplierError = class extends Error {
10
+ constructor(message, code) {
11
+ super(message);
12
+ this.code = code;
13
+ this.name = "ApplierError";
14
+ }
15
+ };
16
+ async function applyBranchProtection(repoInfo, branch, desired, diffResult) {
17
+ if (!diffResult.hasChanges) {
18
+ return { success: true, applied: [], failed: [] };
19
+ }
20
+ const requestBody = buildBranchRulesetBody(branch, desired);
21
+ try {
22
+ if (diffResult.currentRulesetId === null) {
23
+ await execa(
24
+ "gh",
25
+ ["api", `repos/${repoInfo.owner}/${repoInfo.repo}/rulesets`, "-X", "POST", "--input", "-"],
26
+ { input: JSON.stringify(requestBody) }
27
+ );
28
+ } else {
29
+ await execa(
30
+ "gh",
31
+ [
32
+ "api",
33
+ `repos/${repoInfo.owner}/${repoInfo.repo}/rulesets/${diffResult.currentRulesetId}`,
34
+ "-X",
35
+ "PUT",
36
+ "--input",
37
+ "-"
38
+ ],
39
+ { input: JSON.stringify(requestBody) }
40
+ );
41
+ }
42
+ return { success: true, applied: diffResult.diffs, failed: [] };
43
+ } catch (error) {
44
+ return handleBranchApplyError(error, diffResult.diffs);
45
+ }
46
+ }
47
+ function handleBranchApplyError(error, diffs) {
48
+ const errorMessage = error instanceof Error ? error.message : String(error);
49
+ if (errorMessage.includes("403") || errorMessage.includes("Must have admin rights")) {
50
+ throw new ApplierError(
51
+ "Cannot update branch protection: insufficient permissions (requires admin access)",
52
+ "NO_PERMISSION"
53
+ );
54
+ }
55
+ return {
56
+ success: false,
57
+ applied: [],
58
+ failed: diffs.map((diff) => ({ diff, error: errorMessage }))
59
+ };
60
+ }
61
+ function buildBranchRulesetBody(branch, desired) {
62
+ const rules = [];
63
+ const pullRequestRule = buildPullRequestRule(desired);
64
+ if (pullRequestRule) {
65
+ rules.push(pullRequestRule);
66
+ }
67
+ const statusChecksRule = buildStatusChecksRule(desired);
68
+ if (statusChecksRule) {
69
+ rules.push(statusChecksRule);
70
+ }
71
+ if (desired.require_signed_commits === true) {
72
+ rules.push({ type: "required_signatures" });
73
+ }
74
+ const bypassActors = desired.bypass_actors?.map((actor) => ({
75
+ actor_id: actor.actor_id ?? null,
76
+ actor_type: actor.actor_type,
77
+ bypass_mode: actor.bypass_mode ?? "always"
78
+ })) ?? [];
79
+ return {
80
+ name: "Branch Protection",
81
+ target: "branch",
82
+ enforcement: "active",
83
+ conditions: {
84
+ ref_name: {
85
+ include: [`refs/heads/${branch}`],
86
+ exclude: []
87
+ }
88
+ },
89
+ bypass_actors: bypassActors,
90
+ rules
91
+ };
92
+ }
93
+ function buildPullRequestRule(desired) {
94
+ const hasReviewSettings = desired.required_reviews !== void 0 || desired.dismiss_stale_reviews !== void 0 || desired.require_code_owner_reviews !== void 0;
95
+ if (!hasReviewSettings) {
96
+ return null;
97
+ }
98
+ return {
99
+ type: "pull_request",
100
+ parameters: {
101
+ ...desired.required_reviews !== void 0 && {
102
+ required_approving_review_count: desired.required_reviews
103
+ },
104
+ ...desired.dismiss_stale_reviews !== void 0 && {
105
+ dismiss_stale_reviews_on_push: desired.dismiss_stale_reviews
106
+ },
107
+ ...desired.require_code_owner_reviews !== void 0 && {
108
+ require_code_owner_review: desired.require_code_owner_reviews
109
+ }
110
+ }
111
+ };
112
+ }
113
+ function buildStatusChecksRule(desired) {
114
+ const hasStatusSettings = desired.require_status_checks !== void 0 || desired.require_branches_up_to_date !== void 0;
115
+ if (!hasStatusSettings) {
116
+ return null;
117
+ }
118
+ const statusChecks = desired.require_status_checks?.map((context) => ({
119
+ context
120
+ })) ?? [];
121
+ return {
122
+ type: "required_status_checks",
123
+ parameters: {
124
+ required_status_checks: statusChecks,
125
+ strict_required_status_checks_policy: desired.require_branches_up_to_date ?? false
126
+ }
127
+ };
128
+ }
129
+ async function applyTagProtection(repoInfo, desired, diffResult) {
130
+ if (!diffResult.hasChanges) {
131
+ return { success: true, applied: [], failed: [] };
132
+ }
133
+ const requestBody = buildTagRulesetBody(desired);
134
+ try {
135
+ if (diffResult.currentRulesetId === null) {
136
+ await execa(
137
+ "gh",
138
+ ["api", `repos/${repoInfo.owner}/${repoInfo.repo}/rulesets`, "-X", "POST", "--input", "-"],
139
+ { input: JSON.stringify(requestBody) }
140
+ );
141
+ } else {
142
+ await execa(
143
+ "gh",
144
+ [
145
+ "api",
146
+ `repos/${repoInfo.owner}/${repoInfo.repo}/rulesets/${diffResult.currentRulesetId}`,
147
+ "-X",
148
+ "PUT",
149
+ "--input",
150
+ "-"
151
+ ],
152
+ { input: JSON.stringify(requestBody) }
153
+ );
154
+ }
155
+ return { success: true, applied: diffResult.diffs, failed: [] };
156
+ } catch (error) {
157
+ return handleTagApplyError(error, diffResult.diffs);
158
+ }
159
+ }
160
+ function buildTagRulesetBody(desired) {
161
+ const rules = [];
162
+ if (desired.prevent_deletion !== false) {
163
+ rules.push({ type: "deletion" });
164
+ }
165
+ if (desired.prevent_update !== false) {
166
+ rules.push({ type: "update" });
167
+ }
168
+ const patterns = desired.patterns ?? ["v*"];
169
+ const includePatterns = patterns.map((p) => `refs/tags/${p}`);
170
+ return {
171
+ name: "Tag Protection",
172
+ target: "tag",
173
+ enforcement: "active",
174
+ conditions: {
175
+ ref_name: {
176
+ include: includePatterns,
177
+ exclude: []
178
+ }
179
+ },
180
+ rules
181
+ };
182
+ }
183
+ function handleTagApplyError(error, diffs) {
184
+ const errorMessage = error instanceof Error ? error.message : String(error);
185
+ if (errorMessage.includes("403") || errorMessage.includes("Must have admin rights")) {
186
+ throw new ApplierError(
187
+ "Cannot update tag protection: insufficient permissions (requires admin access)",
188
+ "NO_PERMISSION"
189
+ );
190
+ }
191
+ return {
192
+ success: false,
193
+ applied: [],
194
+ failed: diffs.map((diff) => ({ diff, error: errorMessage }))
195
+ };
196
+ }
197
+
198
+ // src/process/sync/differ.ts
199
+ var fieldMappings = [
200
+ {
201
+ name: "required_reviews",
202
+ getCurrentValue: (c) => c.requiredReviews,
203
+ getDesiredValue: (d) => d.required_reviews
204
+ },
205
+ {
206
+ name: "dismiss_stale_reviews",
207
+ getCurrentValue: (c) => c.dismissStaleReviews,
208
+ getDesiredValue: (d) => d.dismiss_stale_reviews
209
+ },
210
+ {
211
+ name: "require_code_owner_reviews",
212
+ getCurrentValue: (c) => c.requireCodeOwnerReviews,
213
+ getDesiredValue: (d) => d.require_code_owner_reviews
214
+ },
215
+ {
216
+ name: "require_status_checks",
217
+ getCurrentValue: (c) => c.requiredStatusChecks,
218
+ getDesiredValue: (d) => d.require_status_checks,
219
+ isArray: true
220
+ },
221
+ {
222
+ name: "require_branches_up_to_date",
223
+ getCurrentValue: (c) => c.requireBranchesUpToDate,
224
+ getDesiredValue: (d) => d.require_branches_up_to_date
225
+ },
226
+ {
227
+ name: "require_signed_commits",
228
+ getCurrentValue: (c) => c.requireSignedCommits,
229
+ getDesiredValue: (d) => d.require_signed_commits
230
+ },
231
+ {
232
+ name: "enforce_admins",
233
+ getCurrentValue: (c) => c.enforceAdmins,
234
+ getDesiredValue: (d) => d.enforce_admins
235
+ }
236
+ ];
237
+ function computeDiff(repoInfo, current, desired) {
238
+ const diffs = collectDiffs(current, desired);
239
+ const bypassDiff = compareBypassActors(
240
+ current.bypassActors,
241
+ desired.bypass_actors,
242
+ current.rulesetId
243
+ );
244
+ if (bypassDiff) {
245
+ diffs.push(bypassDiff);
246
+ }
247
+ return {
248
+ repoInfo,
249
+ branch: current.branch,
250
+ diffs,
251
+ hasChanges: diffs.length > 0,
252
+ currentRulesetId: current.rulesetId
253
+ };
254
+ }
255
+ function collectDiffs(current, desired) {
256
+ const diffs = [];
257
+ for (const mapping of fieldMappings) {
258
+ const desiredValue = mapping.getDesiredValue(desired);
259
+ if (desiredValue === void 0) {
260
+ continue;
261
+ }
262
+ const currentValue = mapping.getCurrentValue(current);
263
+ const diff = mapping.isArray ? compareArrayValue(mapping.name, currentValue, desiredValue) : compareValue(mapping.name, currentValue, desiredValue);
264
+ if (diff) {
265
+ diffs.push(diff);
266
+ }
267
+ }
268
+ return diffs;
269
+ }
270
+ function compareValue(setting, current, desired) {
271
+ const currentValue = current ?? null;
272
+ if (currentValue === desired) {
273
+ return null;
274
+ }
275
+ return {
276
+ setting,
277
+ current: currentValue,
278
+ desired,
279
+ action: currentValue === null ? "add" : "change"
280
+ };
281
+ }
282
+ function compareArrayValue(setting, current, desired) {
283
+ const currentArray = current ?? [];
284
+ const sortedCurrent = [...currentArray].sort();
285
+ const sortedDesired = [...desired].sort();
286
+ const areEqual = sortedCurrent.length === sortedDesired.length && sortedCurrent.every((v, i) => v === sortedDesired[i]);
287
+ if (areEqual) {
288
+ return null;
289
+ }
290
+ return {
291
+ setting,
292
+ current: currentArray,
293
+ desired,
294
+ action: currentArray.length === 0 ? "add" : "change"
295
+ };
296
+ }
297
+ function compareBypassActors(current, desired, rulesetId) {
298
+ if (desired === void 0) {
299
+ return null;
300
+ }
301
+ const currentActors = current ?? [];
302
+ const sortKey = (a) => `${a.actor_type}:${a.actor_id ?? ""}:${a.bypass_mode ?? "always"}`;
303
+ const sortedCurrent = [...currentActors].sort((a, b) => sortKey(a).localeCompare(sortKey(b)));
304
+ const sortedDesired = [...desired].sort((a, b) => sortKey(a).localeCompare(sortKey(b)));
305
+ const normalize = (actors) => actors.map((a) => ({
306
+ actor_type: a.actor_type,
307
+ actor_id: a.actor_id,
308
+ bypass_mode: a.bypass_mode ?? "always"
309
+ }));
310
+ const normalizedCurrent = normalize(sortedCurrent);
311
+ const normalizedDesired = normalize(sortedDesired);
312
+ const areEqual = normalizedCurrent.length === normalizedDesired.length && normalizedCurrent.every(
313
+ (c, i) => c.actor_type === normalizedDesired[i].actor_type && c.actor_id === normalizedDesired[i].actor_id && c.bypass_mode === normalizedDesired[i].bypass_mode
314
+ );
315
+ if (areEqual) {
316
+ return null;
317
+ }
318
+ return {
319
+ setting: "bypass_actors",
320
+ current: currentActors,
321
+ desired,
322
+ action: rulesetId === null ? "add" : "change"
323
+ };
324
+ }
325
+ function formatValue(value) {
326
+ if (value === null || value === void 0) {
327
+ return "not set";
328
+ }
329
+ if (Array.isArray(value)) {
330
+ return value.length === 0 ? "[]" : `[${value.join(", ")}]`;
331
+ }
332
+ return String(value);
333
+ }
334
+ function computeTagDiff(repoInfo, current, desired) {
335
+ const diffs = [];
336
+ const rulesetId = current.rulesetId;
337
+ collectPatternDiff(diffs, current, desired);
338
+ collectBooleanDiff(diffs, {
339
+ s: "prevent_deletion",
340
+ c: current.preventDeletion,
341
+ d: desired.prevent_deletion,
342
+ r: rulesetId
343
+ });
344
+ collectBooleanDiff(diffs, {
345
+ s: "prevent_update",
346
+ c: current.preventUpdate,
347
+ d: desired.prevent_update,
348
+ r: rulesetId
349
+ });
350
+ return { repoInfo, diffs, hasChanges: diffs.length > 0, currentRulesetId: rulesetId };
351
+ }
352
+ function collectPatternDiff(diffs, current, desired) {
353
+ if (desired.patterns === void 0) {
354
+ return;
355
+ }
356
+ const curr = [...current.patterns].sort();
357
+ const des = [...desired.patterns].sort();
358
+ const match = curr.length === des.length && curr.every((v, i) => v === des[i]);
359
+ if (!match) {
360
+ diffs.push({
361
+ setting: "patterns",
362
+ current: current.patterns,
363
+ desired: desired.patterns,
364
+ action: curr.length === 0 ? "add" : "change"
365
+ });
366
+ }
367
+ }
368
+ function collectBooleanDiff(diffs, o) {
369
+ if (o.d !== void 0 && o.c !== o.d) {
370
+ diffs.push({
371
+ setting: o.s,
372
+ current: o.c,
373
+ desired: o.d,
374
+ action: o.r === null ? "add" : "change"
375
+ });
376
+ }
377
+ }
378
+
379
+ // src/process/sync/fetcher.ts
380
+ import { execa as execa2 } from "execa";
381
+ var FetcherError = class extends Error {
382
+ constructor(message, code) {
383
+ super(message);
384
+ this.code = code;
385
+ this.name = "FetcherError";
386
+ }
387
+ };
388
+ async function isGhAvailable() {
389
+ try {
390
+ await execa2("gh", ["--version"]);
391
+ return true;
392
+ } catch {
393
+ return false;
394
+ }
395
+ }
396
+ async function getRepoInfo(projectRoot) {
397
+ try {
398
+ const result = await execa2("gh", ["repo", "view", "--json", "owner,name"], {
399
+ cwd: projectRoot
400
+ });
401
+ const data = JSON.parse(result.stdout);
402
+ return { owner: data.owner.login, repo: data.name };
403
+ } catch {
404
+ throw new FetcherError("Could not determine GitHub repository from git remote", "NO_REPO");
405
+ }
406
+ }
407
+ async function fetchBranchProtection(repoInfo, branch) {
408
+ try {
409
+ const result = await execa2("gh", ["api", `repos/${repoInfo.owner}/${repoInfo.repo}/rulesets`]);
410
+ const rulesets = JSON.parse(result.stdout);
411
+ return parseBranchRuleset(rulesets, branch);
412
+ } catch (error) {
413
+ return handleBranchFetchError(error, branch);
414
+ }
415
+ }
416
+ function handleBranchFetchError(error, branch) {
417
+ const errorMessage = error instanceof Error ? error.message : String(error);
418
+ if (errorMessage.includes("404")) {
419
+ return createEmptySettings(branch);
420
+ }
421
+ if (errorMessage.includes("403") || errorMessage.includes("Must have admin rights")) {
422
+ throw new FetcherError(
423
+ "Cannot read branch protection: insufficient permissions (requires admin access)",
424
+ "NO_PERMISSION"
425
+ );
426
+ }
427
+ throw new FetcherError(`Failed to fetch branch protection: ${errorMessage}`, "API_ERROR");
428
+ }
429
+ function parseBranchRuleset(rulesets, branch) {
430
+ const branchRuleset = rulesets.find(
431
+ (r) => r.target === "branch" && r.enforcement === "active" && matchesBranch(r.conditions?.ref_name?.include ?? [], branch)
432
+ );
433
+ if (!branchRuleset) {
434
+ return createEmptySettings(branch);
435
+ }
436
+ const rules = branchRuleset.rules ?? [];
437
+ const prRule = rules.find((r) => r.type === "pull_request");
438
+ const statusRule = rules.find((r) => r.type === "required_status_checks");
439
+ const signaturesRule = rules.find((r) => r.type === "required_signatures");
440
+ const bypassActors = parseBypassActors(branchRuleset.bypass_actors);
441
+ const enforceAdmins = bypassActors === null || bypassActors.length === 0;
442
+ return {
443
+ branch,
444
+ requiredReviews: prRule?.parameters?.required_approving_review_count ?? null,
445
+ dismissStaleReviews: prRule?.parameters?.dismiss_stale_reviews_on_push ?? null,
446
+ requireCodeOwnerReviews: prRule?.parameters?.require_code_owner_review ?? null,
447
+ requiredStatusChecks: statusRule?.parameters?.required_status_checks?.map((c) => c.context) ?? null,
448
+ requireBranchesUpToDate: statusRule?.parameters?.strict_required_status_checks_policy ?? null,
449
+ requireSignedCommits: signaturesRule !== void 0,
450
+ enforceAdmins,
451
+ bypassActors,
452
+ rulesetId: branchRuleset.id,
453
+ rulesetName: branchRuleset.name
454
+ };
455
+ }
456
+ function matchesBranch(patterns, branch) {
457
+ for (const pattern of patterns) {
458
+ const cleanPattern = pattern.replace(/^refs\/heads\//, "");
459
+ if (cleanPattern === branch) {
460
+ return true;
461
+ }
462
+ if (cleanPattern === "~DEFAULT_BRANCH" && branch === "main") {
463
+ return true;
464
+ }
465
+ if (cleanPattern === "~ALL") {
466
+ return true;
467
+ }
468
+ if (cleanPattern.includes("*")) {
469
+ const regex = new RegExp(`^${cleanPattern.replace(/\*/g, ".*")}$`);
470
+ if (regex.test(branch)) {
471
+ return true;
472
+ }
473
+ }
474
+ }
475
+ return false;
476
+ }
477
+ function parseBypassActors(actors) {
478
+ if (!actors || actors.length === 0) {
479
+ return null;
480
+ }
481
+ return actors.map((actor) => ({
482
+ actor_type: actor.actor_type,
483
+ actor_id: actor.actor_id ?? void 0,
484
+ bypass_mode: actor.bypass_mode
485
+ }));
486
+ }
487
+ function createEmptySettings(branch) {
488
+ return {
489
+ branch,
490
+ requiredReviews: null,
491
+ dismissStaleReviews: null,
492
+ requireCodeOwnerReviews: null,
493
+ requiredStatusChecks: null,
494
+ requireBranchesUpToDate: null,
495
+ requireSignedCommits: null,
496
+ enforceAdmins: null,
497
+ bypassActors: null,
498
+ rulesetId: null,
499
+ rulesetName: null
500
+ };
501
+ }
502
+ async function fetchTagProtection(repoInfo) {
503
+ try {
504
+ const result = await execa2("gh", ["api", `repos/${repoInfo.owner}/${repoInfo.repo}/rulesets`]);
505
+ const rulesets = JSON.parse(result.stdout);
506
+ return parseTagRuleset(rulesets);
507
+ } catch (error) {
508
+ return handleTagFetchError(error);
509
+ }
510
+ }
511
+ function parseTagRuleset(rulesets) {
512
+ const tagRuleset = rulesets.find((r) => r.target === "tag" && r.name === "Tag Protection");
513
+ if (!tagRuleset) {
514
+ return createEmptyTagSettings();
515
+ }
516
+ const patterns = tagRuleset.conditions?.ref_name?.include?.map((p) => p.replace(/^refs\/tags\//, "")) ?? [];
517
+ const rules = tagRuleset.rules ?? [];
518
+ const preventDeletion = rules.some((r) => r.type === "deletion");
519
+ const preventUpdate = rules.some((r) => r.type === "update");
520
+ return {
521
+ patterns,
522
+ preventDeletion,
523
+ preventUpdate,
524
+ rulesetId: tagRuleset.id,
525
+ rulesetName: tagRuleset.name
526
+ };
527
+ }
528
+ function createEmptyTagSettings() {
529
+ return {
530
+ patterns: [],
531
+ preventDeletion: false,
532
+ preventUpdate: false,
533
+ rulesetId: null,
534
+ rulesetName: null
535
+ };
536
+ }
537
+ function handleTagFetchError(error) {
538
+ const errorMessage = error instanceof Error ? error.message : String(error);
539
+ if (errorMessage.includes("404")) {
540
+ return createEmptyTagSettings();
541
+ }
542
+ if (errorMessage.includes("403") || errorMessage.includes("Must have admin rights")) {
543
+ throw new FetcherError(
544
+ "Cannot read tag protection: insufficient permissions (requires admin access)",
545
+ "NO_PERMISSION"
546
+ );
547
+ }
548
+ throw new FetcherError(`Failed to fetch tag protection: ${errorMessage}`, "API_ERROR");
549
+ }
550
+
551
+ // src/process/sync/index.ts
552
+ function writeLine(text) {
553
+ process.stdout.write(`${text}
554
+ `);
555
+ }
556
+ async function runDiff(options) {
557
+ try {
558
+ const diffResult = await getDiffResult(options);
559
+ outputDiff(diffResult, options.format);
560
+ process.exit(diffResult.hasChanges ? 1 : 0);
561
+ } catch (error) {
562
+ handleError(error, options.format);
563
+ }
564
+ }
565
+ async function runSync(options) {
566
+ try {
567
+ const diffResult = await getDiffResult(options);
568
+ if (!diffResult.hasChanges) {
569
+ outputNoChanges(diffResult, options.format);
570
+ process.exit(0);
571
+ }
572
+ if (options.validateActors) {
573
+ const validationPassed = await validateActorsBeforeApply(options);
574
+ if (!validationPassed) {
575
+ process.exit(1);
576
+ }
577
+ }
578
+ if (!options.apply) {
579
+ outputPreview(diffResult, options.format);
580
+ process.exit(0);
581
+ }
582
+ const result = await applyChanges(options, diffResult);
583
+ outputSyncResult(diffResult, result, options.format);
584
+ process.exit(result.success ? 0 : 1);
585
+ } catch (error) {
586
+ handleError(error, options.format);
587
+ }
588
+ }
589
+ async function applyChanges(options, diffResult) {
590
+ const { config } = loadConfig(options.config);
591
+ const projectRoot = getProjectRoot(loadConfig(options.config).configPath);
592
+ const repoInfo = await getRepoInfo(projectRoot);
593
+ const desired = config.process?.repo?.ruleset ?? {};
594
+ return applyBranchProtection(repoInfo, diffResult.branch, desired, diffResult);
595
+ }
596
+ async function validateActorsBeforeApply(options) {
597
+ const { validateBypassActors, formatValidationResult } = await import("./validator-6PL5I5EC.js");
598
+ const { config } = loadConfig(options.config);
599
+ const projectRoot = getProjectRoot(loadConfig(options.config).configPath);
600
+ const repoInfo = await getRepoInfo(projectRoot);
601
+ const rulesetConfig = config.process?.repo?.ruleset;
602
+ const actors = rulesetConfig?.bypass_actors ?? [];
603
+ if (actors.length === 0) {
604
+ return true;
605
+ }
606
+ const result = await validateBypassActors(repoInfo, actors);
607
+ if (options.format === "json") {
608
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
609
+ `);
610
+ } else {
611
+ process.stdout.write(`${formatValidationResult(result)}
612
+ `);
613
+ }
614
+ return result.valid;
615
+ }
616
+ async function getDiffResult(options) {
617
+ if (!await isGhAvailable()) {
618
+ throw new FetcherError("GitHub CLI (gh) not available", "NO_GH");
619
+ }
620
+ const { config, configPath } = loadConfig(options.config);
621
+ const projectRoot = getProjectRoot(configPath);
622
+ const repoInfo = await getRepoInfo(projectRoot);
623
+ const repoConfig = config.process?.repo;
624
+ const desired = repoConfig?.ruleset;
625
+ if (!desired) {
626
+ throw new Error("No [process.repo.ruleset] configured in standards.toml");
627
+ }
628
+ const branch = getBranch(desired.branch);
629
+ const current = await fetchBranchProtection(repoInfo, branch);
630
+ return computeDiff(repoInfo, current, desired);
631
+ }
632
+ function getBranch(configuredBranch) {
633
+ return configuredBranch ?? "main";
634
+ }
635
+ function outputDiff(result, format) {
636
+ if (format === "json") {
637
+ writeLine(JSON.stringify(result, null, 2));
638
+ } else {
639
+ outputDiffText(result);
640
+ }
641
+ }
642
+ function outputDiffText(result) {
643
+ writeRepoHeader(result);
644
+ if (!result.hasChanges) {
645
+ writeLine("No changes needed. Settings match configuration.");
646
+ return;
647
+ }
648
+ writeDiffTable(result);
649
+ writeLine("");
650
+ writeLine(
651
+ `${result.diffs.length} setting(s) differ. Run 'conform process sync --apply' to apply changes.`
652
+ );
653
+ }
654
+ function writeRepoHeader(result) {
655
+ writeLine(`Repository: ${result.repoInfo.owner}/${result.repoInfo.repo}`);
656
+ writeLine(`Branch: ${result.branch}`);
657
+ writeLine("");
658
+ }
659
+ function writeDiffTable(result) {
660
+ const settingWidth = Math.max(...result.diffs.map((d) => d.setting.length), 7);
661
+ const currentWidth = Math.max(...result.diffs.map((d) => formatValue(d.current).length), 7);
662
+ writeLine(`${"Setting".padEnd(settingWidth)} ${"Current".padEnd(currentWidth)} Desired`);
663
+ writeLine("-".repeat(settingWidth + currentWidth + 20));
664
+ for (const diff of result.diffs) {
665
+ const currentStr = formatValue(diff.current);
666
+ const desiredStr = formatValue(diff.desired);
667
+ writeLine(
668
+ `${diff.setting.padEnd(settingWidth)} ${currentStr.padEnd(currentWidth)} ${desiredStr}`
669
+ );
670
+ }
671
+ }
672
+ function outputNoChanges(result, format) {
673
+ if (format === "json") {
674
+ writeLine(JSON.stringify({ ...result, message: "No changes needed" }, null, 2));
675
+ } else {
676
+ writeRepoHeader(result);
677
+ writeLine("No changes needed. Settings match configuration.");
678
+ }
679
+ }
680
+ function outputPreview(result, format) {
681
+ if (format === "json") {
682
+ writeLine(JSON.stringify({ ...result, preview: true }, null, 2));
683
+ } else {
684
+ writeRepoHeader(result);
685
+ writeLine("Would apply the following changes:");
686
+ for (const diff of result.diffs) {
687
+ writeLine(` ${diff.setting}: ${formatValue(diff.current)} -> ${formatValue(diff.desired)}`);
688
+ }
689
+ writeLine("");
690
+ writeLine("Run with --apply to make these changes.");
691
+ }
692
+ }
693
+ function outputSyncResult(diffResult, result, format) {
694
+ if (format === "json") {
695
+ writeLine(
696
+ JSON.stringify(
697
+ { repoInfo: diffResult.repoInfo, branch: diffResult.branch, ...result },
698
+ null,
699
+ 2
700
+ )
701
+ );
702
+ } else {
703
+ outputSyncResultText(diffResult, result);
704
+ }
705
+ }
706
+ function outputSyncResultText(diffResult, result) {
707
+ writeRepoHeader(diffResult);
708
+ writeLine("Applying changes...");
709
+ for (const diff of result.applied) {
710
+ writeLine(` + ${diff.setting}: ${formatValue(diff.current)} -> ${formatValue(diff.desired)}`);
711
+ }
712
+ for (const { diff, error } of result.failed) {
713
+ writeLine(` x ${diff.setting}: ${error}`);
714
+ }
715
+ writeLine("");
716
+ if (result.success) {
717
+ writeLine(`+ ${result.applied.length} setting(s) synchronized successfully.`);
718
+ } else {
719
+ writeLine(`x ${result.failed.length} setting(s) failed to sync.`);
720
+ }
721
+ }
722
+ function handleError(error, format) {
723
+ const { message, code } = extractErrorInfo(error);
724
+ if (format === "json") {
725
+ writeLine(JSON.stringify({ error: true, code, message }, null, 2));
726
+ } else {
727
+ writeLine(`Error: ${message}`);
728
+ }
729
+ process.exit(2);
730
+ }
731
+ function extractErrorInfo(error) {
732
+ if (error instanceof FetcherError) {
733
+ return { message: error.message, code: error.code };
734
+ }
735
+ if (error instanceof ApplierError) {
736
+ return { message: error.message, code: error.code };
737
+ }
738
+ if (error instanceof Error) {
739
+ return { message: error.message, code: "ERROR" };
740
+ }
741
+ return { message: String(error), code: "ERROR" };
742
+ }
743
+ async function runTagDiff(options) {
744
+ try {
745
+ const diffResult = await getTagDiffResult(options);
746
+ outputTagDiff(diffResult, options.format);
747
+ process.exit(diffResult.hasChanges ? 1 : 0);
748
+ } catch (error) {
749
+ handleError(error, options.format);
750
+ }
751
+ }
752
+ async function runTagSync(options) {
753
+ try {
754
+ const diffResult = await getTagDiffResult(options);
755
+ if (!diffResult.hasChanges) {
756
+ outputTagNoChanges(diffResult, options.format);
757
+ process.exit(0);
758
+ }
759
+ if (!options.apply) {
760
+ outputTagPreview(diffResult, options.format);
761
+ process.exit(0);
762
+ }
763
+ const result = await applyTagChanges(options, diffResult);
764
+ outputTagSyncResult(diffResult, result, options.format);
765
+ process.exit(result.success ? 0 : 1);
766
+ } catch (error) {
767
+ handleError(error, options.format);
768
+ }
769
+ }
770
+ async function getTagDiffResult(options) {
771
+ if (!await isGhAvailable()) {
772
+ throw new FetcherError("GitHub CLI (gh) not available", "NO_GH");
773
+ }
774
+ const { config, configPath } = loadConfig(options.config);
775
+ const projectRoot = getProjectRoot(configPath);
776
+ const repoInfo = await getRepoInfo(projectRoot);
777
+ const repoConfig = config.process?.repo;
778
+ if (!repoConfig?.tag_protection?.patterns || repoConfig.tag_protection.patterns.length === 0) {
779
+ throw new Error("No [process.repo.tag_protection] patterns configured in standards.toml");
780
+ }
781
+ const desired = repoConfig.tag_protection;
782
+ const current = await fetchTagProtection(repoInfo);
783
+ return computeTagDiff(repoInfo, current, desired);
784
+ }
785
+ async function applyTagChanges(options, diffResult) {
786
+ const { config } = loadConfig(options.config);
787
+ const desired = config.process?.repo?.tag_protection ?? {};
788
+ return applyTagProtection(diffResult.repoInfo, desired, diffResult);
789
+ }
790
+ function outputTagDiff(result, format) {
791
+ if (format === "json") {
792
+ writeLine(JSON.stringify(result, null, 2));
793
+ } else {
794
+ outputTagDiffText(result);
795
+ }
796
+ }
797
+ function outputTagDiffText(result) {
798
+ writeTagRepoHeader(result);
799
+ if (!result.hasChanges) {
800
+ writeLine("No changes needed. Tag protection settings match configuration.");
801
+ return;
802
+ }
803
+ writeTagDiffTable(result);
804
+ writeLine("");
805
+ writeLine(
806
+ `${result.diffs.length} setting(s) differ. Run 'conform process sync-tags --apply' to apply changes.`
807
+ );
808
+ }
809
+ function writeTagRepoHeader(result) {
810
+ writeLine(`Repository: ${result.repoInfo.owner}/${result.repoInfo.repo}`);
811
+ writeLine(`Target: Tag Protection Ruleset`);
812
+ writeLine("");
813
+ }
814
+ function writeTagDiffTable(result) {
815
+ const settingWidth = Math.max(...result.diffs.map((d) => d.setting.length), 7);
816
+ const currentWidth = Math.max(...result.diffs.map((d) => formatValue(d.current).length), 7);
817
+ writeLine(`${"Setting".padEnd(settingWidth)} ${"Current".padEnd(currentWidth)} Desired`);
818
+ writeLine("-".repeat(settingWidth + currentWidth + 20));
819
+ for (const diff of result.diffs) {
820
+ const currentStr = formatValue(diff.current);
821
+ const desiredStr = formatValue(diff.desired);
822
+ writeLine(
823
+ `${diff.setting.padEnd(settingWidth)} ${currentStr.padEnd(currentWidth)} ${desiredStr}`
824
+ );
825
+ }
826
+ }
827
+ function outputTagNoChanges(result, format) {
828
+ if (format === "json") {
829
+ writeLine(JSON.stringify({ ...result, message: "No changes needed" }, null, 2));
830
+ } else {
831
+ writeTagRepoHeader(result);
832
+ writeLine("No changes needed. Tag protection settings match configuration.");
833
+ }
834
+ }
835
+ function outputTagPreview(result, format) {
836
+ if (format === "json") {
837
+ writeLine(JSON.stringify({ ...result, preview: true }, null, 2));
838
+ } else {
839
+ writeTagRepoHeader(result);
840
+ writeLine("Would apply the following changes:");
841
+ for (const diff of result.diffs) {
842
+ writeLine(` ${diff.setting}: ${formatValue(diff.current)} -> ${formatValue(diff.desired)}`);
843
+ }
844
+ writeLine("");
845
+ writeLine("Run with --apply to make these changes.");
846
+ }
847
+ }
848
+ function outputTagSyncResult(diffResult, result, format) {
849
+ if (format === "json") {
850
+ writeLine(JSON.stringify({ repoInfo: diffResult.repoInfo, ...result }, null, 2));
851
+ } else {
852
+ outputTagSyncResultText(diffResult, result);
853
+ }
854
+ }
855
+ function outputTagSyncResultText(diffResult, result) {
856
+ writeTagRepoHeader(diffResult);
857
+ writeLine("Applying tag protection changes...");
858
+ for (const diff of result.applied) {
859
+ writeLine(` + ${diff.setting}: ${formatValue(diff.current)} -> ${formatValue(diff.desired)}`);
860
+ }
861
+ for (const { diff, error } of result.failed) {
862
+ writeLine(` x ${diff.setting}: ${error}`);
863
+ }
864
+ writeLine("");
865
+ if (result.success) {
866
+ writeLine(`+ ${result.applied.length} setting(s) synchronized successfully.`);
867
+ } else {
868
+ writeLine(`x ${result.failed.length} setting(s) failed to sync.`);
869
+ }
870
+ }
871
+ export {
872
+ runDiff,
873
+ runSync,
874
+ runTagDiff,
875
+ runTagSync
876
+ };
877
+ //# sourceMappingURL=sync-RLYBGYNY.js.map