@vivswan/github-settings-as-code 2.0.1-main.1090.g15ef0c9 → 2.0.1-main.1110.g63435fc

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.

Potentially problematic release.


This version of @vivswan/github-settings-as-code might be problematic. Click here for more details.

package/lib/pkg/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as SNAPSHOT_ONLY_INPUTS, E as parseSnapshotFileConfig, F as concludeMerge, I as concludeRun, Kt as redactRanges, L as failRun, Rt as GithubApi, S as SNAPSHOT_INPUTS, T as parseConfig, U as PRIVATE_REPORT_CHANNELS, Wt as maskRegistry, _ as INPUT_DECLS, d as runSnapshot, f as runSingle, j as readSettingsFile, k as runMulti, o as snapshotRepository, pt as SECTIONS, s as validateSettings, t as runMerge, tt as describeProblem, u as concludeSnapshot, v as MERGE_INPUTS, y as MERGE_ONLY_INPUTS, yt as sectionGrant } from "./src-BhuD5Ycj.js";
2
+ import { C as SNAPSHOT_ONLY_INPUTS, E as parseSnapshotFileConfig, F as concludeMerge, I as concludeRun, Kt as redactRanges, L as failRun, Rt as GithubApi, S as SNAPSHOT_INPUTS, T as parseConfig, U as PRIVATE_REPORT_CHANNELS, Wt as maskRegistry, _ as INPUT_DECLS, d as runSnapshot, f as runSingle, j as readSettingsFile, k as runMulti, o as snapshotRepository, pt as SECTIONS, s as validateSettings, t as runMerge, tt as describeProblem, u as concludeSnapshot, v as MERGE_INPUTS, y as MERGE_ONLY_INPUTS, yt as sectionGrant } from "./src-B13lb6Pp.js";
3
3
  import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
4
4
  import { dirname } from "node:path";
5
5
  import { ResultAsync, err, ok } from "neverthrow";
@@ -2307,6 +2307,22 @@ declare const GRAPHQL_OPS: {
2307
2307
  repo: string;
2308
2308
  } | undefined;
2309
2309
  };
2310
+ readonly pinsSnapshot: {
2311
+ readonly name: "EnvironmentPinsSnapshot";
2312
+ readonly kind: "read";
2313
+ readonly query: "query EnvironmentPinsSnapshot($owner: String!, $repo: String!, $cursor: String) { repository(owner: $owner, name: $repo) { pinnedEnvironments(first: 100, after: $cursor) { nodes { position environment { name } } pageInfo { hasNextPage endCursor } } } }";
2314
+ readonly connection: {
2315
+ readonly path: readonly ["repository", "pinnedEnvironments"];
2316
+ };
2317
+ readonly outcomes: {
2318
+ readonly ok: "the pinned environments with their 1-based positions, for the snapshot";
2319
+ };
2320
+ } & {
2321
+ readonly _variables?: {
2322
+ owner: string;
2323
+ repo: string;
2324
+ } | undefined;
2325
+ };
2310
2326
  readonly pin: {
2311
2327
  readonly name: "PinEnvironment";
2312
2328
  readonly kind: "write";
@@ -2428,6 +2444,22 @@ declare const GRAPHQL: {
2428
2444
  repo: string;
2429
2445
  } | undefined;
2430
2446
  };
2447
+ readonly rulesSnapshot: {
2448
+ readonly name: "BranchProtectionRulesSnapshot";
2449
+ readonly kind: "read";
2450
+ readonly connection: {
2451
+ readonly path: readonly ["repository", "branchProtectionRules"];
2452
+ };
2453
+ readonly outcomes: {
2454
+ readonly ok: "the repository's classic branch protection rules, for the snapshot";
2455
+ };
2456
+ readonly query: "query BranchProtectionRulesSnapshot($owner: String!, $repo: String!, $cursor: String) {\n repository(owner: $owner, name: $repo) {\n branchProtectionRules(first: 100, after: $cursor) {\n nodes {\n id\n pattern\n isAdminEnforced\n requiresLinearHistory\n allowsForcePushes\n allowsDeletions\n blocksCreations\n requiresConversationResolution\n lockBranch\n lockAllowsFetchAndMerge\n requiresCommitSignatures\n requiresStatusChecks\n requiresStrictStatusChecks\n requiredStatusCheckContexts\n requiresApprovingReviews\n requiredApprovingReviewCount\n requiresCodeOwnerReviews\n dismissesStaleReviews\n requireLastPushApproval\n requiresDeployments\n requiredDeploymentEnvironments\n bypassForcePushAllowances(first: 100) {\n nodes {\n actor {\n __typename\n ... on User { login }\n ... on Team { combinedSlug }\n ... on App { slug }\n }\n }\n pageInfo { hasNextPage }\n }\n }\n pageInfo { hasNextPage endCursor }\n }\n }\n}";
2457
+ } & {
2458
+ readonly _variables?: {
2459
+ owner: string;
2460
+ repo: string;
2461
+ } | undefined;
2462
+ };
2431
2463
  readonly repoLookup: {
2432
2464
  readonly name: "BranchProtectionRepository";
2433
2465
  readonly kind: "read";
@@ -3935,6 +3967,22 @@ declare const byKey: {
3935
3967
  repo: string;
3936
3968
  } | undefined;
3937
3969
  };
