@devdogsuga/devtools 0.1.26 → 0.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -11
- package/bin/devtools.mjs +4 -4
- package/dist/cli-CZKkNHK5.js +6619 -0
- package/dist/failure-log-BBPGejQv.js +256 -0
- package/dist/launch.js +267 -281
- package/dist/local-env-D8HC2JK_.js +351 -0
- package/dist/local-env-DrBCXqD8.js +2 -0
- package/dist/menu-CQBsQRgH.js +1684 -0
- package/dist/peer-redirect-hooks.js +15 -0
- package/dist/peers-RBplwovA.js +229 -0
- package/dist/root-C2OmZDwD.js +94 -0
- package/dist/stack-Bx-55oi_.js +2 -0
- package/dist/stack-BynCgRVS.js +181 -0
- package/dist/supabase-project-0knM0mqd.js +426 -0
- package/dist/telemetry-C4foD8MD.js +249 -0
- package/dist/telemetry.js +2 -148
- package/package.json +10 -29
- package/bin/devtools-ci-bare.mjs +0 -48
- package/bin/devtools-ci.mjs +0 -31
- package/dist/args.d.ts +0 -26
- package/dist/args.js +0 -78
- package/dist/bws/bw.d.ts +0 -18
- package/dist/bws/bw.js +0 -70
- package/dist/bws/client.d.ts +0 -43
- package/dist/bws/client.js +0 -363
- package/dist/bws/environments.d.ts +0 -96
- package/dist/bws/environments.js +0 -111
- package/dist/bws/pace.d.ts +0 -25
- package/dist/bws/pace.js +0 -43
- package/dist/bws/retry.d.ts +0 -12
- package/dist/bws/retry.js +0 -39
- package/dist/bws/store.d.ts +0 -1
- package/dist/bws/store.js +0 -32
- package/dist/bws/token.d.ts +0 -31
- package/dist/bws/token.js +0 -88
- package/dist/bws/vault.d.ts +0 -29
- package/dist/bws/vault.js +0 -200
- package/dist/catalog.d.ts +0 -57
- package/dist/catalog.js +0 -57
- package/dist/cf/build.d.ts +0 -13
- package/dist/cf/build.js +0 -65
- package/dist/cf/commands.d.ts +0 -1
- package/dist/cf/commands.js +0 -172
- package/dist/cf/local-env.d.ts +0 -58
- package/dist/cf/local-env.js +0 -177
- package/dist/ci.d.ts +0 -1
- package/dist/ci.js +0 -328
- package/dist/cli.d.ts +0 -11
- package/dist/cli.js +0 -1094
- package/dist/commands.d.ts +0 -261
- package/dist/commands.js +0 -1318
- package/dist/completions.d.ts +0 -4
- package/dist/completions.js +0 -110
- package/dist/cron/commands.d.ts +0 -47
- package/dist/cron/commands.js +0 -372
- package/dist/cron/discovery.d.ts +0 -55
- package/dist/cron/discovery.js +0 -85
- package/dist/cron/schema.d.ts +0 -45
- package/dist/cron/schema.js +0 -35
- package/dist/db/config-push.d.ts +0 -8
- package/dist/db/config-push.js +0 -11
- package/dist/db/connection.d.ts +0 -79
- package/dist/db/connection.js +0 -114
- package/dist/db/exec.d.ts +0 -1
- package/dist/db/exec.js +0 -11
- package/dist/db/generate-types.d.ts +0 -1
- package/dist/db/generate-types.js +0 -4
- package/dist/db/generated-env.d.ts +0 -69
- package/dist/db/generated-env.js +0 -113
- package/dist/db/introspect.d.ts +0 -1
- package/dist/db/introspect.js +0 -122
- package/dist/db/local-env.d.ts +0 -65
- package/dist/db/local-env.js +0 -23
- package/dist/db/new-migration.d.ts +0 -8
- package/dist/db/new-migration.js +0 -69
- package/dist/db/pooler.d.ts +0 -23
- package/dist/db/pooler.js +0 -55
- package/dist/db/run.d.ts +0 -68
- package/dist/db/run.js +0 -156
- package/dist/db/seed-buckets.d.ts +0 -2
- package/dist/db/seed-buckets.js +0 -4
- package/dist/db/seed-production.d.ts +0 -8
- package/dist/db/seed-production.js +0 -79
- package/dist/db/seed-roles.d.ts +0 -7
- package/dist/db/seed-roles.js +0 -33
- package/dist/db/session-refresh.d.ts +0 -43
- package/dist/db/session-refresh.js +0 -40
- package/dist/deploy/migrations.d.ts +0 -20
- package/dist/deploy/migrations.js +0 -68
- package/dist/deploy/orphans.d.ts +0 -20
- package/dist/deploy/orphans.js +0 -177
- package/dist/deploy/preflight.d.ts +0 -53
- package/dist/deploy/preflight.js +0 -189
- package/dist/deploy/report.d.ts +0 -26
- package/dist/deploy/report.js +0 -70
- package/dist/deploy/require-planner.d.ts +0 -2
- package/dist/deploy/require-planner.js +0 -60
- package/dist/deploy/require-token.d.ts +0 -15
- package/dist/deploy/require-token.js +0 -78
- package/dist/deploy/secrets-file.d.ts +0 -17
- package/dist/deploy/secrets-file.js +0 -166
- package/dist/deploy/write-env.d.ts +0 -40
- package/dist/deploy/write-env.js +0 -395
- package/dist/docs/index-pages.d.ts +0 -38
- package/dist/docs/index-pages.js +0 -186
- package/dist/emails/commands.d.ts +0 -15
- package/dist/emails/commands.js +0 -130
- package/dist/emails/email-types.d.ts +0 -13
- package/dist/emails/email-types.js +0 -1
- package/dist/emails/fixtures.d.ts +0 -20
- package/dist/emails/fixtures.js +0 -20
- package/dist/env/audit.d.ts +0 -206
- package/dist/env/audit.js +0 -436
- package/dist/env/cloudflare.d.ts +0 -21
- package/dist/env/cloudflare.js +0 -61
- package/dist/env/commands.d.ts +0 -99
- package/dist/env/commands.js +0 -613
- package/dist/env/discovery.d.ts +0 -15
- package/dist/env/discovery.js +0 -239
- package/dist/env/document.d.ts +0 -119
- package/dist/env/document.js +0 -323
- package/dist/env/example.d.ts +0 -68
- package/dist/env/example.js +0 -605
- package/dist/env/selection.d.ts +0 -105
- package/dist/env/selection.js +0 -228
- package/dist/env-entry.d.ts +0 -49
- package/dist/env-entry.js +0 -204
- package/dist/environment-doctor.d.ts +0 -45
- package/dist/environment-doctor.js +0 -429
- package/dist/environment.d.ts +0 -92
- package/dist/environment.js +0 -185
- package/dist/fingerprint.d.ts +0 -10
- package/dist/fingerprint.js +0 -18
- package/dist/gen/campus-map.d.ts +0 -1
- package/dist/gen/campus-map.js +0 -473
- package/dist/gen/commands.d.ts +0 -1
- package/dist/gen/commands.js +0 -25
- package/dist/gen/hypno.d.ts +0 -1
- package/dist/gen/hypno.js +0 -134
- package/dist/gh/client.d.ts +0 -80
- package/dist/gh/client.js +0 -259
- package/dist/gh/environments.d.ts +0 -83
- package/dist/gh/environments.js +0 -178
- package/dist/gh/rulesets/actors.d.ts +0 -14
- package/dist/gh/rulesets/actors.js +0 -69
- package/dist/gh/rulesets/api.d.ts +0 -15
- package/dist/gh/rulesets/api.js +0 -104
- package/dist/gh/rulesets/commands.d.ts +0 -3
- package/dist/gh/rulesets/commands.js +0 -203
- package/dist/gh/rulesets/desired.d.ts +0 -112
- package/dist/gh/rulesets/desired.js +0 -271
- package/dist/gh/rulesets/diff.d.ts +0 -65
- package/dist/gh/rulesets/diff.js +0 -170
- package/dist/gh/rulesets/types.d.ts +0 -76
- package/dist/gh/rulesets/types.js +0 -13
- package/dist/gh/settings/api.d.ts +0 -40
- package/dist/gh/settings/api.js +0 -180
- package/dist/gh/settings/commands.d.ts +0 -1
- package/dist/gh/settings/commands.js +0 -254
- package/dist/gh/settings/desired.d.ts +0 -53
- package/dist/gh/settings/desired.js +0 -43
- package/dist/gh/settings/diff.d.ts +0 -57
- package/dist/gh/settings/diff.js +0 -161
- package/dist/gh/settings/types.d.ts +0 -92
- package/dist/gh/settings/types.js +0 -11
- package/dist/gh/settings/workflows.d.ts +0 -41
- package/dist/gh/settings/workflows.js +0 -128
- package/dist/grantRoot.d.ts +0 -42
- package/dist/grantRoot.js +0 -89
- package/dist/help.d.ts +0 -18
- package/dist/help.js +0 -169
- package/dist/images/commands.d.ts +0 -32
- package/dist/images/commands.js +0 -231
- package/dist/images/events.d.ts +0 -47
- package/dist/images/events.js +0 -116
- package/dist/images/graphics.d.ts +0 -70
- package/dist/images/graphics.js +0 -234
- package/dist/images/open-graph-types.d.ts +0 -75
- package/dist/images/open-graph-types.js +0 -1
- package/dist/images/prompts.d.ts +0 -34
- package/dist/images/prompts.js +0 -100
- package/dist/images/render.d.ts +0 -21
- package/dist/images/render.js +0 -61
- package/dist/images/select.d.ts +0 -64
- package/dist/images/select.js +0 -96
- package/dist/instance.d.ts +0 -69
- package/dist/instance.js +0 -102
- package/dist/invocation.d.ts +0 -49
- package/dist/invocation.js +0 -78
- package/dist/launch-ci.d.ts +0 -1
- package/dist/launch-ci.js +0 -90
- package/dist/launch.d.ts +0 -18
- package/dist/menu.d.ts +0 -43
- package/dist/menu.js +0 -300
- package/dist/moderation.d.ts +0 -70
- package/dist/moderation.js +0 -128
- package/dist/oauth/browser.d.ts +0 -1
- package/dist/oauth/browser.js +0 -13
- package/dist/oauth/config.d.ts +0 -24
- package/dist/oauth/config.js +0 -24
- package/dist/oauth/connect-url.d.ts +0 -17
- package/dist/oauth/connect-url.js +0 -23
- package/dist/oauth/db.d.ts +0 -37
- package/dist/oauth/db.js +0 -108
- package/dist/oauth/device.d.ts +0 -93
- package/dist/oauth/device.js +0 -198
- package/dist/oauth/discovery.d.ts +0 -28
- package/dist/oauth/discovery.js +0 -54
- package/dist/oauth/env-file.d.ts +0 -6
- package/dist/oauth/env-file.js +0 -35
- package/dist/oauth/exchange.d.ts +0 -43
- package/dist/oauth/exchange.js +0 -93
- package/dist/oauth/label.d.ts +0 -1
- package/dist/oauth/label.js +0 -25
- package/dist/oauth/loopback.d.ts +0 -52
- package/dist/oauth/loopback.js +0 -168
- package/dist/oauth/pkce.d.ts +0 -6
- package/dist/oauth/pkce.js +0 -34
- package/dist/oauth/target.d.ts +0 -54
- package/dist/oauth/target.js +0 -102
- package/dist/oauth/transport.d.ts +0 -41
- package/dist/oauth/transport.js +0 -32
- package/dist/oauth/wizard.d.ts +0 -40
- package/dist/oauth/wizard.js +0 -578
- package/dist/persona.d.ts +0 -18
- package/dist/persona.js +0 -221
- package/dist/pick.d.ts +0 -6
- package/dist/pick.js +0 -64
- package/dist/pipes.d.ts +0 -1
- package/dist/pipes.js +0 -25
- package/dist/planner/checks.d.ts +0 -26
- package/dist/planner/checks.js +0 -104
- package/dist/planner/commands.d.ts +0 -10
- package/dist/planner/commands.js +0 -367
- package/dist/planner/db.d.ts +0 -15
- package/dist/planner/db.js +0 -33
- package/dist/planner/role.d.ts +0 -33
- package/dist/planner/role.js +0 -68
- package/dist/repo/peer-redirect-hooks.d.ts +0 -16
- package/dist/repo/peer-redirect-hooks.js +0 -10
- package/dist/repo/peer-redirect.d.ts +0 -4
- package/dist/repo/peer-redirect.js +0 -26
- package/dist/repo/peers.d.ts +0 -39
- package/dist/repo/peers.js +0 -139
- package/dist/repo/resolve.d.ts +0 -41
- package/dist/repo/resolve.js +0 -152
- package/dist/repo/root.d.ts +0 -23
- package/dist/repo/root.js +0 -91
- package/dist/repo/source.d.ts +0 -14
- package/dist/repo/source.js +0 -54
- package/dist/repo/supabase-project.d.ts +0 -60
- package/dist/repo/supabase-project.js +0 -124
- package/dist/repo/tsx-loader.d.ts +0 -6
- package/dist/repo/tsx-loader.js +0 -41
- package/dist/run/pick.d.ts +0 -90
- package/dist/run/pick.js +0 -664
- package/dist/setup.d.ts +0 -1
- package/dist/setup.js +0 -280
- package/dist/stack.d.ts +0 -48
- package/dist/stack.js +0 -403
- package/dist/telemetry.d.ts +0 -59
- package/dist/tier.d.ts +0 -40
- package/dist/tier.js +0 -96
- package/dist/ui.d.ts +0 -40
- package/dist/ui.js +0 -71
- package/dist/version.d.ts +0 -7
- package/dist/version.js +0 -17
- package/dist/workers.d.ts +0 -5
- package/dist/workers.js +0 -33
- package/dist/workflows/commands.d.ts +0 -54
- package/dist/workflows/commands.js +0 -763
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The five fixed rulesets this repository must carry, as code.
|
|
3
|
-
*
|
|
4
|
-
* `apps/platform/src/server/github/rulesets.ts` (DevDogsUGA) has described
|
|
5
|
-
* these in prose since it was written — its module doc counts them toward
|
|
6
|
-
* the 75-ruleset cap — but nothing ever created them. TASK-322 is that gap:
|
|
7
|
-
* `main-protection` blocked only deletion and force-push (a push landed on
|
|
8
|
-
* `main` directly), `production` had no ruleset at all (a push there
|
|
9
|
-
* deploys), and a stale `comp-branches` ruleset survived a naming change.
|
|
10
|
-
* This module is the missing desired-state builder; `diff.ts` compares it
|
|
11
|
-
* against what `gh api .../rulesets` actually reports; `commands.ts` is the
|
|
12
|
-
* `devtools github rulesets` command that prints or applies the difference.
|
|
13
|
-
*
|
|
14
|
-
* ## Why the fixed `team/**` ruleset is safe alongside per-team ones
|
|
15
|
-
*
|
|
16
|
-
* `rulesets.ts`'s constraint 2 warns against ever running a broad `team/**`
|
|
17
|
-
* ruleset alongside the per-team ones `teamSync.ts` creates: rules AGGREGATE
|
|
18
|
-
* across rulesets sharing a ref, but bypass does NOT, so a broad ruleset
|
|
19
|
-
* restricting `update`/`push` would leave every team blocked by the broad
|
|
20
|
-
* one while bypassing only its own — readable, unpushable, silently.
|
|
21
|
-
*
|
|
22
|
-
* That warning is about a ruleset carrying `update` on `team/**`. This one
|
|
23
|
-
* carries `creation`, `deletion` and `non_fast_forward` — deliberately NOT
|
|
24
|
-
* `update`. No per-team ruleset (`teamRulesetPayload` in DevDogsUGA) has
|
|
25
|
-
* ever granted a `creation` rule — `rulesets.ts`'s own doc says why: the
|
|
26
|
-
* branch is cut by `cutTeamBranch` with the App's token before the per-team
|
|
27
|
-
* ruleset exists, so a `creation` rule there would be inert on the first
|
|
28
|
-
* branch and would block re-provisioning after a deletion. Two rulesets
|
|
29
|
-
* governing disjoint rule TYPES on the same ref pattern don't aggregate into
|
|
30
|
-
* a conflict for `creation` — there is nothing for them to disagree about.
|
|
31
|
-
* What that rule closes is a real hole `rulesets.ts` never covered: every
|
|
32
|
-
* team member holds a repository-wide `push` grant (GitHub team permissions
|
|
33
|
-
* have no branch dimension), so *before* the platform provisions a team, any
|
|
34
|
-
* member could `git push origin HEAD:refs/heads/team/<slug>` and front-run
|
|
35
|
-
* the App — creating the ref with content of their choosing, or squatting a
|
|
36
|
-
* slug that never gets legitimately provisioned.
|
|
37
|
-
*
|
|
38
|
-
* `deletion` and `non_fast_forward` DO aggregate against the per-team
|
|
39
|
-
* ruleset's own `deletion` rule (and its deliberate absence of
|
|
40
|
-
* `non_fast_forward`) — deliberately. A team is a bypass actor on its OWN
|
|
41
|
-
* per-team ruleset, not on this one, so the aggregate result is that a team
|
|
42
|
-
* can no longer delete or force-push its own branch, even though its
|
|
43
|
-
* per-team ruleset alone would let it (bypass is ruleset-scoped, constraint
|
|
44
|
-
* 1) — TASK-324's access-control checklist requires exactly that refusal.
|
|
45
|
-
* `update` is excluded from this ruleset for the opposite reason: it is the
|
|
46
|
-
* one rule type the per-team ruleset itself carries and bypasses its own
|
|
47
|
-
* team for, so adding a second `update` rule here — which only the App and
|
|
48
|
-
* devops bypass — would block every team's ordinary pushes to its own
|
|
49
|
-
* branch despite its per-team ruleset correctly bypassing them. That is
|
|
50
|
-
* exactly the failure constraint 2 describes; leaving `update` out is what
|
|
51
|
-
* keeps this ruleset on the safe side of it.
|
|
52
|
-
*
|
|
53
|
-
* ## Fixed cost is now 5, not 4
|
|
54
|
-
*
|
|
55
|
-
* `rulesets.ts`'s constraint 3 said "Fixed cost is 4: `main`, `production`,
|
|
56
|
-
* `~ALL` and the tag ruleset, leaving 71 for teams." This adds `team/**` as
|
|
57
|
-
* a fifth fixed ruleset, so the constant there needs updating to 5 fixed /
|
|
58
|
-
* 70 for teams — done alongside this file, in the same DevDogsUGA commit
|
|
59
|
-
* that would eventually consume this reconciler's output.
|
|
60
|
-
*/
|
|
61
|
-
import type { DesiredRuleset } from "./types.js";
|
|
62
|
-
/** Resolved GitHub ids this module needs before it can build a plan. */
|
|
63
|
-
export interface RulesetActors {
|
|
64
|
-
/** The `devops` team's numeric id (`GET /orgs/{org}/teams/devops`). */
|
|
65
|
-
devopsTeamId: number;
|
|
66
|
-
/** The `admins` team's numeric id — bypasses the tag ruleset, as it does live today. */
|
|
67
|
-
adminsTeamId: number;
|
|
68
|
-
/** The DevDogs Platform GitHub App's id (`app_id`, not the installation id). */
|
|
69
|
-
appId: number;
|
|
70
|
-
/**
|
|
71
|
-
* The Renovate GitHub App's id, or `undefined` when it cannot be resolved
|
|
72
|
-
* yet (TASK-299 — Sloan has not installed it at the time this module was
|
|
73
|
-
* written). `undefined` means "omit it from `~ALL`'s bypass list", not "fail
|
|
74
|
-
* the plan" — `commands.ts` resolves this separately from the other three
|
|
75
|
-
* (all required) and prints a warning rather than refusing, so re-running
|
|
76
|
-
* after the App is installed picks it up with no code change here.
|
|
77
|
-
*/
|
|
78
|
-
renovateAppId?: number;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Canonical fixed-ruleset name -> earlier name(s) this reconciler recognizes
|
|
82
|
-
* as the SAME slot, so the first run renames rather than creating a
|
|
83
|
-
* duplicate that leaves the old one live and unmanaged.
|
|
84
|
-
*
|
|
85
|
-
* Only `main` has a legacy alias today (`main-protection`, live on GitHub as
|
|
86
|
-
* of 2026-09-24). `production`, `~ALL` and `team/**` have no live ruleset to
|
|
87
|
-
* rename — they are created fresh. The tag ruleset keeps its live name,
|
|
88
|
-
* `tag-protection`, unchanged; nothing about its shape is wrong today, so
|
|
89
|
-
* there is no rename to make.
|
|
90
|
-
*/
|
|
91
|
-
export declare const LEGACY_NAME_ALIASES: Readonly<Record<string, readonly string[]>>;
|
|
92
|
-
/** Ruleset names this reconciler deletes outright — nothing in the desired set replaces them. */
|
|
93
|
-
export declare const DELETE_RULESET_NAMES: readonly string[];
|
|
94
|
-
/**
|
|
95
|
-
* A live ruleset name this reconciler must NEVER touch: one `teamSync.ts`
|
|
96
|
-
* created and owns (`team/<slug>`, an exact-ref ruleset), as opposed to the
|
|
97
|
-
* `team/**` wildcard-creation ruleset this module itself manages.
|
|
98
|
-
*
|
|
99
|
-
* `teamRulesetName()` in DevDogsUGA's `rulesets.ts` builds these as
|
|
100
|
-
* `team/${slug}`, and slugs never contain `/`, so a live name is a per-team
|
|
101
|
-
* ruleset iff it starts with `team/` and is not the literal `team/**` this
|
|
102
|
-
* reconciler owns.
|
|
103
|
-
*/
|
|
104
|
-
export declare function isPerTeamRulesetName(name: string): boolean;
|
|
105
|
-
/**
|
|
106
|
-
* The five fixed rulesets, built from resolved actor ids.
|
|
107
|
-
*
|
|
108
|
-
* Pure — no network, no `gh` — so `desired.test.ts` can assert the shape
|
|
109
|
-
* without mocking anything, and `commands.ts` calls this only after
|
|
110
|
-
* resolving `RulesetActors` from the live org.
|
|
111
|
-
*/
|
|
112
|
-
export declare function buildDesiredRulesets(actors: RulesetActors): DesiredRuleset[];
|
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canonical fixed-ruleset name -> earlier name(s) this reconciler recognizes
|
|
3
|
-
* as the SAME slot, so the first run renames rather than creating a
|
|
4
|
-
* duplicate that leaves the old one live and unmanaged.
|
|
5
|
-
*
|
|
6
|
-
* Only `main` has a legacy alias today (`main-protection`, live on GitHub as
|
|
7
|
-
* of 2026-09-24). `production`, `~ALL` and `team/**` have no live ruleset to
|
|
8
|
-
* rename — they are created fresh. The tag ruleset keeps its live name,
|
|
9
|
-
* `tag-protection`, unchanged; nothing about its shape is wrong today, so
|
|
10
|
-
* there is no rename to make.
|
|
11
|
-
*/
|
|
12
|
-
export const LEGACY_NAME_ALIASES = {
|
|
13
|
-
main: ["main-protection"],
|
|
14
|
-
};
|
|
15
|
-
/** Ruleset names this reconciler deletes outright — nothing in the desired set replaces them. */
|
|
16
|
-
export const DELETE_RULESET_NAMES = ["comp-branches"];
|
|
17
|
-
/**
|
|
18
|
-
* A live ruleset name this reconciler must NEVER touch: one `teamSync.ts`
|
|
19
|
-
* created and owns (`team/<slug>`, an exact-ref ruleset), as opposed to the
|
|
20
|
-
* `team/**` wildcard-creation ruleset this module itself manages.
|
|
21
|
-
*
|
|
22
|
-
* `teamRulesetName()` in DevDogsUGA's `rulesets.ts` builds these as
|
|
23
|
-
* `team/${slug}`, and slugs never contain `/`, so a live name is a per-team
|
|
24
|
-
* ruleset iff it starts with `team/` and is not the literal `team/**` this
|
|
25
|
-
* reconciler owns.
|
|
26
|
-
*/
|
|
27
|
-
export function isPerTeamRulesetName(name) {
|
|
28
|
-
return name.startsWith("team/") && name !== "team/**";
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* The five fixed rulesets, built from resolved actor ids.
|
|
32
|
-
*
|
|
33
|
-
* Pure — no network, no `gh` — so `desired.test.ts` can assert the shape
|
|
34
|
-
* without mocking anything, and `commands.ts` calls this only after
|
|
35
|
-
* resolving `RulesetActors` from the live org.
|
|
36
|
-
*/
|
|
37
|
-
export function buildDesiredRulesets(actors) {
|
|
38
|
-
return [
|
|
39
|
-
{
|
|
40
|
-
name: "main",
|
|
41
|
-
target: "branch",
|
|
42
|
-
enforcement: "active",
|
|
43
|
-
conditions: {
|
|
44
|
-
ref_name: { include: ["refs/heads/main"], exclude: [] },
|
|
45
|
-
},
|
|
46
|
-
// `devops` bypasses everything on `main`, including a direct push in
|
|
47
|
-
// an emergency — unlike `production`, there is no deploy on the other
|
|
48
|
-
// side of a `main` push, so `always` (not `pull_request`-only) is the
|
|
49
|
-
// right bypass mode here.
|
|
50
|
-
bypass_actors: [
|
|
51
|
-
{
|
|
52
|
-
actor_id: actors.devopsTeamId,
|
|
53
|
-
actor_type: "Team",
|
|
54
|
-
bypass_mode: "always",
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
rules: [
|
|
58
|
-
{ type: "deletion" },
|
|
59
|
-
{ type: "non_fast_forward" },
|
|
60
|
-
// The bug this closes: `main-protection` carried `deletion` and
|
|
61
|
-
// `non_fast_forward` only, so a plain `git push origin main` landed
|
|
62
|
-
// directly. `update` restricts that to bypass actors.
|
|
63
|
-
{
|
|
64
|
-
type: "update",
|
|
65
|
-
parameters: { update_allows_fetch_and_merge: false },
|
|
66
|
-
},
|
|
67
|
-
// Competition entries merge into `main` by squash — one commit per
|
|
68
|
-
// entry, not the team branch's whole history.
|
|
69
|
-
{
|
|
70
|
-
type: "pull_request",
|
|
71
|
-
parameters: {
|
|
72
|
-
allowed_merge_methods: ["squash"],
|
|
73
|
-
dismiss_stale_reviews_on_push: false,
|
|
74
|
-
require_code_owner_review: false,
|
|
75
|
-
require_last_push_approval: false,
|
|
76
|
-
required_approving_review_count: 0,
|
|
77
|
-
required_review_thread_resolution: false,
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
name: "production",
|
|
84
|
-
target: "branch",
|
|
85
|
-
enforcement: "active",
|
|
86
|
-
conditions: {
|
|
87
|
-
ref_name: { include: ["refs/heads/production"], exclude: [] },
|
|
88
|
-
},
|
|
89
|
-
// A push to `production` deploys (see `GITHUB_COMPETITION_REPO`'s doc
|
|
90
|
-
// in DevDogsUGA's env.ts: "the production branch, not a second repo,
|
|
91
|
-
// is the deploy boundary"). `devops` bypasses on PULL REQUESTS only —
|
|
92
|
-
// `bypass_mode: "pull_request"` — so a Promote PR still needs the
|
|
93
|
-
// code-owner review below even from devops; only a direct push
|
|
94
|
-
// outside a PR bypasses freely, which devops should not have either
|
|
95
|
-
// for the deploy boundary. Kept at `pull_request` rather than
|
|
96
|
-
// dropping the bypass entirely because devops still needs to be able
|
|
97
|
-
// to merge a Promote PR without waiting on a second reviewer when
|
|
98
|
-
// nobody else is around.
|
|
99
|
-
bypass_actors: [
|
|
100
|
-
{
|
|
101
|
-
actor_id: actors.devopsTeamId,
|
|
102
|
-
actor_type: "Team",
|
|
103
|
-
bypass_mode: "pull_request",
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
rules: [
|
|
107
|
-
{ type: "deletion" },
|
|
108
|
-
{ type: "non_fast_forward" },
|
|
109
|
-
{
|
|
110
|
-
type: "update",
|
|
111
|
-
parameters: { update_allows_fetch_and_merge: false },
|
|
112
|
-
},
|
|
113
|
-
// Promote PRs merge as a single merge commit — `production`'s
|
|
114
|
-
// history should read as one commit per promotion, traceable back
|
|
115
|
-
// to the `main` state it promoted.
|
|
116
|
-
{
|
|
117
|
-
type: "pull_request",
|
|
118
|
-
parameters: {
|
|
119
|
-
allowed_merge_methods: ["merge"],
|
|
120
|
-
dismiss_stale_reviews_on_push: false,
|
|
121
|
-
require_code_owner_review: true,
|
|
122
|
-
require_last_push_approval: false,
|
|
123
|
-
required_approving_review_count: 1,
|
|
124
|
-
required_review_thread_resolution: false,
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
// `~ALL` is GitHub's own wildcard for "every branch in the
|
|
131
|
-
// repository" (`target: "branch"` scopes it to `refs/heads/*`, never
|
|
132
|
-
// tags). It exists to close the gap `teamSync.ts`'s header names
|
|
133
|
-
// directly: every team member holds a repository-wide `push` grant,
|
|
134
|
-
// because GitHub team permissions have no branch dimension, so
|
|
135
|
-
// WITHOUT this ruleset any team member can push straight to any
|
|
136
|
-
// branch that is not their own team's — including `main` and
|
|
137
|
-
// `production`, if either of THOSE rulesets were ever misconfigured,
|
|
138
|
-
// and including any other stray branch nothing else names. This is
|
|
139
|
-
// deliberately the blunt, redundant backstop.
|
|
140
|
-
//
|
|
141
|
-
// TASK-324's access-control checklist requires refusing a non-admin
|
|
142
|
-
// member who tries to CREATE an arbitrary new branch too, not only
|
|
143
|
-
// push to an existing one — `creation`, `deletion` and
|
|
144
|
-
// `non_fast_forward` join `update` here for that. `grep`-verified
|
|
145
|
-
// (2026-09-24) that no platform code path (`teamSync.ts`, the rest of
|
|
146
|
-
// `server/github/`) ever creates, deletes or force-pushes a ref
|
|
147
|
-
// outside `refs/heads/team/**` — `cutTeamBranch`'s `createRef` is the
|
|
148
|
-
// only ref-creation call in the app, and it targets a team ref, which
|
|
149
|
-
// `~ALL` already excludes. So the platform's own App does NOT bypass
|
|
150
|
-
// `~ALL`; nothing here needs it to.
|
|
151
|
-
name: "~ALL",
|
|
152
|
-
target: "branch",
|
|
153
|
-
enforcement: "active",
|
|
154
|
-
conditions: {
|
|
155
|
-
ref_name: {
|
|
156
|
-
include: ["~ALL"],
|
|
157
|
-
// MUST exclude `team/**`: rules aggregate across rulesets, and a
|
|
158
|
-
// per-team ruleset's bypass (its own team, `always`) does not
|
|
159
|
-
// exempt that team from a DIFFERENT ruleset's `update` rule
|
|
160
|
-
// matching the same ref (`rulesets.ts`'s constraint 2). Without
|
|
161
|
-
// this exclude, every team would be correctly bypassing its own
|
|
162
|
-
// per-team ruleset and STILL blocked here — readable, unpushable,
|
|
163
|
-
// and the cause invisible from either ruleset read alone.
|
|
164
|
-
exclude: ["refs/heads/team/**"],
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
bypass_actors: [
|
|
168
|
-
{
|
|
169
|
-
actor_id: actors.devopsTeamId,
|
|
170
|
-
actor_type: "Team",
|
|
171
|
-
bypass_mode: "always",
|
|
172
|
-
},
|
|
173
|
-
// Renovate (TASK-299) creates and updates `renovate/*` branches for
|
|
174
|
-
// its dependency-update PRs — an ordinary bot workflow, not a hole,
|
|
175
|
-
// so it bypasses the same way the platform App bypasses `team/**`.
|
|
176
|
-
// Conditional: the App may not be installed yet, in which case
|
|
177
|
-
// `commands.ts` never resolves an id for it and this entry is
|
|
178
|
-
// omitted rather than the whole plan failing — see
|
|
179
|
-
// `RulesetActors.renovateAppId`'s doc.
|
|
180
|
-
...(actors.renovateAppId !== undefined
|
|
181
|
-
? [
|
|
182
|
-
{
|
|
183
|
-
actor_id: actors.renovateAppId,
|
|
184
|
-
actor_type: "Integration",
|
|
185
|
-
bypass_mode: "always",
|
|
186
|
-
},
|
|
187
|
-
]
|
|
188
|
-
: []),
|
|
189
|
-
],
|
|
190
|
-
rules: [
|
|
191
|
-
{
|
|
192
|
-
type: "update",
|
|
193
|
-
parameters: { update_allows_fetch_and_merge: false },
|
|
194
|
-
},
|
|
195
|
-
{ type: "creation" },
|
|
196
|
-
{ type: "deletion" },
|
|
197
|
-
{ type: "non_fast_forward" },
|
|
198
|
-
],
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
// `creation`, `deletion` and `non_fast_forward` — deliberately NOT
|
|
202
|
-
// `update`. Pushes to a team's own branch stay governed entirely by
|
|
203
|
-
// that team's per-team ruleset (`teamRulesetPayload`, which grants
|
|
204
|
-
// exactly that team `update`+`deletion` bypass on its own ref); this
|
|
205
|
-
// ruleset must not add a SECOND `update` rule here, because rules
|
|
206
|
-
// aggregate and only the App/devops bypass this one — an `update` rule
|
|
207
|
-
// here would block every team's own pushes despite their per-team
|
|
208
|
-
// ruleset correctly bypassing (`rulesets.ts`'s constraint 2, the exact
|
|
209
|
-
// failure this file's header already warns about).
|
|
210
|
-
//
|
|
211
|
-
// `deletion` and `non_fast_forward`, by contrast, are SAFE to add here
|
|
212
|
-
// precisely because they aggregate: a team bypasses its own per-team
|
|
213
|
-
// ruleset's `deletion` rule, but is not a bypass actor on THIS one, so
|
|
214
|
-
// the aggregate result is that nobody but the App or devops can delete
|
|
215
|
-
// or force-push any `team/**` branch — including a team acting on its
|
|
216
|
-
// own. See `rulesets.ts`'s `teamRulesetPayload` doc, updated alongside
|
|
217
|
-
// this file, for what that changes about the per-team ruleset's own
|
|
218
|
-
// "right trade" reasoning.
|
|
219
|
-
name: "team/**",
|
|
220
|
-
target: "branch",
|
|
221
|
-
enforcement: "active",
|
|
222
|
-
conditions: {
|
|
223
|
-
ref_name: { include: ["refs/heads/team/**"], exclude: [] },
|
|
224
|
-
},
|
|
225
|
-
bypass_actors: [
|
|
226
|
-
{
|
|
227
|
-
actor_id: actors.appId,
|
|
228
|
-
actor_type: "Integration",
|
|
229
|
-
bypass_mode: "always",
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
actor_id: actors.devopsTeamId,
|
|
233
|
-
actor_type: "Team",
|
|
234
|
-
bypass_mode: "always",
|
|
235
|
-
},
|
|
236
|
-
],
|
|
237
|
-
rules: [
|
|
238
|
-
{ type: "creation" },
|
|
239
|
-
{ type: "deletion" },
|
|
240
|
-
{ type: "non_fast_forward" },
|
|
241
|
-
],
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
// The tag ruleset `rulesets.ts`'s constraint 3 counts toward the fixed
|
|
245
|
-
// cost but never named a shape for. Kept at its live name,
|
|
246
|
-
// `tag-protection` — nothing about its current shape is the bug this
|
|
247
|
-
// task fixes, so there is no rename to make, only a shape to hold
|
|
248
|
-
// steady (a plan against the live fixture is a no-op here; see
|
|
249
|
-
// `diff.test.ts`'s idempotence case).
|
|
250
|
-
name: "tag-protection",
|
|
251
|
-
target: "tag",
|
|
252
|
-
enforcement: "active",
|
|
253
|
-
conditions: {
|
|
254
|
-
ref_name: { include: ["refs/tags/**"], exclude: [] },
|
|
255
|
-
},
|
|
256
|
-
bypass_actors: [
|
|
257
|
-
{
|
|
258
|
-
actor_id: actors.adminsTeamId,
|
|
259
|
-
actor_type: "Team",
|
|
260
|
-
bypass_mode: "always",
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
actor_id: actors.devopsTeamId,
|
|
264
|
-
actor_type: "Team",
|
|
265
|
-
bypass_mode: "always",
|
|
266
|
-
},
|
|
267
|
-
],
|
|
268
|
-
rules: [{ type: "creation" }, { type: "update" }, { type: "deletion" }],
|
|
269
|
-
},
|
|
270
|
-
];
|
|
271
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Live ruleset list -> a plan of creates/updates/deletes/no-ops, against
|
|
3
|
-
* `desired.ts`'s five fixed rulesets.
|
|
4
|
-
*
|
|
5
|
-
* Every live ruleset is classified exactly once, by name, into one of four
|
|
6
|
-
* buckets — and the classification is what makes this reconciler safe to
|
|
7
|
-
* re-run against a live org: it only ever writes to a ruleset it can name a
|
|
8
|
-
* reason for touching.
|
|
9
|
-
*
|
|
10
|
-
* 1. A per-team ruleset (`isPerTeamRulesetName`) — owned by
|
|
11
|
-
* `teamSync.ts` in DevDogsUGA. Never read past its name, never
|
|
12
|
-
* diffed, never appears in any action list. `blast radius` here is
|
|
13
|
-
* the whole point: a bug that let this reconciler touch a team's
|
|
14
|
-
* ruleset would silently rewrite that team's own bypass actor.
|
|
15
|
-
* 2. Named in `DELETE_RULESET_NAMES` — deleted outright.
|
|
16
|
-
* 3. The canonical name (or a `LEGACY_NAME_ALIASES` entry) of a desired
|
|
17
|
-
* ruleset — created if absent, updated (by id, renamed if the alias
|
|
18
|
-
* matched) if its live shape differs, left as a no-op if it already
|
|
19
|
-
* matches.
|
|
20
|
-
* 4. Anything else — reported, never touched. An unrecognized ruleset is
|
|
21
|
-
* as likely to be something Sloan added by hand for a reason this
|
|
22
|
-
* reconciler doesn't know as it is to be drift, and only
|
|
23
|
-
* `DELETE_RULESET_NAMES` is trusted to mean "remove this".
|
|
24
|
-
*/
|
|
25
|
-
import { type RulesetActors } from "./desired.js";
|
|
26
|
-
import type { DesiredRuleset, LiveRuleset, LiveRulesetSummary } from "./types.js";
|
|
27
|
-
export interface CreateAction {
|
|
28
|
-
kind: "create";
|
|
29
|
-
desired: DesiredRuleset;
|
|
30
|
-
}
|
|
31
|
-
export interface UpdateAction {
|
|
32
|
-
kind: "update";
|
|
33
|
-
id: number;
|
|
34
|
-
/** The live ruleset's current name, when different from `desired.name` — a rename is part of this update. */
|
|
35
|
-
liveName: string;
|
|
36
|
-
desired: DesiredRuleset;
|
|
37
|
-
}
|
|
38
|
-
export interface DeleteAction {
|
|
39
|
-
kind: "delete";
|
|
40
|
-
id: number;
|
|
41
|
-
name: string;
|
|
42
|
-
}
|
|
43
|
-
export interface NoopEntry {
|
|
44
|
-
name: string;
|
|
45
|
-
id: number;
|
|
46
|
-
}
|
|
47
|
-
export interface RulesetPlan {
|
|
48
|
-
creates: CreateAction[];
|
|
49
|
-
updates: UpdateAction[];
|
|
50
|
-
deletes: DeleteAction[];
|
|
51
|
-
noops: NoopEntry[];
|
|
52
|
-
/** Per-team rulesets seen live, by name — reported for visibility, never acted on. */
|
|
53
|
-
perTeamSkipped: string[];
|
|
54
|
-
/** Live rulesets that matched no desired slot and no delete name — reported, never acted on. */
|
|
55
|
-
unmanaged: string[];
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Builds the plan. `liveDetails` must carry one fully-fetched `LiveRuleset`
|
|
59
|
-
* for every summary in `liveSummaries` EXCEPT per-team ones — `commands.ts`
|
|
60
|
-
* is expected to skip fetching those, since there can be up to ~70 of them
|
|
61
|
-
* and this reconciler never reads past their name either way.
|
|
62
|
-
*/
|
|
63
|
-
export declare function planRulesets(liveSummaries: readonly LiveRulesetSummary[], liveDetails: ReadonlyMap<number, LiveRuleset>, actors: RulesetActors, desiredRulesets: readonly DesiredRuleset[]): RulesetPlan;
|
|
64
|
-
/** Whether a plan would write anything at all. */
|
|
65
|
-
export declare function planHasChanges(plan: RulesetPlan): boolean;
|
package/dist/gh/rulesets/diff.js
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Live ruleset list -> a plan of creates/updates/deletes/no-ops, against
|
|
3
|
-
* `desired.ts`'s five fixed rulesets.
|
|
4
|
-
*
|
|
5
|
-
* Every live ruleset is classified exactly once, by name, into one of four
|
|
6
|
-
* buckets — and the classification is what makes this reconciler safe to
|
|
7
|
-
* re-run against a live org: it only ever writes to a ruleset it can name a
|
|
8
|
-
* reason for touching.
|
|
9
|
-
*
|
|
10
|
-
* 1. A per-team ruleset (`isPerTeamRulesetName`) — owned by
|
|
11
|
-
* `teamSync.ts` in DevDogsUGA. Never read past its name, never
|
|
12
|
-
* diffed, never appears in any action list. `blast radius` here is
|
|
13
|
-
* the whole point: a bug that let this reconciler touch a team's
|
|
14
|
-
* ruleset would silently rewrite that team's own bypass actor.
|
|
15
|
-
* 2. Named in `DELETE_RULESET_NAMES` — deleted outright.
|
|
16
|
-
* 3. The canonical name (or a `LEGACY_NAME_ALIASES` entry) of a desired
|
|
17
|
-
* ruleset — created if absent, updated (by id, renamed if the alias
|
|
18
|
-
* matched) if its live shape differs, left as a no-op if it already
|
|
19
|
-
* matches.
|
|
20
|
-
* 4. Anything else — reported, never touched. An unrecognized ruleset is
|
|
21
|
-
* as likely to be something Sloan added by hand for a reason this
|
|
22
|
-
* reconciler doesn't know as it is to be drift, and only
|
|
23
|
-
* `DELETE_RULESET_NAMES` is trusted to mean "remove this".
|
|
24
|
-
*/
|
|
25
|
-
import { DELETE_RULESET_NAMES, LEGACY_NAME_ALIASES, isPerTeamRulesetName, } from "./desired.js";
|
|
26
|
-
/**
|
|
27
|
-
* Reduces a rule to the fields this reconciler manages, in a fixed key order,
|
|
28
|
-
* so a live rule and its desired twin compare equal whenever they protect
|
|
29
|
-
* the same way.
|
|
30
|
-
*
|
|
31
|
-
* Two ways GitHub's copy differs from what was sent, both seen live on
|
|
32
|
-
* 2026-09-27 right after creating `production`:
|
|
33
|
-
*
|
|
34
|
-
* - it omits parameters that hold their default, so `update` comes back with
|
|
35
|
-
* no `update_allows_fetch_and_merge` (see `UpdateRule.parameters`' doc in
|
|
36
|
-
* `types.ts`);
|
|
37
|
-
* - it adds `pull_request` parameters `desired.ts` never sets
|
|
38
|
-
* (`dismissal_restriction`, `require_extra_approval_for_unattributed_changes`,
|
|
39
|
-
* `required_reviewers`), in its own key order.
|
|
40
|
-
*
|
|
41
|
-
* Without this, every ruleset with a `pull_request` rule planned an update on
|
|
42
|
-
* every run, so the reconciler never settled.
|
|
43
|
-
*/
|
|
44
|
-
function canonicalizeRule(rule) {
|
|
45
|
-
if (rule.type === "update") {
|
|
46
|
-
return {
|
|
47
|
-
type: "update",
|
|
48
|
-
parameters: {
|
|
49
|
-
update_allows_fetch_and_merge: rule.parameters?.update_allows_fetch_and_merge ?? false,
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
if (rule.type === "pull_request") {
|
|
54
|
-
const p = rule.parameters;
|
|
55
|
-
return {
|
|
56
|
-
type: "pull_request",
|
|
57
|
-
parameters: {
|
|
58
|
-
allowed_merge_methods: [...p.allowed_merge_methods].sort(),
|
|
59
|
-
dismiss_stale_reviews_on_push: p.dismiss_stale_reviews_on_push,
|
|
60
|
-
require_code_owner_review: p.require_code_owner_review,
|
|
61
|
-
require_last_push_approval: p.require_last_push_approval,
|
|
62
|
-
required_approving_review_count: p.required_approving_review_count,
|
|
63
|
-
required_review_thread_resolution: p.required_review_thread_resolution,
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
return { type: rule.type };
|
|
68
|
-
}
|
|
69
|
-
function sortRules(rules) {
|
|
70
|
-
return [...rules]
|
|
71
|
-
.map(canonicalizeRule)
|
|
72
|
-
.sort((a, b) => a.type.localeCompare(b.type));
|
|
73
|
-
}
|
|
74
|
-
/** Sorted, and each actor rebuilt in a fixed key order for the same reason as `canonicalizeRule`. */
|
|
75
|
-
function sortActors(actors) {
|
|
76
|
-
return [...actors]
|
|
77
|
-
.sort((a, b) => a.actor_type.localeCompare(b.actor_type) || a.actor_id - b.actor_id)
|
|
78
|
-
.map((a) => ({
|
|
79
|
-
actor_id: a.actor_id,
|
|
80
|
-
actor_type: a.actor_type,
|
|
81
|
-
bypass_mode: a.bypass_mode,
|
|
82
|
-
}));
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* A canonical, order-independent string for one ruleset's PROTECTION shape —
|
|
86
|
-
* everything except its name and id. Two rulesets that protect identically
|
|
87
|
-
* but list their rules or bypass actors in a different order must compare
|
|
88
|
-
* equal; GitHub does not promise a stable order for either.
|
|
89
|
-
*/
|
|
90
|
-
function fingerprint(r) {
|
|
91
|
-
return JSON.stringify({
|
|
92
|
-
target: r.target,
|
|
93
|
-
enforcement: r.enforcement,
|
|
94
|
-
conditions: {
|
|
95
|
-
ref_name: {
|
|
96
|
-
include: [...r.conditions.ref_name.include].sort(),
|
|
97
|
-
exclude: [...r.conditions.ref_name.exclude].sort(),
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
bypass_actors: sortActors(r.bypass_actors),
|
|
101
|
-
rules: sortRules(r.rules),
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
/** Every name a live ruleset could carry and still BE `desired` — its canonical name plus any legacy alias. */
|
|
105
|
-
function candidateNames(desired) {
|
|
106
|
-
return [desired.name, ...(LEGACY_NAME_ALIASES[desired.name] ?? [])];
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Builds the plan. `liveDetails` must carry one fully-fetched `LiveRuleset`
|
|
110
|
-
* for every summary in `liveSummaries` EXCEPT per-team ones — `commands.ts`
|
|
111
|
-
* is expected to skip fetching those, since there can be up to ~70 of them
|
|
112
|
-
* and this reconciler never reads past their name either way.
|
|
113
|
-
*/
|
|
114
|
-
export function planRulesets(liveSummaries, liveDetails, actors, desiredRulesets) {
|
|
115
|
-
const plan = {
|
|
116
|
-
creates: [],
|
|
117
|
-
updates: [],
|
|
118
|
-
deletes: [],
|
|
119
|
-
noops: [],
|
|
120
|
-
perTeamSkipped: [],
|
|
121
|
-
unmanaged: [],
|
|
122
|
-
};
|
|
123
|
-
const matchedLiveIds = new Set();
|
|
124
|
-
for (const desired of desiredRulesets) {
|
|
125
|
-
const names = candidateNames(desired);
|
|
126
|
-
const live = liveSummaries.find((s) => names.includes(s.name));
|
|
127
|
-
if (!live) {
|
|
128
|
-
plan.creates.push({ kind: "create", desired });
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
matchedLiveIds.add(live.id);
|
|
132
|
-
const detail = liveDetails.get(live.id);
|
|
133
|
-
if (!detail) {
|
|
134
|
-
throw new Error(`planRulesets: no detail fetched for live ruleset "${live.name}" (id ${live.id})`);
|
|
135
|
-
}
|
|
136
|
-
const renamed = detail.name !== desired.name;
|
|
137
|
-
const drifted = fingerprint(detail) !== fingerprint(desired);
|
|
138
|
-
if (renamed || drifted) {
|
|
139
|
-
plan.updates.push({
|
|
140
|
-
kind: "update",
|
|
141
|
-
id: live.id,
|
|
142
|
-
liveName: detail.name,
|
|
143
|
-
desired,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
plan.noops.push({ name: desired.name, id: live.id });
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
for (const live of liveSummaries) {
|
|
151
|
-
if (matchedLiveIds.has(live.id))
|
|
152
|
-
continue;
|
|
153
|
-
if (isPerTeamRulesetName(live.name)) {
|
|
154
|
-
plan.perTeamSkipped.push(live.name);
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
if (DELETE_RULESET_NAMES.includes(live.name)) {
|
|
158
|
-
plan.deletes.push({ kind: "delete", id: live.id, name: live.name });
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
plan.unmanaged.push(live.name);
|
|
162
|
-
}
|
|
163
|
-
return plan;
|
|
164
|
-
}
|
|
165
|
-
/** Whether a plan would write anything at all. */
|
|
166
|
-
export function planHasChanges(plan) {
|
|
167
|
-
return (plan.creates.length > 0 ||
|
|
168
|
-
plan.updates.length > 0 ||
|
|
169
|
-
plan.deletes.length > 0);
|
|
170
|
-
}
|