@devdogsuga/devtools 0.1.26 → 0.1.28
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
package/dist/gh/environments.js
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The GitHub environments, and which BWS project (if any) backs each.
|
|
3
|
-
*
|
|
4
|
-
* GitHub environments and BWS projects are NOT the same set, and conflating
|
|
5
|
-
* them is the mistake this file exists to prevent. There are FOUR GitHub
|
|
6
|
-
* environments and THREE BWS projects:
|
|
7
|
-
*
|
|
8
|
-
* | GitHub environment | BWS project | Receives |
|
|
9
|
-
* |--------------------|--------------|------------------------------|
|
|
10
|
-
* | `preflight` | `preflight` | everything in the project |
|
|
11
|
-
* | `staging` | `staging` | everything in the project |
|
|
12
|
-
* | `production` | `production` | everything EXCEPT apply-only |
|
|
13
|
-
* | `production-apply` | `production` | everything, apply-only too |
|
|
14
|
-
*
|
|
15
|
-
* The last two split one project between two GitHub environments, and that
|
|
16
|
-
* split IS the reviewer gate. `production` deploys on a push with nothing in
|
|
17
|
-
* front of it; `production-apply` has required reviewers. A write-capable
|
|
18
|
-
* credential reaching the first makes the second decorative, so the routing is
|
|
19
|
-
* enforced here rather than left to whoever last edited a file.
|
|
20
|
-
*
|
|
21
|
-
* ⚠️ THE GATE IS ONE-DIRECTIONAL, and reading it as symmetric is the mistake
|
|
22
|
-
* this file made until 2026-08-17. What the gate asserts, through
|
|
23
|
-
* `production.excludeKeys`, is that apply-tier credentials never reach the
|
|
24
|
-
* UNREVIEWED environment, and nothing more. What the reviewed environment also
|
|
25
|
-
* holds buys no privilege: it is the same Bitwarden project, and
|
|
26
|
-
* `production-apply` is the strictly MORE trusted of the two. Withholding
|
|
27
|
-
* ordinary keys from it broke every job that runs there: the config push wanted
|
|
28
|
-
* deploy-tier OAuth secrets, and the orphan prune wanted a deploy-tier API
|
|
29
|
-
* token. So `production-apply` now receives a SUPERSET of `production`.
|
|
30
|
-
*/
|
|
31
|
-
import { assertRegistryLoaded } from "../env/discovery.js";
|
|
32
|
-
import { getEnvSync } from "../repo/peers.js";
|
|
33
|
-
export const GITHUB_ENVIRONMENTS = [
|
|
34
|
-
"preflight",
|
|
35
|
-
"staging",
|
|
36
|
-
"production",
|
|
37
|
-
"production-apply",
|
|
38
|
-
];
|
|
39
|
-
export function isGithubEnvironment(v) {
|
|
40
|
-
return GITHUB_ENVIRONMENTS.includes(v);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* The apply-only set, derived from the env manifests (`tier: "apply"` on the
|
|
44
|
-
* declarations in `packages/devtools/env.ts`) rather than listed here.
|
|
45
|
-
*
|
|
46
|
-
* Read at ACCESS time, not module load: this module is imported by the CLI
|
|
47
|
-
* before any manifest is, so a snapshot taken now would be empty, and an empty
|
|
48
|
-
* apply set routes the write-capable credentials to the unreviewed
|
|
49
|
-
* `production` environment, which is the exact failure the split exists to
|
|
50
|
-
* prevent. The guard turns "forgot to loadRegistry()" into a crash instead.
|
|
51
|
-
*/
|
|
52
|
-
function applyOnly() {
|
|
53
|
-
assertRegistryLoaded();
|
|
54
|
-
return getEnvSync().applyOnlyKeys();
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* The plan-tier set, same shape and same load-time caveat as `applyOnly()`.
|
|
58
|
-
* Read by `main-plan` (preflight) and `production-plan` (production); a copy
|
|
59
|
-
* anywhere else is a credential nothing reads, so `staging` excludes it.
|
|
60
|
-
*/
|
|
61
|
-
function planOnly() {
|
|
62
|
-
assertRegistryLoaded();
|
|
63
|
-
return getEnvSync().planOnlyKeys();
|
|
64
|
-
}
|
|
65
|
-
export const GITHUB_ENVIRONMENT_SPECS = {
|
|
66
|
-
preflight: {
|
|
67
|
-
bwsProject: "preflight",
|
|
68
|
-
branch: "main",
|
|
69
|
-
onlyKeys: null,
|
|
70
|
-
excludeKeys: [],
|
|
71
|
-
guarded: false,
|
|
72
|
-
},
|
|
73
|
-
staging: {
|
|
74
|
-
bwsProject: "staging",
|
|
75
|
-
branch: "main",
|
|
76
|
-
onlyKeys: null,
|
|
77
|
-
// Both narrow tiers: no staging job plans, and none may apply.
|
|
78
|
-
get excludeKeys() {
|
|
79
|
-
return [...applyOnly(), ...planOnly()];
|
|
80
|
-
},
|
|
81
|
-
guarded: false,
|
|
82
|
-
},
|
|
83
|
-
// ⚠️ THE REVIEWER GATE, in one property. `excludeKeys` here is the only
|
|
84
|
-
// thing keeping the apply-tier credentials out of an environment that
|
|
85
|
-
// deploys on a push to `production` with nobody in front of it. Widening it
|
|
86
|
-
// to `[]` hands a write-capable token to an unreviewed deploy, and so does
|
|
87
|
-
// letting `applyOnly()` answer empty, which is what `assertRegistryLoaded()`
|
|
88
|
-
// exists to prevent. `environments.test.ts` asserts both apply keys by name
|
|
89
|
-
// against exactly this.
|
|
90
|
-
production: {
|
|
91
|
-
bwsProject: "production",
|
|
92
|
-
branch: "production",
|
|
93
|
-
onlyKeys: null,
|
|
94
|
-
get excludeKeys() {
|
|
95
|
-
return applyOnly();
|
|
96
|
-
},
|
|
97
|
-
guarded: true,
|
|
98
|
-
},
|
|
99
|
-
// A SUPERSET of `production`, deliberately: everything that environment
|
|
100
|
-
// receives, plus the apply-tier credential it may not have.
|
|
101
|
-
//
|
|
102
|
-
// Not a relaxation of the gate. This is the same Bitwarden project behind
|
|
103
|
-
// required reviewers, the more trusted half of the split, so withholding a
|
|
104
|
-
// key from it protects nothing and only starves the jobs that run here
|
|
105
|
-
// (`production-config`, `prune-orphans`, both of which needed a deploy-tier
|
|
106
|
-
// secret and got none). The gate is what `production` may NOT have, one row
|
|
107
|
-
// above.
|
|
108
|
-
"production-apply": {
|
|
109
|
-
bwsProject: "production",
|
|
110
|
-
branch: "production",
|
|
111
|
-
onlyKeys: null,
|
|
112
|
-
excludeKeys: [],
|
|
113
|
-
guarded: true,
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
// ── Routing ──────────────────────────────────────────────────────────────────
|
|
117
|
-
//
|
|
118
|
-
// One Bitwarden project can feed more than one GitHub environment; production
|
|
119
|
-
// feeds two. So a push has to know which key goes where. Derived from the table
|
|
120
|
-
// above rather than written out a second time: the split IS the reviewer gate,
|
|
121
|
-
// and a hardcoded copy of it is a copy that can disagree.
|
|
122
|
-
/** The GitHub environments fed by one Bitwarden project, in precedence order. */
|
|
123
|
-
export function githubTargets(bwsProject) {
|
|
124
|
-
return GITHUB_ENVIRONMENTS.filter((e) => GITHUB_ENVIRONMENT_SPECS[e].bwsProject === bwsProject);
|
|
125
|
-
}
|
|
126
|
-
/** Whether one environment takes a given key. */
|
|
127
|
-
export function accepts(environment, key) {
|
|
128
|
-
const spec = GITHUB_ENVIRONMENT_SPECS[environment];
|
|
129
|
-
return spec.onlyKeys
|
|
130
|
-
? spec.onlyKeys.includes(key)
|
|
131
|
-
: !spec.excludeKeys.includes(key);
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* The PRIMARY environment a key belongs in, or `null` for "nowhere here".
|
|
135
|
-
*
|
|
136
|
-
* `null` is not an error. Pushing `staging` with an apply-only credential in
|
|
137
|
-
* the file is the ordinary case: that key belongs to production and has no home
|
|
138
|
-
* in the staging environment.
|
|
139
|
-
*
|
|
140
|
-
* ⚠️ "Primary", not "only". Since `production-apply` became a superset, an
|
|
141
|
-
* ordinary production key legitimately lives in TWO environments and this
|
|
142
|
-
* returns the first, the unreviewed one, where the deploy reads it. A caller
|
|
143
|
-
* asking "is this copy misplaced?" wants `acceptedBy()` instead; answering that
|
|
144
|
-
* question with this function reports every correctly-pushed copy in
|
|
145
|
-
* `production-apply` as a stray that somebody should delete.
|
|
146
|
-
*/
|
|
147
|
-
export function routeTo(bwsProject, key) {
|
|
148
|
-
return acceptedBy(bwsProject, key)[0] ?? null;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* EVERY environment of a project that takes a key, in precedence order.
|
|
152
|
-
*
|
|
153
|
-
* The set-shaped question, for callers that compare against what a push
|
|
154
|
-
* actually wrote rather than against one destination. `routeTo()` is this with
|
|
155
|
-
* `[0]` taken, and the difference is only ever visible for `production`, the
|
|
156
|
-
* one project that fans out to two environments.
|
|
157
|
-
*/
|
|
158
|
-
export function acceptedBy(bwsProject, key) {
|
|
159
|
-
return githubTargets(bwsProject).filter((e) => accepts(e, key));
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* `accepts()` for an environment name that has not been narrowed yet, in the
|
|
163
|
-
* argument order `AuditInput.accepted` wants.
|
|
164
|
-
*
|
|
165
|
-
* Exists so that the predicate `env audit` hands the audit is a NAME rather
|
|
166
|
-
* than a lambda written at the call site: `runEnvAudit` cannot be unit-tested
|
|
167
|
-
* without mocking Bitwarden, GitHub and Cloudflare at once, so anything spelled
|
|
168
|
-
* out there is untested by construction.
|
|
169
|
-
*
|
|
170
|
-
* ⚠️ An unrecognised environment is REFUSED, not accepted. The string comes
|
|
171
|
-
* from whatever `gh` listed, and the audit uses this to decide whether a found
|
|
172
|
-
* copy is a stray. Failing open here would silence the finding for a copy
|
|
173
|
-
* sitting in an environment this repository has never heard of, which is
|
|
174
|
-
* exactly the case worth hearing about.
|
|
175
|
-
*/
|
|
176
|
-
export function acceptsKey(key, environment) {
|
|
177
|
-
return isGithubEnvironment(environment) && accepts(environment, key);
|
|
178
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/** `GET /orgs/{org}/teams/{slug}` -> the team's numeric id. */
|
|
2
|
-
export declare function resolveTeamId(org: string, slug: string): Promise<number>;
|
|
3
|
-
/**
|
|
4
|
-
* `GET /orgs/{org}/installations` -> the id of the App installation whose
|
|
5
|
-
* `app_slug` matches, e.g. `devdogs-platform` (production) or
|
|
6
|
-
* `devdogs-platform-staging`.
|
|
7
|
-
*
|
|
8
|
-
* `orgs/{org}/installations` rather than the App's own `/app` endpoint: the
|
|
9
|
-
* latter needs App-JWT auth this CLI has no path to, while the installations
|
|
10
|
-
* list is a plain org-admin GET — the exact command DevDogsUGA's own
|
|
11
|
-
* `docs/platform/guides/identity/github-app.md` already tells an operator to
|
|
12
|
-
* run to verify the App's permission grant.
|
|
13
|
-
*/
|
|
14
|
-
export declare function resolveAppId(org: string, appSlug: string): Promise<number>;
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolves the numeric ids `desired.ts`'s bypass actors need, by NAME —
|
|
3
|
-
* never hardcoded, because a team or App id is only stable until somebody
|
|
4
|
-
* deletes and recreates it, and a stale id in a bypass actor is the
|
|
5
|
-
* dangerous failure: the ruleset then blocks the team or App it is named
|
|
6
|
-
* for while letting whoever inherited the old id through (the same
|
|
7
|
-
* reasoning `teamSync.ts`'s `ensureTeamRuleset` gives for always re-writing
|
|
8
|
-
* rather than skipping on an existing ruleset).
|
|
9
|
-
*
|
|
10
|
-
* Both come from plain, read-only `gh api` GETs — no Octokit, same as every
|
|
11
|
-
* other GitHub call in this package.
|
|
12
|
-
*/
|
|
13
|
-
import { execFile } from "node:child_process";
|
|
14
|
-
import { promisify } from "node:util";
|
|
15
|
-
import { GhRulesetsError } from "./api.js";
|
|
16
|
-
const run = promisify(execFile);
|
|
17
|
-
function describe(err) {
|
|
18
|
-
const e = err;
|
|
19
|
-
const stderr = (e.stderr ?? "").trim();
|
|
20
|
-
const message = e.message ?? "";
|
|
21
|
-
return stderr || message || "gh failed with no output.";
|
|
22
|
-
}
|
|
23
|
-
/** `GET /orgs/{org}/teams/{slug}` -> the team's numeric id. */
|
|
24
|
-
export async function resolveTeamId(org, slug) {
|
|
25
|
-
try {
|
|
26
|
-
const { stdout } = await run("gh", ["api", `orgs/${org}/teams/${slug}`], {
|
|
27
|
-
shell: false,
|
|
28
|
-
});
|
|
29
|
-
const data = JSON.parse(stdout);
|
|
30
|
-
if (typeof data.id !== "number") {
|
|
31
|
-
throw new GhRulesetsError(`orgs/${org}/teams/${slug} returned no numeric id`);
|
|
32
|
-
}
|
|
33
|
-
return data.id;
|
|
34
|
-
}
|
|
35
|
-
catch (err) {
|
|
36
|
-
if (err instanceof GhRulesetsError)
|
|
37
|
-
throw err;
|
|
38
|
-
throw new GhRulesetsError(`resolving team "${slug}" in ${org}: ${describe(err)}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* `GET /orgs/{org}/installations` -> the id of the App installation whose
|
|
43
|
-
* `app_slug` matches, e.g. `devdogs-platform` (production) or
|
|
44
|
-
* `devdogs-platform-staging`.
|
|
45
|
-
*
|
|
46
|
-
* `orgs/{org}/installations` rather than the App's own `/app` endpoint: the
|
|
47
|
-
* latter needs App-JWT auth this CLI has no path to, while the installations
|
|
48
|
-
* list is a plain org-admin GET — the exact command DevDogsUGA's own
|
|
49
|
-
* `docs/platform/guides/identity/github-app.md` already tells an operator to
|
|
50
|
-
* run to verify the App's permission grant.
|
|
51
|
-
*/
|
|
52
|
-
export async function resolveAppId(org, appSlug) {
|
|
53
|
-
try {
|
|
54
|
-
const { stdout } = await run("gh", ["api", `orgs/${org}/installations`], {
|
|
55
|
-
shell: false,
|
|
56
|
-
});
|
|
57
|
-
const data = JSON.parse(stdout);
|
|
58
|
-
const match = (data.installations ?? []).find((i) => i.app_slug === appSlug);
|
|
59
|
-
if (!match) {
|
|
60
|
-
throw new GhRulesetsError(`no installation with app_slug "${appSlug}" found in orgs/${org}/installations`);
|
|
61
|
-
}
|
|
62
|
-
return match.app_id;
|
|
63
|
-
}
|
|
64
|
-
catch (err) {
|
|
65
|
-
if (err instanceof GhRulesetsError)
|
|
66
|
-
throw err;
|
|
67
|
-
throw new GhRulesetsError(`resolving App "${appSlug}" in ${org}: ${describe(err)}`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { DesiredRuleset, LiveRuleset, LiveRulesetSummary } from "./types.js";
|
|
2
|
-
export declare class GhRulesetsError extends Error {
|
|
3
|
-
}
|
|
4
|
-
/** `owner/repo`, e.g. `DevDogsUGA/DevDogsUGA`. */
|
|
5
|
-
export interface Repo {
|
|
6
|
-
owner: string;
|
|
7
|
-
repo: string;
|
|
8
|
-
}
|
|
9
|
-
/** `GET /repos/{owner}/{repo}/rulesets` — id, name and target only. */
|
|
10
|
-
export declare function listRulesets(r: Repo): Promise<LiveRulesetSummary[]>;
|
|
11
|
-
/** `GET /repos/{owner}/{repo}/rulesets/{id}` — the full ruleset. */
|
|
12
|
-
export declare function getRuleset(r: Repo, id: number): Promise<LiveRuleset>;
|
|
13
|
-
export declare function createRuleset(r: Repo, desired: DesiredRuleset): Promise<LiveRuleset>;
|
|
14
|
-
export declare function updateRuleset(r: Repo, id: number, desired: DesiredRuleset): Promise<LiveRuleset>;
|
|
15
|
-
export declare function deleteRuleset(r: Repo, id: number): Promise<void>;
|
package/dist/gh/rulesets/api.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `gh api` calls this reconciler needs: listing and reading rulesets, and
|
|
3
|
-
* writing them (create/update/delete).
|
|
4
|
-
*
|
|
5
|
-
* `gh`, not Octokit — devtools has no Octokit dependency, and every other
|
|
6
|
-
* GitHub call in this package (`../client.ts`) already goes through the
|
|
7
|
-
* `gh` CLI for the same reason `bws` goes through its own CLI: `gh` already
|
|
8
|
-
* owns authentication (a contributor's `gh auth login`, or `GH_TOKEN` in
|
|
9
|
-
* CI), and re-deriving that here would mean a second credential path to
|
|
10
|
-
* keep in step with the first.
|
|
11
|
-
*
|
|
12
|
-
* Reads (`listRulesets`, `getRuleset`) run unauthenticated-safe GETs and are
|
|
13
|
-
* always allowed. Writes (`createRuleset`, `updateRuleset`, `deleteRuleset`)
|
|
14
|
-
* need `admin` on the repository — the same requirement `gh api
|
|
15
|
-
* .../rulesets -X POST` has always had — and `commands.ts` is the only
|
|
16
|
-
* caller that reaches them, gated behind `--apply`.
|
|
17
|
-
*/
|
|
18
|
-
import { execFile, spawn } from "node:child_process";
|
|
19
|
-
import { promisify } from "node:util";
|
|
20
|
-
const run = promisify(execFile);
|
|
21
|
-
const MAX_BUFFER = 16 * 1024 * 1024;
|
|
22
|
-
export class GhRulesetsError extends Error {
|
|
23
|
-
}
|
|
24
|
-
function describe(err) {
|
|
25
|
-
const e = err;
|
|
26
|
-
const stderr = (e.stderr ?? "").trim();
|
|
27
|
-
const message = e.message ?? "";
|
|
28
|
-
return stderr || message || "gh failed with no output.";
|
|
29
|
-
}
|
|
30
|
-
function path(r, suffix = "") {
|
|
31
|
-
return `repos/${r.owner}/${r.repo}/rulesets${suffix}`;
|
|
32
|
-
}
|
|
33
|
-
/** `GET /repos/{owner}/{repo}/rulesets` — id, name and target only. */
|
|
34
|
-
export async function listRulesets(r) {
|
|
35
|
-
try {
|
|
36
|
-
const { stdout } = await run("gh", ["api", path(r), "--paginate"], {
|
|
37
|
-
maxBuffer: MAX_BUFFER,
|
|
38
|
-
shell: false,
|
|
39
|
-
});
|
|
40
|
-
return stdout.trim() === ""
|
|
41
|
-
? []
|
|
42
|
-
: JSON.parse(stdout);
|
|
43
|
-
}
|
|
44
|
-
catch (err) {
|
|
45
|
-
throw new GhRulesetsError(describe(err));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/** `GET /repos/{owner}/{repo}/rulesets/{id}` — the full ruleset. */
|
|
49
|
-
export async function getRuleset(r, id) {
|
|
50
|
-
try {
|
|
51
|
-
const { stdout } = await run("gh", ["api", path(r, `/${id}`)], {
|
|
52
|
-
maxBuffer: MAX_BUFFER,
|
|
53
|
-
shell: false,
|
|
54
|
-
});
|
|
55
|
-
return JSON.parse(stdout);
|
|
56
|
-
}
|
|
57
|
-
catch (err) {
|
|
58
|
-
throw new GhRulesetsError(describe(err));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Runs `gh api <path> -X <method> --input -`, writing `body` on stdin (JSON)
|
|
63
|
-
* and returning the parsed JSON response.
|
|
64
|
-
*
|
|
65
|
-
* stdin, not `-f`/`-F` field flags: this reconciler always sends the WHOLE
|
|
66
|
-
* ruleset payload as one JSON document (the shape `desired.ts` already
|
|
67
|
-
* builds), and `gh api`'s field flags exist for building that document one
|
|
68
|
-
* key at a time from shell arguments — a worse fit than handing over the
|
|
69
|
-
* object this code already has. Not `-X POST --input path` via a temp file
|
|
70
|
-
* either: stdin needs no cleanup and cannot be left behind by a crash.
|
|
71
|
-
*/
|
|
72
|
-
async function writeJson(method, apiPath, body) {
|
|
73
|
-
return new Promise((resolve, reject) => {
|
|
74
|
-
const child = spawn("gh", ["api", apiPath, "-X", method, "--input", "-"], {
|
|
75
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
76
|
-
shell: false,
|
|
77
|
-
});
|
|
78
|
-
let stdout = "";
|
|
79
|
-
let stderr = "";
|
|
80
|
-
child.stdout.on("data", (c) => (stdout += c.toString()));
|
|
81
|
-
child.stderr.on("data", (c) => (stderr += c.toString()));
|
|
82
|
-
child.on("error", (err) => reject(new GhRulesetsError(describe(err))));
|
|
83
|
-
child.on("close", (code) => {
|
|
84
|
-
if (code !== 0) {
|
|
85
|
-
reject(new GhRulesetsError(describe({ stderr })));
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
const trimmed = stdout.trim();
|
|
89
|
-
resolve(trimmed === "" ? {} : JSON.parse(trimmed));
|
|
90
|
-
});
|
|
91
|
-
child.stdin.end(JSON.stringify(body));
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
export async function createRuleset(r, desired) {
|
|
95
|
-
return writeJson("POST", path(r), desired);
|
|
96
|
-
}
|
|
97
|
-
export async function updateRuleset(r, id, desired) {
|
|
98
|
-
// PUT, not PATCH: GitHub's rulesets API has no PATCH route and answers one
|
|
99
|
-
// with a 404, which reads like a missing ruleset rather than a wrong verb.
|
|
100
|
-
return writeJson("PUT", path(r, `/${id}`), desired);
|
|
101
|
-
}
|
|
102
|
-
export async function deleteRuleset(r, id) {
|
|
103
|
-
await writeJson("DELETE", path(r, `/${id}`), {});
|
|
104
|
-
}
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `devtools github rulesets` — TASK-322.
|
|
3
|
-
*
|
|
4
|
-
* Prints the diff between the five fixed rulesets DevDogsUGA's
|
|
5
|
-
* `apps/platform/src/server/github/rulesets.ts` has always assumed exist
|
|
6
|
-
* (`main`, `production`, `~ALL`, `team/**`, and the tag ruleset — see
|
|
7
|
-
* `desired.ts`'s header) and what `gh api .../rulesets` actually reports.
|
|
8
|
-
* Defaults to a dry-run plan; `--apply` writes it, gated behind a
|
|
9
|
-
* confirmation unless `--yes`.
|
|
10
|
-
*
|
|
11
|
-
* Never touches a per-team ruleset (`teamRulesetName()`'s `team/<slug>`) —
|
|
12
|
-
* those are `teamSync.ts`'s, not this command's — and never deletes or
|
|
13
|
-
* creates anything outside `desired.ts`'s five names plus the one explicit
|
|
14
|
-
* deletion (`comp-branches`). See `diff.ts`'s header for the full
|
|
15
|
-
* classification.
|
|
16
|
-
*/
|
|
17
|
-
import { confirm } from "@clack/prompts";
|
|
18
|
-
import { resolveAppId, resolveTeamId } from "./actors.js";
|
|
19
|
-
import { createRuleset, deleteRuleset, getRuleset, listRulesets, updateRuleset, } from "./api.js";
|
|
20
|
-
import { buildDesiredRulesets, isPerTeamRulesetName } from "./desired.js";
|
|
21
|
-
import { planHasChanges, planRulesets } from "./diff.js";
|
|
22
|
-
import { unwrap } from "../../ui.js";
|
|
23
|
-
const DEFAULT_ORG = "DevDogsUGA";
|
|
24
|
-
const DEFAULT_REPO = "DevDogsUGA";
|
|
25
|
-
const DEFAULT_APP_SLUG = "devdogs-platform";
|
|
26
|
-
const DEFAULT_DEVOPS_SLUG = "devops";
|
|
27
|
-
const DEFAULT_ADMINS_SLUG = "admins";
|
|
28
|
-
/** TASK-299 — installed separately from the platform App; may not exist yet. */
|
|
29
|
-
const DEFAULT_RENOVATE_SLUG = "renovate";
|
|
30
|
-
function parseOptions(argv) {
|
|
31
|
-
const opts = {
|
|
32
|
-
org: DEFAULT_ORG,
|
|
33
|
-
repo: DEFAULT_REPO,
|
|
34
|
-
appSlug: DEFAULT_APP_SLUG,
|
|
35
|
-
apply: false,
|
|
36
|
-
yes: false,
|
|
37
|
-
json: false,
|
|
38
|
-
};
|
|
39
|
-
for (let i = 0; i < argv.length; i++) {
|
|
40
|
-
const arg = argv[i];
|
|
41
|
-
if (arg === "--org")
|
|
42
|
-
opts.org = argv[++i] ?? opts.org;
|
|
43
|
-
else if (arg === "--repo")
|
|
44
|
-
opts.repo = argv[++i] ?? opts.repo;
|
|
45
|
-
else if (arg === "--app-slug")
|
|
46
|
-
opts.appSlug = argv[++i] ?? opts.appSlug;
|
|
47
|
-
else if (arg === "--apply")
|
|
48
|
-
opts.apply = true;
|
|
49
|
-
else if (arg === "--yes")
|
|
50
|
-
opts.yes = true;
|
|
51
|
-
else if (arg === "--json")
|
|
52
|
-
opts.json = true;
|
|
53
|
-
}
|
|
54
|
-
return opts;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Fetches full detail for every live ruleset EXCEPT per-team ones —
|
|
58
|
-
* `diff.ts`'s `planRulesets` never reads past a per-team ruleset's name, and
|
|
59
|
-
* there can be up to ~70 of them, so fetching each would be up to ~70 extra
|
|
60
|
-
* GETs this reconciler has no use for.
|
|
61
|
-
*/
|
|
62
|
-
async function fetchDetails(r, summaries) {
|
|
63
|
-
const details = new Map();
|
|
64
|
-
for (const s of summaries) {
|
|
65
|
-
if (isPerTeamRulesetName(s.name))
|
|
66
|
-
continue;
|
|
67
|
-
details.set(s.id, await getRuleset(r, s.id));
|
|
68
|
-
}
|
|
69
|
-
return details;
|
|
70
|
-
}
|
|
71
|
-
function renderRuleLine(rule) {
|
|
72
|
-
return rule.type;
|
|
73
|
-
}
|
|
74
|
-
function renderDesired(desired) {
|
|
75
|
-
return [
|
|
76
|
-
` rules: ${desired.rules.map(renderRuleLine).join(", ")}`,
|
|
77
|
-
` bypass: ${desired.bypass_actors.length === 0
|
|
78
|
-
? "(none)"
|
|
79
|
-
: desired.bypass_actors
|
|
80
|
-
.map((a) => `${a.actor_type}#${a.actor_id} (${a.bypass_mode})`)
|
|
81
|
-
.join(", ")}`,
|
|
82
|
-
];
|
|
83
|
-
}
|
|
84
|
-
export function renderPlan(plan) {
|
|
85
|
-
const lines = [];
|
|
86
|
-
for (const create of plan.creates) {
|
|
87
|
-
lines.push(`+ create "${create.desired.name}"`);
|
|
88
|
-
lines.push(...renderDesired(create.desired));
|
|
89
|
-
}
|
|
90
|
-
for (const update of plan.updates) {
|
|
91
|
-
const renameNote = update.liveName !== update.desired.name
|
|
92
|
-
? ` (renamed from "${update.liveName}")`
|
|
93
|
-
: "";
|
|
94
|
-
lines.push(`~ update "${update.desired.name}"${renameNote} [id ${update.id}]`);
|
|
95
|
-
lines.push(...renderDesired(update.desired));
|
|
96
|
-
}
|
|
97
|
-
for (const del of plan.deletes) {
|
|
98
|
-
lines.push(`- delete "${del.name}" [id ${del.id}]`);
|
|
99
|
-
}
|
|
100
|
-
for (const noop of plan.noops) {
|
|
101
|
-
lines.push(`= ok "${noop.name}" [id ${noop.id}] — already matches`);
|
|
102
|
-
}
|
|
103
|
-
if (plan.perTeamSkipped.length > 0) {
|
|
104
|
-
lines.push(` skipped ${plan.perTeamSkipped.length} per-team ruleset(s), owned by teamSync.ts: ` +
|
|
105
|
-
plan.perTeamSkipped.join(", "));
|
|
106
|
-
}
|
|
107
|
-
if (plan.unmanaged.length > 0) {
|
|
108
|
-
lines.push(` unmanaged ${plan.unmanaged.join(", ")} — not a fixed ruleset or a deletion target, left alone`);
|
|
109
|
-
}
|
|
110
|
-
if (lines.length === 0)
|
|
111
|
-
return "(nothing to report)";
|
|
112
|
-
return lines.join("\n");
|
|
113
|
-
}
|
|
114
|
-
async function confirmApply(yes) {
|
|
115
|
-
if (yes)
|
|
116
|
-
return true;
|
|
117
|
-
if (!process.stdin.isTTY) {
|
|
118
|
-
process.stderr.write("devtools github rulesets: --yes is required to apply with no terminal available.\n");
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
return unwrap(await confirm({
|
|
122
|
-
message: "Apply this ruleset plan?",
|
|
123
|
-
initialValue: false,
|
|
124
|
-
}));
|
|
125
|
-
}
|
|
126
|
-
export async function runGithubRulesets(argv) {
|
|
127
|
-
const opts = parseOptions(argv);
|
|
128
|
-
const r = { owner: opts.org, repo: opts.repo };
|
|
129
|
-
let devopsTeamId;
|
|
130
|
-
let adminsTeamId;
|
|
131
|
-
let appId;
|
|
132
|
-
try {
|
|
133
|
-
[devopsTeamId, adminsTeamId, appId] = await Promise.all([
|
|
134
|
-
resolveTeamId(opts.org, DEFAULT_DEVOPS_SLUG),
|
|
135
|
-
resolveTeamId(opts.org, DEFAULT_ADMINS_SLUG),
|
|
136
|
-
resolveAppId(opts.org, opts.appSlug),
|
|
137
|
-
]);
|
|
138
|
-
}
|
|
139
|
-
catch (err) {
|
|
140
|
-
process.stderr.write(`devtools github rulesets: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
141
|
-
return 1;
|
|
142
|
-
}
|
|
143
|
-
// Renovate (TASK-299) is resolved SEPARATELY and is allowed to fail: Sloan
|
|
144
|
-
// may not have installed it yet, and that must not block reconciling
|
|
145
|
-
// everything else. A failure here prints a warning and the plan simply
|
|
146
|
-
// omits it from `~ALL`'s bypass list — re-running after the App is
|
|
147
|
-
// installed picks it up with no further action.
|
|
148
|
-
let renovateAppId;
|
|
149
|
-
try {
|
|
150
|
-
renovateAppId = await resolveAppId(opts.org, DEFAULT_RENOVATE_SLUG);
|
|
151
|
-
}
|
|
152
|
-
catch (err) {
|
|
153
|
-
process.stderr.write(`devtools github rulesets: warning: could not resolve the Renovate App ` +
|
|
154
|
-
`("${DEFAULT_RENOVATE_SLUG}") — ~ALL's plan omits it as a bypass actor ` +
|
|
155
|
-
`until it is installed (TASK-299). ${err instanceof Error ? err.message : String(err)}\n`);
|
|
156
|
-
}
|
|
157
|
-
const actors = { devopsTeamId, adminsTeamId, appId, renovateAppId };
|
|
158
|
-
const desired = buildDesiredRulesets(actors);
|
|
159
|
-
let summaries;
|
|
160
|
-
let details;
|
|
161
|
-
try {
|
|
162
|
-
summaries = await listRulesets(r);
|
|
163
|
-
details = await fetchDetails(r, summaries);
|
|
164
|
-
}
|
|
165
|
-
catch (err) {
|
|
166
|
-
process.stderr.write(`devtools github rulesets: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
167
|
-
return 1;
|
|
168
|
-
}
|
|
169
|
-
const plan = planRulesets(summaries, details, actors, desired);
|
|
170
|
-
if (opts.json) {
|
|
171
|
-
console.log(JSON.stringify(plan, null, 2));
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
console.log(renderPlan(plan));
|
|
175
|
-
}
|
|
176
|
-
if (!opts.apply) {
|
|
177
|
-
if (!opts.json && planHasChanges(plan)) {
|
|
178
|
-
console.log("\n(dry run — pass --apply to write this plan)");
|
|
179
|
-
}
|
|
180
|
-
return 0;
|
|
181
|
-
}
|
|
182
|
-
if (!planHasChanges(plan))
|
|
183
|
-
return 0;
|
|
184
|
-
if (!(await confirmApply(opts.yes)))
|
|
185
|
-
return 1;
|
|
186
|
-
try {
|
|
187
|
-
for (const create of plan.creates) {
|
|
188
|
-
await createRuleset(r, create.desired);
|
|
189
|
-
}
|
|
190
|
-
for (const update of plan.updates) {
|
|
191
|
-
await updateRuleset(r, update.id, update.desired);
|
|
192
|
-
}
|
|
193
|
-
for (const del of plan.deletes) {
|
|
194
|
-
await deleteRuleset(r, del.id);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch (err) {
|
|
198
|
-
process.stderr.write(`devtools github rulesets: apply failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
199
|
-
return 1;
|
|
200
|
-
}
|
|
201
|
-
console.log("Applied.");
|
|
202
|
-
return 0;
|
|
203
|
-
}
|