3970
+ readonly pinsSnapshot: {
3971
+ readonly name: "EnvironmentPinsSnapshot";
3972
+ readonly kind: "read";
3973
+ readonly query: "query EnvironmentPinsSnapshot($owner: String!, $repo: String!, $cursor: String) { repository(owner: $owner, name: $repo) { pinnedEnvironments(first: 100, after: $cursor) { nodes { position environment { name } } pageInfo { hasNextPage endCursor } } } }";
3974
+ readonly connection: {
3975
+ readonly path: readonly ["repository", "pinnedEnvironments"];
3976
+ };
3977
+ readonly outcomes: {
3978
+ readonly ok: "the pinned environments with their 1-based positions, for the snapshot";
3979
+ };
3980
+ } & {
3981
+ readonly _variables?: {
3982
+ owner: string;
3983
+ repo: string;
3984
+ } | undefined;
3985
+ };
3938
3986
  readonly pin: {
3939
3987
  readonly name: "PinEnvironment";
3940
3988
  readonly kind: "write";
@@ -4134,6 +4182,22 @@ declare const byKey: {
4134
4182
  repo: string;
4135
4183
  } | undefined;
4136
4184
  };
4185
+ readonly pinsSnapshot: {
4186
+ readonly name: "EnvironmentPinsSnapshot";
4187
+ readonly kind: "read";
4188
+ readonly query: "query EnvironmentPinsSnapshot($owner: String!, $repo: String!, $cursor: String) { repository(owner: $owner, name: $repo) { pinnedEnvironments(first: 100, after: $cursor) { nodes { position environment { name } } pageInfo { hasNextPage endCursor } } } }";
4189
+ readonly connection: {
4190
+ readonly path: readonly ["repository", "pinnedEnvironments"];
4191
+ };
4192
+ readonly outcomes: {
4193
+ readonly ok: "the pinned environments with their 1-based positions, for the snapshot";
4194
+ };
4195
+ } & {
4196
+ readonly _variables?: {
4197
+ owner: string;
4198
+ repo: string;
4199
+ } | undefined;
4200
+ };
4137
4201
  readonly pin: {
4138
4202
  readonly name: "PinEnvironment";
4139
4203
  readonly kind: "write";
@@ -4382,6 +4446,22 @@ declare const byKey: {
4382
4446
  repo: string;
4383
4447
  } | undefined;
4384
4448
  };
4449
+ readonly pinsSnapshot: {
4450
+ readonly name: "EnvironmentPinsSnapshot";
4451
+ readonly kind: "read";
4452
+ readonly query: "query EnvironmentPinsSnapshot($owner: String!, $repo: String!, $cursor: String) { repository(owner: $owner, name: $repo) { pinnedEnvironments(first: 100, after: $cursor) { nodes { position environment { name } } pageInfo { hasNextPage endCursor } } } }";
4453
+ readonly connection: {
4454
+ readonly path: readonly ["repository", "pinnedEnvironments"];
4455
+ };
4456
+ readonly outcomes: {
4457
+ readonly ok: "the pinned environments with their 1-based positions, for the snapshot";
4458
+ };
4459
+ } & {
4460
+ readonly _variables?: {
4461
+ owner: string;
4462
+ repo: string;
4463
+ } | undefined;
4464
+ };
4385
4465
  readonly pin: {
4386
4466
  readonly name: "PinEnvironment";
4387
4467
  readonly kind: "write";
@@ -4559,6 +4639,22 @@ declare const byKey: {
4559
4639
  repo: string;
4560
4640
  } | undefined;
4561
4641
  };
4642
+ readonly rulesSnapshot: {
4643
+ readonly name: "BranchProtectionRulesSnapshot";
4644
+ readonly kind: "read";
4645
+ readonly connection: {
4646
+ readonly path: readonly ["repository", "branchProtectionRules"];
4647
+ };
4648
+ readonly outcomes: {
4649
+ readonly ok: "the repository's classic branch protection rules, for the snapshot";
4650
+ };
4651
+ readonly query: "query BranchProtectionRulesSnapshot($owner: String!, $repo: String!, $cursor: String) {\n repository(owner: $owner, name: $repo) {\n branchProtectionRules(first: 100, after: $cursor) {\n nodes {\n id\n pattern\n isAdminEnforced\n requiresLinearHistory\n allowsForcePushes\n allowsDeletions\n blocksCreations\n requiresConversationResolution\n lockBranch\n lockAllowsFetchAndMerge\n requiresCommitSignatures\n requiresStatusChecks\n requiresStrictStatusChecks\n requiredStatusCheckContexts\n requiresApprovingReviews\n requiredApprovingReviewCount\n requiresCodeOwnerReviews\n dismissesStaleReviews\n requireLastPushApproval\n requiresDeployments\n requiredDeploymentEnvironments\n bypassForcePushAllowances(first: 100) {\n nodes {\n actor {\n __typename\n ... on User { login }\n ... on Team { combinedSlug }\n ... on App { slug }\n }\n }\n pageInfo { hasNextPage }\n }\n }\n pageInfo { hasNextPage endCursor }\n }\n }\n}";
4652
+ } & {
4653
+ readonly _variables?: {
4654
+ owner: string;
4655
+ repo: string;
4656
+ } | undefined;
4657
+ };
4562
4658
  readonly repoLookup: {
4563
4659
  readonly name: "BranchProtectionRepository";
4564
4660
  readonly kind: "read";
@@ -4736,6 +4832,22 @@ declare const byKey: {
4736
4832
  repo: string;
4737
4833
  } | undefined;
4738
4834
  };
4835
+ readonly rulesSnapshot: {
4836
+ readonly name: "BranchProtectionRulesSnapshot";
4837
+ readonly kind: "read";
4838
+ readonly connection: {
4839
+ readonly path: readonly ["repository", "branchProtectionRules"];
4840
+ };
4841
+ readonly outcomes: {
4842
+ readonly ok: "the repository's classic branch protection rules, for the snapshot";
4843
+ };
4844
+ readonly query: "query BranchProtectionRulesSnapshot($owner: String!, $repo: String!, $cursor: String) {\n repository(owner: $owner, name: $repo) {\n branchProtectionRules(first: 100, after: $cursor) {\n nodes {\n id\n pattern\n isAdminEnforced\n requiresLinearHistory\n allowsForcePushes\n allowsDeletions\n blocksCreations\n requiresConversationResolution\n lockBranch\n lockAllowsFetchAndMerge\n requiresCommitSignatures\n requiresStatusChecks\n requiresStrictStatusChecks\n requiredStatusCheckContexts\n requiresApprovingReviews\n requiredApprovingReviewCount\n requiresCodeOwnerReviews\n dismissesStaleReviews\n requireLastPushApproval\n requiresDeployments\n requiredDeploymentEnvironments\n bypassForcePushAllowances(first: 100) {\n nodes {\n actor {\n __typename\n ... on User { login }\n ... on Team { combinedSlug }\n ... on App { slug }\n }\n }\n pageInfo { hasNextPage }\n }\n }\n pageInfo { hasNextPage endCursor }\n }\n }\n}";
4845
+ } & {
4846
+ readonly _variables?: {
4847
+ owner: string;
4848
+ repo: string;
4849
+ } | undefined;
4850
+ };
4739
4851
  readonly repoLookup: {
4740
4852
  readonly name: "BranchProtectionRepository";
4741
4853
  readonly kind: "read";
@@ -4922,6 +5034,22 @@ declare const byKey: {
4922
5034
  repo: string;
4923
5035
  } | undefined;
4924
5036
  };
5037
+ readonly rulesSnapshot: {
5038
+ readonly name: "BranchProtectionRulesSnapshot";
5039
+ readonly kind: "read";
5040
+ readonly connection: {
5041
+ readonly path: readonly ["repository", "branchProtectionRules"];
5042
+ };
5043
+ readonly outcomes: {
5044
+ readonly ok: "the repository's classic branch protection rules, for the snapshot";
5045
+ };
5046
+ readonly query: "query BranchProtectionRulesSnapshot($owner: String!, $repo: String!, $cursor: String) {\n repository(owner: $owner, name: $repo) {\n branchProtectionRules(first: 100, after: $cursor) {\n nodes {\n id\n pattern\n isAdminEnforced\n requiresLinearHistory\n allowsForcePushes\n allowsDeletions\n blocksCreations\n requiresConversationResolution\n lockBranch\n lockAllowsFetchAndMerge\n requiresCommitSignatures\n requiresStatusChecks\n requiresStrictStatusChecks\n requiredStatusCheckContexts\n requiresApprovingReviews\n requiredApprovingReviewCount\n requiresCodeOwnerReviews\n dismissesStaleReviews\n requireLastPushApproval\n requiresDeployments\n requiredDeploymentEnvironments\n bypassForcePushAllowances(first: 100) {\n nodes {\n actor {\n __typename\n ... on User { login }\n ... on Team { combinedSlug }\n ... on App { slug }\n }\n }\n pageInfo { hasNextPage }\n }\n }\n pageInfo { hasNextPage endCursor }\n }\n }\n}";
5047
+ } & {
5048
+ readonly _variables?: {
5049
+ owner: string;
5050
+ repo: string;
5051
+ } | undefined;
5052
+ };
4925
5053
  readonly repoLookup: {
4926
5054
  readonly name: "BranchProtectionRepository";
4927
5055
  readonly kind: "read";
@@ -5013,7 +5141,7 @@ declare const byKey: {
5013
5141
  };
5014
5142
  }>): Promise<{
5015
5143
  value: undefined;
5016
- notes: never[];
5144
+ notes: string[];
5017
5145
  } | {
5018
5146
  value: {
5019
5147
  name: string;
package/lib/pkg/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as renderSnapshotYaml, A as parseSettingsDoc, At as parseReposInput, B as planRedaction, Bt as SECRET_TRANSPORT_WITHHELD, C as SNAPSHOT_ONLY_INPUTS, Ct as endpointMethod, D as DEFAULT_SETTINGS_FILE, Dt as SECTION_KEYS, E as parseSnapshotFileConfig, Et as PROBOT_PARITY_KEYS, F as concludeMerge, Ft as VISIBILITY_FILTERS, G as openReportChannel, Gt as prefixedIo, H as toPublicView, Ht as isRateLimitError, I as concludeRun, It as discoverRepos, J as MARKER_LABEL_CONFIG, Jt as resolveCentralTargets, K as ISSUE_TITLE, Kt as redactRanges, L as failRun, Lt as DEFAULT_API_VERSION, M as createVisibilityResolver, Mt as ARCHIVED_FILTERS, N as getRepoFile, Nt as DEFAULT_DISCOVERY_FILTERS, O as resolveTargets, Ot as SettingsFile, P as MERGE_RESULT, Pt as FORKS_FILTERS, Q as parseRecipient, R as PRIVATE_REPOS_POLICIES, Rt as GithubApi, S as SNAPSHOT_INPUTS, St as grantFor, T as parseConfig, Tt as DOCUMENT_DIRECTIVE_KEYS, U as PRIVATE_REPORT_CHANNELS, Ut as collectingIo, V as publicDetail, Vt as isPermissionError, W as applyMarkerInjection, Wt as maskRegistry, X as deliverArtifactReport, Xt as parseRepoSlug, Y as composeReport, Yt as dedupeTargets, Z as encryptReport, _ as INPUT_DECLS, _t as denialPosture, a as snapshotRepositories, at as preflightProbe, b as MERGE_REJECTED_INPUTS, bt as sectionOperations, c as SNAPSHOT_RESULTS, ct as validateSettingsDoc, d as runSnapshot, dt as mergeLayers, et as RERUN_ADVICE, f as runSingle, ft as stripNulls, g as FILTER_INPUTS, gt as sectionModule, h as DEFAULT_PRIVATE_REPOS, ht as allGraphqlOps, i as renderMergedYaml, it as REPO_RESULTS, j as readSettingsFile, jt as AFFILIATIONS, k as runMulti, kt as UNDECLARED_POLICY_SECTIONS, l as SNAPSHOT_SCHEMA_URL, lt as worstOf, m as readLayerFiles, mt as allEndpoints, n as applyRepository, nt as quoteList, o as snapshotRepository, ot as runForRepo, p as foldLayers, pt as SECTIONS, q as MARKER_LABEL, qt as silentIo, r as checkRepository, rt as SectionSelection, s as validateSettings, st as skippedSectionKeys, t as runMerge, tt as describeProblem, u as concludeSnapshot, ut as describeOptOut, v as MERGE_INPUTS, vt as readGating, w as SNAPSHOT_REJECTED_INPUTS, wt as endpointPath, x as MODES, xt as writeGatedReads, y as MERGE_ONLY_INPUTS, yt as sectionGrant, z as capturingIo, zt as SECRET_RESPONSE_WITHHELD } from "./src-BhuD5Ycj.js";
1
+ import { $ as renderSnapshotYaml, A as parseSettingsDoc, At as parseReposInput, B as planRedaction, Bt as SECRET_TRANSPORT_WITHHELD, C as SNAPSHOT_ONLY_INPUTS, Ct as endpointMethod, D as DEFAULT_SETTINGS_FILE, Dt as SECTION_KEYS, E as parseSnapshotFileConfig, Et as PROBOT_PARITY_KEYS, F as concludeMerge, Ft as VISIBILITY_FILTERS, G as openReportChannel, Gt as prefixedIo, H as toPublicView, Ht as isRateLimitError, I as concludeRun, It as discoverRepos, J as MARKER_LABEL_CONFIG, Jt as resolveCentralTargets, K as ISSUE_TITLE, Kt as redactRanges, L as failRun, Lt as DEFAULT_API_VERSION, M as createVisibilityResolver, Mt as ARCHIVED_FILTERS, N as getRepoFile, Nt as DEFAULT_DISCOVERY_FILTERS, O as resolveTargets, Ot as SettingsFile, P as MERGE_RESULT, Pt as FORKS_FILTERS, Q as parseRecipient, R as PRIVATE_REPOS_POLICIES, Rt as GithubApi, S as SNAPSHOT_INPUTS, St as grantFor, T as parseConfig, Tt as DOCUMENT_DIRECTIVE_KEYS, U as PRIVATE_REPORT_CHANNELS, Ut as collectingIo, V as publicDetail, Vt as isPermissionError, W as applyMarkerInjection, Wt as maskRegistry, X as deliverArtifactReport, Xt as parseRepoSlug, Y as composeReport, Yt as dedupeTargets, Z as encryptReport, _ as INPUT_DECLS, _t as denialPosture, a as snapshotRepositories, at as preflightProbe, b as MERGE_REJECTED_INPUTS, bt as sectionOperations, c as SNAPSHOT_RESULTS, ct as validateSettingsDoc, d as runSnapshot, dt as mergeLayers, et as RERUN_ADVICE, f as runSingle, ft as stripNulls, g as FILTER_INPUTS, gt as sectionModule, h as DEFAULT_PRIVATE_REPOS, ht as allGraphqlOps, i as renderMergedYaml, it as REPO_RESULTS, j as readSettingsFile, jt as AFFILIATIONS, k as runMulti, kt as UNDECLARED_POLICY_SECTIONS, l as SNAPSHOT_SCHEMA_URL, lt as worstOf, m as readLayerFiles, mt as allEndpoints, n as applyRepository, nt as quoteList, o as snapshotRepository, ot as runForRepo, p as foldLayers, pt as SECTIONS, q as MARKER_LABEL, qt as silentIo, r as checkRepository, rt as SectionSelection, s as validateSettings, st as skippedSectionKeys, t as runMerge, tt as describeProblem, u as concludeSnapshot, ut as describeOptOut, v as MERGE_INPUTS, vt as readGating, w as SNAPSHOT_REJECTED_INPUTS, wt as endpointPath, x as MODES, xt as writeGatedReads, y as MERGE_ONLY_INPUTS, yt as sectionGrant, z as capturingIo, zt as SECRET_RESPONSE_WITHHELD } from "./src-B13lb6Pp.js";
2
2
  export { AFFILIATIONS, ARCHIVED_FILTERS, DEFAULT_API_VERSION, DEFAULT_DISCOVERY_FILTERS, DEFAULT_PRIVATE_REPOS, DEFAULT_SETTINGS_FILE, DOCUMENT_DIRECTIVE_KEYS, FILTER_INPUTS, FORKS_FILTERS, GithubApi, INPUT_DECLS, ISSUE_TITLE, MARKER_LABEL, MARKER_LABEL_CONFIG, MERGE_INPUTS, MERGE_ONLY_INPUTS, MERGE_REJECTED_INPUTS, MERGE_RESULT, MODES, PRIVATE_REPORT_CHANNELS, PRIVATE_REPOS_POLICIES, PROBOT_PARITY_KEYS, REPO_RESULTS, RERUN_ADVICE, SECRET_RESPONSE_WITHHELD, SECRET_TRANSPORT_WITHHELD, SECTIONS, SECTION_KEYS, SNAPSHOT_INPUTS, SNAPSHOT_ONLY_INPUTS, SNAPSHOT_REJECTED_INPUTS, SNAPSHOT_RESULTS, SNAPSHOT_SCHEMA_URL, SectionSelection, SettingsFile, UNDECLARED_POLICY_SECTIONS, VISIBILITY_FILTERS, allEndpoints, allGraphqlOps, applyMarkerInjection, applyRepository, capturingIo, checkRepository, collectingIo, composeReport, concludeMerge, concludeRun, concludeSnapshot, createVisibilityResolver, dedupeTargets, deliverArtifactReport, denialPosture, describeOptOut, describeProblem, discoverRepos, encryptReport, endpointMethod, endpointPath, failRun, foldLayers, getRepoFile, grantFor, isPermissionError, isRateLimitError, maskRegistry, mergeLayers, openReportChannel, parseConfig, parseRecipient, parseRepoSlug, parseReposInput, parseSettingsDoc, parseSnapshotFileConfig, planRedaction, prefixedIo, preflightProbe, publicDetail, quoteList, readGating, readLayerFiles, readSettingsFile, redactRanges, renderMergedYaml, renderSnapshotYaml, resolveCentralTargets, resolveTargets, runForRepo, runMerge, runMulti, runSingle, runSnapshot, sectionGrant, sectionModule, sectionOperations, silentIo, skippedSectionKeys, snapshotRepositories, snapshotRepository, stripNulls, toPublicView, validateSettings, validateSettingsDoc, worstOf, writeGatedReads };
@@ -4192,16 +4192,11 @@ const WILDCARD_KEYS = [
4192
4192
  "required_deployments"
4193
4193
  ];
4194
4194
  const WILDCARD_KEY_SET = new Set(WILDCARD_KEYS);
4195
- const GRAPHQL = {
4196
- rulesQuery: graphqlOp()({
4197
- name: "BranchProtectionRules",
4198
- kind: "read",
4199
- connection: { path: ["repository", "branchProtectionRules"] },
4200
- outcomes: {
4201
- ok: "the repository's classic branch protection rules",
4202
- NOT_FOUND: "the repository is not visible to the token; read as no rules"
4203
- },
4204
- query: `query BranchProtectionRules($owner: String!, $repo: String!, $cursor: String) {
4195
+ /**
4196
+ * The rule selection both rules reads share, so the snapshot's read cannot lag the planner's
4197
+ * translation tables (test/sections/graphql-queries.test.ts asserts every twin is selected).
4198
+ */
4199
+ const RULES_SELECTION = `($owner: String!, $repo: String!, $cursor: String) {
4205
4200
  repository(owner: $owner, name: $repo) {
4206
4201
  branchProtectionRules(first: 100, after: $cursor) {
4207
4202
  nodes {
@@ -4241,7 +4236,24 @@ const GRAPHQL = {
4241
4236
  pageInfo { hasNextPage endCursor }
4242
4237
  }
4243
4238
  }
4244
- }`
4239
+ }`;
4240
+ const GRAPHQL = {
4241
+ rulesQuery: graphqlOp()({
4242
+ name: "BranchProtectionRules",
4243
+ kind: "read",
4244
+ connection: { path: ["repository", "branchProtectionRules"] },
4245
+ outcomes: {
4246
+ ok: "the repository's classic branch protection rules",
4247
+ NOT_FOUND: "the repository is not visible to the token; read as no rules"
4248
+ },
4249
+ query: `query BranchProtectionRules${RULES_SELECTION}`
4250
+ }),
4251
+ rulesSnapshot: graphqlOp()({
4252
+ name: "BranchProtectionRulesSnapshot",
4253
+ kind: "read",
4254
+ connection: { path: ["repository", "branchProtectionRules"] },
4255
+ outcomes: { ok: "the repository's classic branch protection rules, for the snapshot" },
4256
+ query: `query BranchProtectionRulesSnapshot${RULES_SELECTION}`
4245
4257
  }),
4246
4258
  repoLookup: graphqlOp()({
4247
4259
  name: "BranchProtectionRepository",
@@ -4325,8 +4337,17 @@ function hasRoutedGraphqlKeys(protection) {
4325
4337
  async function fetchRules(ctx) {
4326
4338
  const read = await ctx.read.rulesQuery.listConnection(repoVariables(ctx));
4327
4339
  if ("error" in read) return null;
4340
+ return indexRules(read.items);
4341
+ }
4342
+ /** The snapshot's read: the op tolerates no outcome, so a denial throws with the grant advice. */
4343
+ async function fetchRulesForSnapshot(ctx) {
4344
+ const read = await ctx.read.rulesSnapshot.listConnection(repoVariables(ctx));
4345
+ if ("error" in read) throw new Error("BUG: branches: the snapshot rules query declares no tolerated outcome, yet its read returned an error instead of throwing");
4346
+ return indexRules(read.items);
4347
+ }
4348
+ function indexRules(items) {
4328
4349
  const byPattern = /* @__PURE__ */ new Map();
4329
- for (const node of read.items) if (typeof node === "object" && node !== null) {
4350
+ for (const node of items) if (typeof node === "object" && node !== null) {
4330
4351
  const rule = node;
4331
4352
  if (rule.bypassForcePushAllowances?.pageInfo?.hasNextPage === true) throw new Error(`branches: the live protection rule "${String(rule.pattern)}" allows more than 100 force-push bypass actors, which this section cannot read back completely; trim the live allowance list below 100 to manage it here`);
4332
4353
  byPattern.set(String(rule.pattern), rule);
@@ -4367,6 +4388,32 @@ function classicViewOfRule(node) {
4367
4388
  out.required_deployments = node.requiresDeployments === true ? { environments: Array.isArray(node.requiredDeploymentEnvironments) ? node.requiredDeploymentEnvironments : [] } : null;
4368
4389
  return out;
4369
4390
  }
4391
+ /**
4392
+ * The two routed keys of a LITERAL entry as the snapshot declares them: only a non-empty allowance
4393
+ * list and a requirement that is on. An omitted routed key leaves the live value untouched (unlike
4394
+ * the replacing PUT, which resets an omitted control), so the file pins what is set.
4395
+ */
4396
+ function routedKeysSnapshot(node) {
4397
+ const view = classicViewOfRule(node);
4398
+ const out = {};
4399
+ const actors = view.force_push_bypassers;
4400
+ if (actors.length > 0) out.force_push_bypassers = actors;
4401
+ if (view.required_deployments !== null) out.required_deployments = view.required_deployments;
4402
+ return out;
4403
+ }
4404
+ /**
4405
+ * A WILDCARD rule as the snapshot declares it: the classic view with every control that is off
4406
+ * dropped and a nested null (an unset review count) omitted, so the entry carries only keys the
4407
+ * wildcard shape accepts and the check reads clean against the same rule.
4408
+ */
4409
+ function wildcardSnapshot(node) {
4410
+ const out = {};
4411
+ for (const [key, value] of Object.entries(classicViewOfRule(node))) {
4412
+ if (value === false || value === null || Array.isArray(value) && value.length === 0) continue;
4413
+ out[key] = typeof value === "object" && !Array.isArray(value) ? Object.fromEntries(Object.entries(value).filter(([, inner]) => inner !== null)) : value;
4414
+ }
4415
+ return out;
4416
+ }
4370
4417
  /** Shape validation already restricted a wildcard entry's keys, so an unknown key here is a bug. */
4371
4418
  function translateWildcardProtection(protection) {
4372
4419
  const input = {};
@@ -4696,8 +4743,14 @@ const permission$13 = { repo: ["administration"] };
4696
4743
  const LiveProtection = z.looseObject({ required_signatures: z.looseObject({ enabled: z.boolean() }).optional() });
4697
4744
  /** One item of the protected-branch listing; only the name is read (the protection is probed). */
4698
4745
  const LiveBranchSummary = z.looseObject({ name: z.string() });
4699
- /** The snapshot note for the surface the REST reads cannot carry. */
4700
- const GRAPHQL_ONLY_NOTE = "protection.force_push_bypassers, protection.required_deployments, and wildcard rules ride the GraphQL rule surface, which snapshot does not read; an omitted key leaves its live value untouched, so declare them to manage them. A branch a wildcard rule protects is written here as a LITERAL entry carrying that rule's protection (the REST reads name no pattern), and applying it would create a literal rule beside the wildcard; replace such entries with one wildcard entry naming the pattern";
4746
+ /**
4747
+ * A literal-pattern rule the REST reads did not surface as a protected branch: no branch of that
4748
+ * name exists (a pattern needs none), or its protection probe was denied. A literal entry applies
4749
+ * through the REST PUT, which needs the branch, so the rule is noted instead of written. The
4750
+ * pattern appears once and the text stays short, so the rendered header line ("# " prefixed)
4751
+ * stays under 256 chars for patterns up to 54 chars.
4752
+ */
4753
+ const unreachableLiteralRuleNote = (pattern) => `branches[${pattern}]: rule kept out of the snapshot: REST read no protected branch by this name (the branch is missing, or its protection is unreadable); create the branch or fix the grant, then snapshot again`;
4701
4754
  const WILDCARD_KEY_ERROR = (name, key) => `the wildcard entry "${name}" declares protection.${key}, which this section does not manage on wildcard rules; only the keys it can round-trip through the GraphQL rule mutations apply here: [${WILDCARD_KEYS.join(", ")}]. For actor lists and richer controls, prefer the rulesets section (the modern successor of classic protection)`;
4702
4755
  const branchesSection = {
4703
4756
  key: "branches",
@@ -4798,23 +4851,36 @@ const branchesSection = {
4798
4851
  },
4799
4852
  async snapshot(ctx) {
4800
4853
  const listed = parseLive(this, ENDPOINTS$15.listProtected, z.array(LiveBranchSummary), await ctx.read.listProtected.listAll({ query: { protected: "true" } }));
4854
+ const rules = await fetchRulesForSnapshot(ctx);
4801
4855
  const entries = [];
4856
+ const notes = [];
4802
4857
  for (const { name } of listed) {
4803
4858
  const probe = await ctx.read.getProtection.probeAbsent({ params: { branch: name } });
4804
4859
  if ("missing" in probe) continue;
4860
+ const rule = rules.get(name);
4861
+ if (rule === void 0) continue;
4805
4862
  const live = parseLive(this, ENDPOINTS$15.getProtection, LiveProtection, probe.data, `branch "${name}"`);
4806
4863
  entries.push({
4807
4864
  name,
4808
- protection: protectionSnapshot(live)
4865
+ protection: {
4866
+ ...protectionSnapshot(live),
4867
+ ...routedKeysSnapshot(rule)
4868
+ }
4809
4869
  });
4810
4870
  }
4871
+ const written = new Set(entries.map((entry) => entry.name));
4872
+ for (const [pattern, rule] of rules) if (isWildcardPattern(pattern)) entries.push({
4873
+ name: pattern,
4874
+ protection: wildcardSnapshot(rule)
4875
+ });
4876
+ else if (!written.has(pattern)) notes.push(unreachableLiteralRuleNote(pattern));
4811
4877
  if (entries.length === 0) return {
4812
4878
  value: void 0,
4813
- notes: []
4879
+ notes
4814
4880
  };
4815
4881
  return {
4816
4882
  value: entries,
4817
- notes: [GRAPHQL_ONLY_NOTE]
4883
+ notes
4818
4884
  };
4819
4885
  }
4820
4886
  };
@@ -6333,17 +6399,32 @@ async function planEnvironmentSecrets(ctx, section, envName, policy, entries, li
6333
6399
  }
6334
6400
  return planned;
6335
6401
  }
6402
+ //#endregion
6403
+ //#region src/sections/environments/pins.ts
6404
+ /**
6405
+ * Pinned environments (the routed `pinned` scalar): the GraphQL operations and the pin/unpin/reorder
6406
+ * planning. plan() gates the call on a declared `pinned` key, so a pin-free file never touches /graphql.
6407
+ */
6408
+ /** The pins selection both pins reads share, so the snapshot's read cannot lag the planner's. */
6409
+ const PINS_SELECTION = "($owner: String!, $repo: String!, $cursor: String) { repository(owner: $owner, name: $repo) { pinnedEnvironments(first: 100, after: $cursor) { nodes { position environment { name } } pageInfo { hasNextPage endCursor } } } }";
6336
6410
  const GRAPHQL_OPS$1 = {
6337
6411
  pins: graphqlOp()({
6338
6412
  name: "EnvironmentPins",
6339
6413
  kind: "read",
6340
- query: "query EnvironmentPins($owner: String!, $repo: String!, $cursor: String) { repository(owner: $owner, name: $repo) { pinnedEnvironments(first: 100, after: $cursor) { nodes { position environment { name } } pageInfo { hasNextPage endCursor } } } }",
6414
+ query: `query EnvironmentPins${PINS_SELECTION}`,
6341
6415
  connection: { path: ["repository", "pinnedEnvironments"] },
6342
6416
  outcomes: {
6343
6417
  ok: "the pinned environments with their 1-based positions",
6344
6418
  NOT_FOUND: "the repository is not visible to the token; read as no pins (the denial surfaces on the first pin write)"
6345
6419
  }
6346
6420
  }),
6421
+ pinsSnapshot: graphqlOp()({
6422
+ name: "EnvironmentPinsSnapshot",
6423
+ kind: "read",
6424
+ query: `query EnvironmentPinsSnapshot${PINS_SELECTION}`,
6425
+ connection: { path: ["repository", "pinnedEnvironments"] },
6426
+ outcomes: { ok: "the pinned environments with their 1-based positions, for the snapshot" }
6427
+ }),
6347
6428
  pin: graphqlOp()({
6348
6429
  name: "PinEnvironment",
6349
6430
  kind: "write",
@@ -6382,7 +6463,16 @@ function livePin(node) {
6382
6463
  async function listLivePins(ctx) {
6383
6464
  const listed = await ctx.read.pins.listConnection(repoVariables(ctx));
6384
6465
  if ("error" in listed) return [];
6385
- return listed.items.map(livePin).sort((a, b) => a.position - b.position);
6466
+ return rankPins(listed.items);
6467
+ }
6468
+ /** The snapshot's read: the op tolerates no outcome, so a denial throws with the grant advice. */
6469
+ async function snapshotPins(ctx) {
6470
+ const listed = await ctx.read.pinsSnapshot.listConnection(repoVariables(ctx));
6471
+ if ("error" in listed) throw new Error("BUG: environments: the snapshot pins query declares no tolerated outcome, yet its read returned an error instead of throwing");
6472
+ return rankPins(listed.items).map((pin) => pin.name);
6473
+ }
6474
+ function rankPins(nodes) {
6475
+ return nodes.map(livePin).sort((a, b) => a.position - b.position);
6386
6476
  }
6387
6477
  /** Environment names are case-insensitive on GitHub. */
6388
6478
  function pinKey(name) {
@@ -6513,9 +6603,41 @@ async function planPinned(ctx, declarations) {
6513
6603
  /**
6514
6604
  * The snapshot half of the nested seam: one environment's sub-resource lists read back in their
6515
6605
  * declared wrapped form, each under its planner's own default policy (nestedDefaultPolicy), and
6516
- * the environment-level GraphQL surface the REST reads cannot carry, as a note.
6606
+ * the pin state read off the GraphQL pins connection folded onto the entries.
6607
+ */
6608
+ /**
6609
+ * The pin state folded onto the entries: the pinned environments lead, in rank order, each with
6610
+ * `pinned: true` (the planner reads declaration order as pin order, so any other order would plan
6611
+ * a reorder); the rest follow in listing order without the key, since an unpinned environment has
6612
+ * nothing to declare and an absent key leaves a pin untouched. A pin naming no listed environment
6613
+ * (matched case-insensitively, as GitHub names them) cannot be declared, and the declared pins
6614
+ * must lead the live list, so from that rank on no pin is declared; one note names them all.
6517
6615
  */
6518
- const PINNED_NOTE = "pinned rides the GraphQL pins connection, which snapshot does not read; an entry without the key leaves its pin untouched, so declare pinned to manage pins";
6616
+ function withPins(entries, pins) {
6617
+ const byKey = new Map(entries.map((entry) => [entry.name.toLowerCase(), entry]));
6618
+ const leading = [];
6619
+ const notes = [];
6620
+ const unlistedAt = pins.findIndex((name) => !byKey.has(name.toLowerCase()));
6621
+ const declared = unlistedAt < 0 ? pins : pins.slice(0, unlistedAt);
6622
+ for (const name of declared) {
6623
+ const entry = byKey.get(name.toLowerCase());
6624
+ byKey.delete(name.toLowerCase());
6625
+ const { name: entryName, ...rest } = entry;
6626
+ leading.push({
6627
+ name: entryName,
6628
+ pinned: true,
6629
+ ...rest
6630
+ });
6631
+ }
6632
+ if (unlistedAt >= 0) {
6633
+ const undeclared = pins.slice(unlistedAt + 1).map((name) => `"${name}"`);
6634
+ notes.push(`environments: the pinned environment "${pins[unlistedAt]}" is not in the environment listing, so its pin cannot be declared` + (undeclared.length === 0 ? "" : `; the pins ranked after it (${undeclared.join(", ")}) are left without the pinned key too, since declared pins must lead the live list`));
6635
+ }
6636
+ return {
6637
+ entries: [...leading, ...byKey.values()],
6638
+ notes
6639
+ };
6640
+ }
6519
6641
  function wrapped(key, entries) {
6520
6642
  return {
6521
6643
  _undeclared: nestedDefaultPolicy(key),
@@ -6684,9 +6806,10 @@ const environmentsSection = {
6684
6806
  });
6685
6807
  notes.push(...nestedNotes);
6686
6808
  }
6687
- notes.push(...sharedSecretNotes(entries), PINNED_NOTE);
6809
+ const pinned = withPins(entries, await snapshotPins(ctx));
6810
+ notes.push(...pinned.notes, ...sharedSecretNotes(pinned.entries));
6688
6811
  return {
6689
- value: entries,
6812
+ value: pinned.entries,
6690
6813
  notes
6691
6814
  };
6692
6815
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vivswan/github-settings-as-code",
3
- "version": "2.0.1-main.1090.g15ef0c9",
3
+ "version": "2.0.1-main.1110.g63435fc",
4
4
  "description": "GitHub Action applying declarative repository settings: rulesets, labels, branch protection, and more.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "type": "module",