@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.
Files changed (271) hide show
  1. package/README.md +72 -11
  2. package/bin/devtools.mjs +4 -4
  3. package/dist/cli-CZKkNHK5.js +6619 -0
  4. package/dist/failure-log-BBPGejQv.js +256 -0
  5. package/dist/launch.js +267 -281
  6. package/dist/local-env-D8HC2JK_.js +351 -0
  7. package/dist/local-env-DrBCXqD8.js +2 -0
  8. package/dist/menu-CQBsQRgH.js +1684 -0
  9. package/dist/peer-redirect-hooks.js +15 -0
  10. package/dist/peers-RBplwovA.js +229 -0
  11. package/dist/root-C2OmZDwD.js +94 -0
  12. package/dist/stack-Bx-55oi_.js +2 -0
  13. package/dist/stack-BynCgRVS.js +181 -0
  14. package/dist/supabase-project-0knM0mqd.js +426 -0
  15. package/dist/telemetry-C4foD8MD.js +249 -0
  16. package/dist/telemetry.js +2 -148
  17. package/package.json +10 -29
  18. package/bin/devtools-ci-bare.mjs +0 -48
  19. package/bin/devtools-ci.mjs +0 -31
  20. package/dist/args.d.ts +0 -26
  21. package/dist/args.js +0 -78
  22. package/dist/bws/bw.d.ts +0 -18
  23. package/dist/bws/bw.js +0 -70
  24. package/dist/bws/client.d.ts +0 -43
  25. package/dist/bws/client.js +0 -363
  26. package/dist/bws/environments.d.ts +0 -96
  27. package/dist/bws/environments.js +0 -111
  28. package/dist/bws/pace.d.ts +0 -25
  29. package/dist/bws/pace.js +0 -43
  30. package/dist/bws/retry.d.ts +0 -12
  31. package/dist/bws/retry.js +0 -39
  32. package/dist/bws/store.d.ts +0 -1
  33. package/dist/bws/store.js +0 -32
  34. package/dist/bws/token.d.ts +0 -31
  35. package/dist/bws/token.js +0 -88
  36. package/dist/bws/vault.d.ts +0 -29
  37. package/dist/bws/vault.js +0 -200
  38. package/dist/catalog.d.ts +0 -57
  39. package/dist/catalog.js +0 -57
  40. package/dist/cf/build.d.ts +0 -13
  41. package/dist/cf/build.js +0 -65
  42. package/dist/cf/commands.d.ts +0 -1
  43. package/dist/cf/commands.js +0 -172
  44. package/dist/cf/local-env.d.ts +0 -58
  45. package/dist/cf/local-env.js +0 -177
  46. package/dist/ci.d.ts +0 -1
  47. package/dist/ci.js +0 -328
  48. package/dist/cli.d.ts +0 -11
  49. package/dist/cli.js +0 -1094
  50. package/dist/commands.d.ts +0 -261
  51. package/dist/commands.js +0 -1318
  52. package/dist/completions.d.ts +0 -4
  53. package/dist/completions.js +0 -110
  54. package/dist/cron/commands.d.ts +0 -47
  55. package/dist/cron/commands.js +0 -372
  56. package/dist/cron/discovery.d.ts +0 -55
  57. package/dist/cron/discovery.js +0 -85
  58. package/dist/cron/schema.d.ts +0 -45
  59. package/dist/cron/schema.js +0 -35
  60. package/dist/db/config-push.d.ts +0 -8
  61. package/dist/db/config-push.js +0 -11
  62. package/dist/db/connection.d.ts +0 -79
  63. package/dist/db/connection.js +0 -114
  64. package/dist/db/exec.d.ts +0 -1
  65. package/dist/db/exec.js +0 -11
  66. package/dist/db/generate-types.d.ts +0 -1
  67. package/dist/db/generate-types.js +0 -4
  68. package/dist/db/generated-env.d.ts +0 -69
  69. package/dist/db/generated-env.js +0 -113
  70. package/dist/db/introspect.d.ts +0 -1
  71. package/dist/db/introspect.js +0 -122
  72. package/dist/db/local-env.d.ts +0 -65
  73. package/dist/db/local-env.js +0 -23
  74. package/dist/db/new-migration.d.ts +0 -8
  75. package/dist/db/new-migration.js +0 -69
  76. package/dist/db/pooler.d.ts +0 -23
  77. package/dist/db/pooler.js +0 -55
  78. package/dist/db/run.d.ts +0 -68
  79. package/dist/db/run.js +0 -156
  80. package/dist/db/seed-buckets.d.ts +0 -2
  81. package/dist/db/seed-buckets.js +0 -4
  82. package/dist/db/seed-production.d.ts +0 -8
  83. package/dist/db/seed-production.js +0 -79
  84. package/dist/db/seed-roles.d.ts +0 -7
  85. package/dist/db/seed-roles.js +0 -33
  86. package/dist/db/session-refresh.d.ts +0 -43
  87. package/dist/db/session-refresh.js +0 -40
  88. package/dist/deploy/migrations.d.ts +0 -20
  89. package/dist/deploy/migrations.js +0 -68
  90. package/dist/deploy/orphans.d.ts +0 -20
  91. package/dist/deploy/orphans.js +0 -177
  92. package/dist/deploy/preflight.d.ts +0 -53
  93. package/dist/deploy/preflight.js +0 -189
  94. package/dist/deploy/report.d.ts +0 -26
  95. package/dist/deploy/report.js +0 -70
  96. package/dist/deploy/require-planner.d.ts +0 -2
  97. package/dist/deploy/require-planner.js +0 -60
  98. package/dist/deploy/require-token.d.ts +0 -15
  99. package/dist/deploy/require-token.js +0 -78
  100. package/dist/deploy/secrets-file.d.ts +0 -17
  101. package/dist/deploy/secrets-file.js +0 -166
  102. package/dist/deploy/write-env.d.ts +0 -40
  103. package/dist/deploy/write-env.js +0 -395
  104. package/dist/docs/index-pages.d.ts +0 -38
  105. package/dist/docs/index-pages.js +0 -186
  106. package/dist/emails/commands.d.ts +0 -15
  107. package/dist/emails/commands.js +0 -130
  108. package/dist/emails/email-types.d.ts +0 -13
  109. package/dist/emails/email-types.js +0 -1
  110. package/dist/emails/fixtures.d.ts +0 -20
  111. package/dist/emails/fixtures.js +0 -20
  112. package/dist/env/audit.d.ts +0 -206
  113. package/dist/env/audit.js +0 -436
  114. package/dist/env/cloudflare.d.ts +0 -21
  115. package/dist/env/cloudflare.js +0 -61
  116. package/dist/env/commands.d.ts +0 -99
  117. package/dist/env/commands.js +0 -613
  118. package/dist/env/discovery.d.ts +0 -15
  119. package/dist/env/discovery.js +0 -239
  120. package/dist/env/document.d.ts +0 -119
  121. package/dist/env/document.js +0 -323
  122. package/dist/env/example.d.ts +0 -68
  123. package/dist/env/example.js +0 -605
  124. package/dist/env/selection.d.ts +0 -105
  125. package/dist/env/selection.js +0 -228
  126. package/dist/env-entry.d.ts +0 -49
  127. package/dist/env-entry.js +0 -204
  128. package/dist/environment-doctor.d.ts +0 -45
  129. package/dist/environment-doctor.js +0 -429
  130. package/dist/environment.d.ts +0 -92
  131. package/dist/environment.js +0 -185
  132. package/dist/fingerprint.d.ts +0 -10
  133. package/dist/fingerprint.js +0 -18
  134. package/dist/gen/campus-map.d.ts +0 -1
  135. package/dist/gen/campus-map.js +0 -473
  136. package/dist/gen/commands.d.ts +0 -1
  137. package/dist/gen/commands.js +0 -25
  138. package/dist/gen/hypno.d.ts +0 -1
  139. package/dist/gen/hypno.js +0 -134
  140. package/dist/gh/client.d.ts +0 -80
  141. package/dist/gh/client.js +0 -259
  142. package/dist/gh/environments.d.ts +0 -83
  143. package/dist/gh/environments.js +0 -178
  144. package/dist/gh/rulesets/actors.d.ts +0 -14
  145. package/dist/gh/rulesets/actors.js +0 -69
  146. package/dist/gh/rulesets/api.d.ts +0 -15
  147. package/dist/gh/rulesets/api.js +0 -104
  148. package/dist/gh/rulesets/commands.d.ts +0 -3
  149. package/dist/gh/rulesets/commands.js +0 -203
  150. package/dist/gh/rulesets/desired.d.ts +0 -112
  151. package/dist/gh/rulesets/desired.js +0 -271
  152. package/dist/gh/rulesets/diff.d.ts +0 -65
  153. package/dist/gh/rulesets/diff.js +0 -170
  154. package/dist/gh/rulesets/types.d.ts +0 -76
  155. package/dist/gh/rulesets/types.js +0 -13
  156. package/dist/gh/settings/api.d.ts +0 -40
  157. package/dist/gh/settings/api.js +0 -180
  158. package/dist/gh/settings/commands.d.ts +0 -1
  159. package/dist/gh/settings/commands.js +0 -254
  160. package/dist/gh/settings/desired.d.ts +0 -53
  161. package/dist/gh/settings/desired.js +0 -43
  162. package/dist/gh/settings/diff.d.ts +0 -57
  163. package/dist/gh/settings/diff.js +0 -161
  164. package/dist/gh/settings/types.d.ts +0 -92
  165. package/dist/gh/settings/types.js +0 -11
  166. package/dist/gh/settings/workflows.d.ts +0 -41
  167. package/dist/gh/settings/workflows.js +0 -128
  168. package/dist/grantRoot.d.ts +0 -42
  169. package/dist/grantRoot.js +0 -89
  170. package/dist/help.d.ts +0 -18
  171. package/dist/help.js +0 -169
  172. package/dist/images/commands.d.ts +0 -32
  173. package/dist/images/commands.js +0 -231
  174. package/dist/images/events.d.ts +0 -47
  175. package/dist/images/events.js +0 -116
  176. package/dist/images/graphics.d.ts +0 -70
  177. package/dist/images/graphics.js +0 -234
  178. package/dist/images/open-graph-types.d.ts +0 -75
  179. package/dist/images/open-graph-types.js +0 -1
  180. package/dist/images/prompts.d.ts +0 -34
  181. package/dist/images/prompts.js +0 -100
  182. package/dist/images/render.d.ts +0 -21
  183. package/dist/images/render.js +0 -61
  184. package/dist/images/select.d.ts +0 -64
  185. package/dist/images/select.js +0 -96
  186. package/dist/instance.d.ts +0 -69
  187. package/dist/instance.js +0 -102
  188. package/dist/invocation.d.ts +0 -49
  189. package/dist/invocation.js +0 -78
  190. package/dist/launch-ci.d.ts +0 -1
  191. package/dist/launch-ci.js +0 -90
  192. package/dist/launch.d.ts +0 -18
  193. package/dist/menu.d.ts +0 -43
  194. package/dist/menu.js +0 -300
  195. package/dist/moderation.d.ts +0 -70
  196. package/dist/moderation.js +0 -128
  197. package/dist/oauth/browser.d.ts +0 -1
  198. package/dist/oauth/browser.js +0 -13
  199. package/dist/oauth/config.d.ts +0 -24
  200. package/dist/oauth/config.js +0 -24
  201. package/dist/oauth/connect-url.d.ts +0 -17
  202. package/dist/oauth/connect-url.js +0 -23
  203. package/dist/oauth/db.d.ts +0 -37
  204. package/dist/oauth/db.js +0 -108
  205. package/dist/oauth/device.d.ts +0 -93
  206. package/dist/oauth/device.js +0 -198
  207. package/dist/oauth/discovery.d.ts +0 -28
  208. package/dist/oauth/discovery.js +0 -54
  209. package/dist/oauth/env-file.d.ts +0 -6
  210. package/dist/oauth/env-file.js +0 -35
  211. package/dist/oauth/exchange.d.ts +0 -43
  212. package/dist/oauth/exchange.js +0 -93
  213. package/dist/oauth/label.d.ts +0 -1
  214. package/dist/oauth/label.js +0 -25
  215. package/dist/oauth/loopback.d.ts +0 -52
  216. package/dist/oauth/loopback.js +0 -168
  217. package/dist/oauth/pkce.d.ts +0 -6
  218. package/dist/oauth/pkce.js +0 -34
  219. package/dist/oauth/target.d.ts +0 -54
  220. package/dist/oauth/target.js +0 -102
  221. package/dist/oauth/transport.d.ts +0 -41
  222. package/dist/oauth/transport.js +0 -32
  223. package/dist/oauth/wizard.d.ts +0 -40
  224. package/dist/oauth/wizard.js +0 -578
  225. package/dist/persona.d.ts +0 -18
  226. package/dist/persona.js +0 -221
  227. package/dist/pick.d.ts +0 -6
  228. package/dist/pick.js +0 -64
  229. package/dist/pipes.d.ts +0 -1
  230. package/dist/pipes.js +0 -25
  231. package/dist/planner/checks.d.ts +0 -26
  232. package/dist/planner/checks.js +0 -104
  233. package/dist/planner/commands.d.ts +0 -10
  234. package/dist/planner/commands.js +0 -367
  235. package/dist/planner/db.d.ts +0 -15
  236. package/dist/planner/db.js +0 -33
  237. package/dist/planner/role.d.ts +0 -33
  238. package/dist/planner/role.js +0 -68
  239. package/dist/repo/peer-redirect-hooks.d.ts +0 -16
  240. package/dist/repo/peer-redirect-hooks.js +0 -10
  241. package/dist/repo/peer-redirect.d.ts +0 -4
  242. package/dist/repo/peer-redirect.js +0 -26
  243. package/dist/repo/peers.d.ts +0 -39
  244. package/dist/repo/peers.js +0 -139
  245. package/dist/repo/resolve.d.ts +0 -41
  246. package/dist/repo/resolve.js +0 -152
  247. package/dist/repo/root.d.ts +0 -23
  248. package/dist/repo/root.js +0 -91
  249. package/dist/repo/source.d.ts +0 -14
  250. package/dist/repo/source.js +0 -54
  251. package/dist/repo/supabase-project.d.ts +0 -60
  252. package/dist/repo/supabase-project.js +0 -124
  253. package/dist/repo/tsx-loader.d.ts +0 -6
  254. package/dist/repo/tsx-loader.js +0 -41
  255. package/dist/run/pick.d.ts +0 -90
  256. package/dist/run/pick.js +0 -664
  257. package/dist/setup.d.ts +0 -1
  258. package/dist/setup.js +0 -280
  259. package/dist/stack.d.ts +0 -48
  260. package/dist/stack.js +0 -403
  261. package/dist/telemetry.d.ts +0 -59
  262. package/dist/tier.d.ts +0 -40
  263. package/dist/tier.js +0 -96
  264. package/dist/ui.d.ts +0 -40
  265. package/dist/ui.js +0 -71
  266. package/dist/version.d.ts +0 -7
  267. package/dist/version.js +0 -17
  268. package/dist/workers.d.ts +0 -5
  269. package/dist/workers.js +0 -33
  270. package/dist/workflows/commands.d.ts +0 -54
  271. package/dist/workflows/commands.js +0 -763
@@ -1,57 +0,0 @@
1
- /**
2
- * Live settings snapshot -> a plan of per-setting checks, against
3
- * `desired.ts`'s desired shape.
4
- *
5
- * Unlike `../rulesets/diff.ts` (one shape, N instances), TASK-342's settings
6
- * are N unrelated resources on one repository — security-and-analysis,
7
- * Dependabot, three Actions endpoints, and three environments' branch
8
- * policies. So the plan here is a flat list of named checks, each its own
9
- * `ok` / `drift` / `unsupported` verdict, rather than create/update/delete
10
- * buckets. `fixable` marks which drifted checks `commands.ts`'s `--apply`
11
- * step is allowed to write — `false` for anything this reconciler must only
12
- * ever report: a missing environment (nothing here creates one), and a
13
- * missing required reviewer (nothing here invents one).
14
- */
15
- import type { DesiredEnvironmentPolicy, DesiredSettings } from "./desired.js";
16
- import type { LiveActionsPermissions, LiveAutomatedSecurityFixes, LiveBranchPolicy, LiveEnvironment, LiveRepo, LiveSelectedActions, LiveWorkflowPermissions } from "./types.js";
17
- import type { ActionUse } from "./workflows.js";
18
- export type CheckStatus = "ok" | "drift" | "unsupported";
19
- export interface SettingCheck {
20
- key: string;
21
- description: string;
22
- desired: string;
23
- live: string;
24
- status: CheckStatus;
25
- /** Whether `commands.ts`'s apply step may write this check's fix. */
26
- fixable: boolean;
27
- }
28
- export interface LiveEnvironmentSnapshot {
29
- environment: LiveEnvironment | null;
30
- branchPolicies: LiveBranchPolicy[];
31
- }
32
- export interface LiveSettingsSnapshot {
33
- repo: LiveRepo;
34
- vulnerabilityAlerts: boolean;
35
- automatedSecurityFixes: LiveAutomatedSecurityFixes;
36
- actionsPermissions: LiveActionsPermissions;
37
- selectedActions: LiveSelectedActions;
38
- workflowPermissions: LiveWorkflowPermissions;
39
- environments: Record<DesiredEnvironmentPolicy["name"], LiveEnvironmentSnapshot>;
40
- }
41
- export interface SettingsPlan {
42
- checks: SettingCheck[];
43
- unpinnedActions: readonly ActionUse[];
44
- /** Whether `sha_pinning_required` was refused this run because the repo's own workflows carry an unpinned `uses:`. */
45
- refuseShaPinning: boolean;
46
- }
47
- /**
48
- * Builds the plan. `unpinnedActions` comes from `workflows.ts` — passed in
49
- * rather than recomputed here so this stays a pure function over data
50
- * `commands.ts` already gathered (and so `diff.test.ts` can assert
51
- * `refuseShaPinning` with a plain fixture array, no filesystem involved).
52
- */
53
- export declare function planSettings(desired: DesiredSettings, live: LiveSettingsSnapshot, unpinnedActions: readonly ActionUse[]): SettingsPlan;
54
- /** Whether a plan has any FIXABLE drift `--apply` would write. */
55
- export declare function planHasFixableChanges(plan: SettingsPlan): boolean;
56
- /** Whether a plan reports any drift at all, fixable or not (for exit-code / `--check` style callers). */
57
- export declare function planHasChanges(plan: SettingsPlan): boolean;
@@ -1,161 +0,0 @@
1
- function sameSet(a, b) {
2
- if (a.length !== b.length)
3
- return false;
4
- const sa = [...a].sort();
5
- const sb = [...b].sort();
6
- return sa.every((v, i) => v === sb[i]);
7
- }
8
- function secretScanningCheck(key, apiField, description, desiredEnabled, sa) {
9
- const field = sa?.[apiField];
10
- if (sa === null || field === undefined) {
11
- return {
12
- key,
13
- description,
14
- desired: desiredEnabled ? "enabled" : "disabled",
15
- live: "unsupported on this repo",
16
- status: "unsupported",
17
- fixable: false,
18
- };
19
- }
20
- const enabled = field.status === "enabled";
21
- return {
22
- key,
23
- description,
24
- desired: desiredEnabled ? "enabled" : "disabled",
25
- live: enabled ? "enabled" : "disabled",
26
- status: enabled === desiredEnabled ? "ok" : "drift",
27
- fixable: true,
28
- };
29
- }
30
- /**
31
- * Builds the plan. `unpinnedActions` comes from `workflows.ts` — passed in
32
- * rather than recomputed here so this stays a pure function over data
33
- * `commands.ts` already gathered (and so `diff.test.ts` can assert
34
- * `refuseShaPinning` with a plain fixture array, no filesystem involved).
35
- */
36
- export function planSettings(desired, live, unpinnedActions) {
37
- const checks = [];
38
- const sa = live.repo.security_and_analysis;
39
- checks.push(secretScanningCheck("security_and_analysis.secret_scanning", "secret_scanning", "Secret scanning", desired.securityAndAnalysis.secretScanning, sa), secretScanningCheck("security_and_analysis.secret_scanning_push_protection", "secret_scanning_push_protection", "Secret scanning push protection", desired.securityAndAnalysis.secretScanningPushProtection, sa), secretScanningCheck("security_and_analysis.secret_scanning_non_provider_patterns", "secret_scanning_non_provider_patterns", "Secret scanning non-provider patterns", desired.securityAndAnalysis.secretScanningNonProviderPatterns, sa), secretScanningCheck("security_and_analysis.secret_scanning_validity_checks", "secret_scanning_validity_checks", "Secret scanning validity checks", desired.securityAndAnalysis.secretScanningValidityChecks, sa));
40
- checks.push({
41
- key: "vulnerability_alerts",
42
- description: "Dependabot alerts",
43
- desired: desired.vulnerabilityAlerts ? "enabled" : "disabled",
44
- live: live.vulnerabilityAlerts ? "enabled" : "disabled",
45
- status: live.vulnerabilityAlerts === desired.vulnerabilityAlerts ? "ok" : "drift",
46
- fixable: true,
47
- });
48
- checks.push({
49
- key: "dependabot_security_updates",
50
- description: "Dependabot security updates",
51
- desired: desired.dependabotSecurityUpdates ? "enabled" : "disabled",
52
- live: live.automatedSecurityFixes.enabled ? "enabled" : "disabled",
53
- status: live.automatedSecurityFixes.enabled === desired.dependabotSecurityUpdates
54
- ? "ok"
55
- : "drift",
56
- fixable: true,
57
- });
58
- const refuseShaPinning = unpinnedActions.length > 0;
59
- const curShaPinning = live.actionsPermissions.sha_pinning_required === true;
60
- checks.push({
61
- key: "actions.sha_pinning_required",
62
- description: "Require actions to be pinned to a full-length SHA",
63
- desired: refuseShaPinning
64
- ? `true (REFUSED — ${unpinnedActions.length} unpinned uses: found)`
65
- : "true",
66
- live: curShaPinning ? "true" : "false",
67
- status: refuseShaPinning
68
- ? "unsupported"
69
- : curShaPinning === desired.actions.shaPinningRequired
70
- ? "ok"
71
- : "drift",
72
- fixable: !refuseShaPinning,
73
- });
74
- checks.push({
75
- key: "actions.allowed_actions",
76
- description: "Allowed actions",
77
- desired: desired.actions.allowedActions,
78
- live: live.actionsPermissions.allowed_actions ?? "(unset)",
79
- status: live.actionsPermissions.allowed_actions === desired.actions.allowedActions
80
- ? "ok"
81
- : "drift",
82
- fixable: true,
83
- });
84
- const patternsMatch = live.selectedActions.github_owned_allowed ===
85
- desired.actions.githubOwnedAllowed &&
86
- live.selectedActions.verified_allowed === desired.actions.verifiedAllowed &&
87
- sameSet(live.selectedActions.patterns_allowed, desired.actions.patterns);
88
- checks.push({
89
- key: "actions.selected_actions",
90
- description: "Selected-actions allowlist (github-owned, verified, patterns)",
91
- desired: `github_owned=${desired.actions.githubOwnedAllowed}, verified=${desired.actions.verifiedAllowed}, patterns=[${[...desired.actions.patterns].sort().join(", ")}]`,
92
- live: `github_owned=${live.selectedActions.github_owned_allowed}, verified=${live.selectedActions.verified_allowed}, patterns=[${[...live.selectedActions.patterns_allowed].sort().join(", ")}]`,
93
- status: patternsMatch ? "ok" : "drift",
94
- fixable: true,
95
- });
96
- const workflowPermissionsMatch = live.workflowPermissions.default_workflow_permissions ===
97
- desired.workflowPermissions.defaultWorkflowPermissions &&
98
- live.workflowPermissions.can_approve_pull_request_reviews ===
99
- desired.workflowPermissions.canApprovePullRequestReviews;
100
- checks.push({
101
- key: "actions.workflow_permissions",
102
- description: "Default workflow permissions",
103
- desired: `${desired.workflowPermissions.defaultWorkflowPermissions}, can_approve_pull_request_reviews=${desired.workflowPermissions.canApprovePullRequestReviews}`,
104
- live: `${live.workflowPermissions.default_workflow_permissions}, can_approve_pull_request_reviews=${live.workflowPermissions.can_approve_pull_request_reviews}`,
105
- status: workflowPermissionsMatch ? "ok" : "drift",
106
- fixable: true,
107
- });
108
- for (const env of desired.environments) {
109
- const snapshot = live.environments[env.name];
110
- if (!snapshot.environment) {
111
- checks.push({
112
- key: `environments.${env.name}`,
113
- description: `Environment "${env.name}"`,
114
- desired: `branches=[${env.allowedBranches.join(", ")}], reviewers=${env.requireReviewers}`,
115
- live: "environment does not exist",
116
- status: "unsupported",
117
- fixable: false,
118
- });
119
- continue;
120
- }
121
- const liveBranches = snapshot.branchPolicies.map((p) => p.name);
122
- const branchesMatch = snapshot.environment.deployment_branch_policy?.custom_branch_policies ===
123
- true &&
124
- snapshot.environment.deployment_branch_policy?.protected_branches ===
125
- false &&
126
- sameSet(liveBranches, env.allowedBranches);
127
- checks.push({
128
- key: `environments.${env.name}.branch_policy`,
129
- description: `Environment "${env.name}" deployment branch policy`,
130
- desired: [...env.allowedBranches].sort().join(", "),
131
- live: snapshot.environment.deployment_branch_policy
132
- ? [...liveBranches].sort().join(", ") ||
133
- "(no branch restriction — all branches allowed)"
134
- : "(no deployment branch policy configured)",
135
- status: branchesMatch ? "ok" : "drift",
136
- fixable: true,
137
- });
138
- if (env.requireReviewers) {
139
- const hasReviewers = (snapshot.environment.protection_rules ?? []).some((rule) => rule.type === "required_reviewers" &&
140
- (rule.reviewers?.length ?? 0) > 0);
141
- checks.push({
142
- key: `environments.${env.name}.required_reviewers`,
143
- description: `Environment "${env.name}" required reviewers`,
144
- desired: "at least one reviewer configured",
145
- live: hasReviewers ? "configured" : "none configured",
146
- status: hasReviewers ? "ok" : "drift",
147
- // Never auto-fixable — this reconciler does not invent reviewers.
148
- fixable: false,
149
- });
150
- }
151
- }
152
- return { checks, unpinnedActions, refuseShaPinning };
153
- }
154
- /** Whether a plan has any FIXABLE drift `--apply` would write. */
155
- export function planHasFixableChanges(plan) {
156
- return plan.checks.some((c) => c.status === "drift" && c.fixable);
157
- }
158
- /** Whether a plan reports any drift at all, fixable or not (for exit-code / `--check` style callers). */
159
- export function planHasChanges(plan) {
160
- return plan.checks.some((c) => c.status === "drift");
161
- }
@@ -1,92 +0,0 @@
1
- /**
2
- * The shapes this reconciler reads and writes — TASK-342's `devtools github
3
- * settings`, sibling of `../rulesets/types.ts`.
4
- *
5
- * Same reasoning as that file's header: a narrower slice of GitHub's REST
6
- * responses than Octokit's generated types, because devtools has no Octokit
7
- * dependency (every GitHub call goes through the `gh` CLI — see
8
- * `../client.ts`'s header). Only the fields this reconciler actually reads
9
- * or writes are represented.
10
- */
11
- export type SecretScanningStatus = "enabled" | "disabled";
12
- export interface LiveSecurityAndAnalysisField {
13
- status: SecretScanningStatus;
14
- }
15
- /**
16
- * `GET /repos/{owner}/{repo}`'s `security_and_analysis` object.
17
- *
18
- * Every field is optional because GitHub omits a sub-feature entirely when
19
- * it is not available on the repository's plan — `secret_scanning_
20
- * non_provider_patterns` and `secret_scanning_validity_checks` in particular
21
- * are Advanced-Security features GitHub grants free on PUBLIC repos, but an
22
- * absent key (not a `"disabled"` status) is how a private repo or a plan
23
- * without them reports "not offered here". `diff.ts` reads that absence as
24
- * `"unsupported"`, never as drift.
25
- */
26
- export interface LiveSecurityAndAnalysis {
27
- secret_scanning?: LiveSecurityAndAnalysisField;
28
- secret_scanning_push_protection?: LiveSecurityAndAnalysisField;
29
- secret_scanning_non_provider_patterns?: LiveSecurityAndAnalysisField;
30
- secret_scanning_validity_checks?: LiveSecurityAndAnalysisField;
31
- }
32
- /** The slice of `GET /repos/{owner}/{repo}` this reconciler reads. */
33
- export interface LiveRepo {
34
- private: boolean;
35
- /** `null` on a repo where security-and-analysis has never been touched. */
36
- security_and_analysis: LiveSecurityAndAnalysis | null;
37
- }
38
- /** `GET`/`PUT /repos/{owner}/{repo}/automated-security-fixes` — Dependabot security updates. */
39
- export interface LiveAutomatedSecurityFixes {
40
- enabled: boolean;
41
- paused?: boolean;
42
- }
43
- /** `GET`/`PUT /repos/{owner}/{repo}/actions/permissions`. */
44
- export interface LiveActionsPermissions {
45
- enabled: boolean;
46
- allowed_actions?: "all" | "local_only" | "selected";
47
- selected_actions_url?: string;
48
- /**
49
- * "Require actions to be pinned to a full-length commit SHA" — the field
50
- * this reconciler exists to set, gated on every `uses:` in the repo's own
51
- * workflows already being pinned (see `workflows.ts`).
52
- */
53
- sha_pinning_required?: boolean;
54
- }
55
- /** `GET`/`PUT /repos/{owner}/{repo}/actions/permissions/selected-actions`. */
56
- export interface LiveSelectedActions {
57
- github_owned_allowed: boolean;
58
- verified_allowed: boolean;
59
- patterns_allowed: string[];
60
- }
61
- /** `GET`/`PUT /repos/{owner}/{repo}/actions/permissions/workflow`. */
62
- export interface LiveWorkflowPermissions {
63
- default_workflow_permissions: "read" | "write";
64
- can_approve_pull_request_reviews: boolean;
65
- }
66
- /** One entry of an environment's `deployment_branch_policy`. */
67
- export interface LiveDeploymentBranchPolicy {
68
- protected_branches: boolean;
69
- custom_branch_policies: boolean;
70
- }
71
- export interface LiveEnvironmentReviewer {
72
- type: string;
73
- reviewer?: {
74
- id: number;
75
- type?: string;
76
- };
77
- }
78
- export interface LiveProtectionRule {
79
- type: string;
80
- reviewers?: LiveEnvironmentReviewer[];
81
- }
82
- /** `GET /repos/{owner}/{repo}/environments/{name}`. */
83
- export interface LiveEnvironment {
84
- name: string;
85
- protection_rules?: LiveProtectionRule[];
86
- deployment_branch_policy: LiveDeploymentBranchPolicy | null;
87
- }
88
- /** One entry of `GET /repos/{owner}/{repo}/environments/{name}/deployment-branch-policies`. */
89
- export interface LiveBranchPolicy {
90
- id: number;
91
- name: string;
92
- }
@@ -1,11 +0,0 @@
1
- /**
2
- * The shapes this reconciler reads and writes — TASK-342's `devtools github
3
- * settings`, sibling of `../rulesets/types.ts`.
4
- *
5
- * Same reasoning as that file's header: a narrower slice of GitHub's REST
6
- * responses than Octokit's generated types, because devtools has no Octokit
7
- * dependency (every GitHub call goes through the `gh` CLI — see
8
- * `../client.ts`'s header). Only the fields this reconciler actually reads
9
- * or writes are represented.
10
- */
11
- export {};
@@ -1,41 +0,0 @@
1
- /** An `owner/repo@ref`-shaped `uses:`; `null` for a local path this reconciler does not govern. */
2
- export interface ActionRef {
3
- owner: string;
4
- repo: string;
5
- version: string;
6
- }
7
- export interface ActionUse {
8
- /** The `uses:` value verbatim, trailing comment and quoting stripped. */
9
- raw: string;
10
- ref: ActionRef | null;
11
- file: string;
12
- line: number;
13
- }
14
- /** The directory `uses:` lines are scanned from — `.github/workflows` at the repo root. */
15
- export declare function workflowsDir(repoRoot?: string): string;
16
- /** Every `*.yml`/`*.yaml` file directly inside `.github/workflows`, sorted for stable output. Empty, not throwing, when the directory does not exist. */
17
- export declare function listWorkflowFiles(repoRoot?: string): string[];
18
- /**
19
- * Parses every `uses:` line out of one workflow file's text.
20
- *
21
- * A plain line scan, not a YAML parser: workflow files are trusted repo
22
- * content, `uses:` is always a scalar on its own line in every workflow
23
- * DevDogsUGA has ever written (job-level and step-level alike), and pulling
24
- * in a YAML dependency to read one field would be a worse trade than a
25
- * regex that `workflows.test.ts` pins against the repo's real files.
26
- */
27
- export declare function parseUsesLines(file: string, contents: string): ActionUse[];
28
- /** Every `uses:` across every workflow file in the repo. */
29
- export declare function collectActionUses(repoRoot?: string): ActionUse[];
30
- /** `owner/repo@ref` uses only — local paths carry no ref to check or allow-list. */
31
- export declare function externalActionUses(uses: readonly ActionUse[]): ActionUse[];
32
- export declare function isShaPinned(use: ActionUse): boolean;
33
- /** Every external `uses:` NOT pinned to a 40-hex SHA — the refusal list for `sha_pinning_required`. */
34
- export declare function unpinnedActionUses(uses: readonly ActionUse[]): ActionUse[];
35
- /**
36
- * `owner/repo@*` for every distinct external action the workflows use,
37
- * sorted and deduplicated — the `patterns_allowed` list for `allowed_actions:
38
- * selected`, computed from what the workflows actually reference rather than
39
- * hand-maintained.
40
- */
41
- export declare function computeActionPatterns(uses: readonly ActionUse[]): string[];
@@ -1,128 +0,0 @@
1
- /**
2
- * Every `uses:` a repository's own `.github/workflows/*.y*ml` files name,
3
- * and whether each one is pinned to a 40-hex commit SHA.
4
- *
5
- * TASK-342's `sha_pinning_required` plan item must never propose turning
6
- * that Actions setting on while the repository's OWN workflows would
7
- * immediately fail under it — GitHub refuses an unpinned `uses:` the moment
8
- * the setting is live. This module is the check that runs BEFORE `desired.ts`
9
- * builds a plan, using the same repo-root discovery contract every other
10
- * devtools module reads a target repo through (`findRepoRoot()`, see
11
- * `../../repo/root.ts`'s header).
12
- *
13
- * A local action (`uses: ./.github/actions/…`) or a same-repo reusable
14
- * workflow (`uses: ./.github/workflows/…`) has no marketplace ref to pin —
15
- * there is no `@<sha>` for a path — so both are treated as pinned by
16
- * definition and excluded from `computeActionPatterns()`'s allow-list: they
17
- * are not "an action" in the sense `allowed_actions: selected` or SHA
18
- * pinning ever governs.
19
- */
20
- import { readFileSync, readdirSync } from "node:fs";
21
- import { join } from "node:path";
22
- import { findRepoRoot } from "../../repo/root.js";
23
- const FULL_SHA = /^[0-9a-f]{40}$/;
24
- /** The directory `uses:` lines are scanned from — `.github/workflows` at the repo root. */
25
- export function workflowsDir(repoRoot = findRepoRoot()) {
26
- return join(repoRoot, ".github", "workflows");
27
- }
28
- /** Every `*.yml`/`*.yaml` file directly inside `.github/workflows`, sorted for stable output. Empty, not throwing, when the directory does not exist. */
29
- export function listWorkflowFiles(repoRoot = findRepoRoot()) {
30
- const dir = workflowsDir(repoRoot);
31
- let entries;
32
- try {
33
- entries = readdirSync(dir);
34
- }
35
- catch {
36
- return [];
37
- }
38
- return entries
39
- .filter((name) => /\.ya?ml$/i.test(name))
40
- .map((name) => join(dir, name))
41
- .sort();
42
- }
43
- const USES_LINE = /^\s*(?:-\s*)?uses:\s*(.+?)\s*$/;
44
- /**
45
- * Parses every `uses:` line out of one workflow file's text.
46
- *
47
- * A plain line scan, not a YAML parser: workflow files are trusted repo
48
- * content, `uses:` is always a scalar on its own line in every workflow
49
- * DevDogsUGA has ever written (job-level and step-level alike), and pulling
50
- * in a YAML dependency to read one field would be a worse trade than a
51
- * regex that `workflows.test.ts` pins against the repo's real files.
52
- */
53
- export function parseUsesLines(file, contents) {
54
- const out = [];
55
- const lines = contents.split("\n");
56
- for (let i = 0; i < lines.length; i++) {
57
- const match = USES_LINE.exec(lines[i]);
58
- if (!match)
59
- continue;
60
- let value = match[1];
61
- const hash = value.indexOf("#");
62
- if (hash !== -1)
63
- value = value.slice(0, hash).trim();
64
- value = value.replace(/^["']|["']$/g, "");
65
- if (value === "")
66
- continue;
67
- if (value.startsWith(".") || value.startsWith("docker://")) {
68
- out.push({ raw: value, ref: null, file, line: i + 1 });
69
- continue;
70
- }
71
- const at = value.lastIndexOf("@");
72
- const spec = at === -1 ? value : value.slice(0, at);
73
- const version = at === -1 ? "" : value.slice(at + 1);
74
- const slash = spec.indexOf("/");
75
- if (at === -1 || slash === -1) {
76
- // No `@ref`, or no `owner/repo` shape — not a marketplace action this
77
- // reconciler can classify; recorded with `ref: null` so it is neither
78
- // flagged as unpinned nor added to the allow-list, and is still
79
- // visible in a raw dump if one is ever needed.
80
- out.push({ raw: value, ref: null, file, line: i + 1 });
81
- continue;
82
- }
83
- out.push({
84
- raw: value,
85
- ref: {
86
- owner: spec.slice(0, slash),
87
- repo: spec.slice(slash + 1),
88
- version,
89
- },
90
- file,
91
- line: i + 1,
92
- });
93
- }
94
- return out;
95
- }
96
- /** Every `uses:` across every workflow file in the repo. */
97
- export function collectActionUses(repoRoot = findRepoRoot()) {
98
- const uses = [];
99
- for (const file of listWorkflowFiles(repoRoot)) {
100
- const contents = readFileSync(file, "utf8");
101
- uses.push(...parseUsesLines(file, contents));
102
- }
103
- return uses;
104
- }
105
- /** `owner/repo@ref` uses only — local paths carry no ref to check or allow-list. */
106
- export function externalActionUses(uses) {
107
- return uses.filter((u) => u.ref !== null);
108
- }
109
- export function isShaPinned(use) {
110
- return use.ref === null || FULL_SHA.test(use.ref.version);
111
- }
112
- /** Every external `uses:` NOT pinned to a 40-hex SHA — the refusal list for `sha_pinning_required`. */
113
- export function unpinnedActionUses(uses) {
114
- return externalActionUses(uses).filter((u) => !isShaPinned(u));
115
- }
116
- /**
117
- * `owner/repo@*` for every distinct external action the workflows use,
118
- * sorted and deduplicated — the `patterns_allowed` list for `allowed_actions:
119
- * selected`, computed from what the workflows actually reference rather than
120
- * hand-maintained.
121
- */
122
- export function computeActionPatterns(uses) {
123
- const patterns = new Set();
124
- for (const use of externalActionUses(uses)) {
125
- patterns.add(`${use.ref.owner}/${use.ref.repo}@*`);
126
- }
127
- return [...patterns].sort();
128
- }
@@ -1,42 +0,0 @@
1
- /**
2
- * Granting yourself Root on your own instance.
3
- *
4
- * Every console page resolves through `getCallerContext()`, which reads
5
- * `resolvedUserPermissions`. A user with no roles resolves to
6
- * all-permissions-false, so on a freshly reset database the console is simply
7
- * invisible: no error, nothing to click. Somebody has to hold the first role,
8
- * and on a throwaway instance that somebody is you.
9
- *
10
- * THIS REPLACES `platform.claim_root()`, an RPC any authenticated caller could
11
- * invoke as long as nobody already held Root, gated on the instance not
12
- * reporting itself as production. Both halves were wrong. The gate was a column
13
- * in a table: data that had to be set correctly on every instance, could not be
14
- * checked by CI, and was writable by anything holding the service key. And the
15
- * capability behind it is a privilege escalation by construction. On a freshly
16
- * reset production database with sign-up open to the university, Root would
17
- * have gone to whoever authenticated first.
18
- *
19
- * What makes this safe is a credential, not a check. It writes the row with the
20
- * service key, read from `supabase status`, so the authorization is "you
21
- * already control this database". That is the only true statement available.
22
- * By hand the equivalent is one INSERT in the Supabase dashboard.
23
- */
24
- import { type Instance } from "./instance.js";
25
- export interface RootHolder {
26
- userId: string;
27
- email: string;
28
- }
29
- /** Who holds Root right now, if anyone. */
30
- export declare function currentRootHolder(instance: Instance): Promise<RootHolder | null>;
31
- /** Everyone who could be granted it, sorted by email. */
32
- export declare function listCandidates(instance: Instance): Promise<RootHolder[]>;
33
- /**
34
- * Writes the Root grant.
35
- *
36
- * `userRoles_root_singleton` is a unique index, so a second grant fails at the
37
- * database rather than here. This check exists to fail with a sentence a
38
- * contributor can act on, the same reasoning the RPC's version had.
39
- */
40
- export declare function grantRoot(instance: Instance, userId: string): Promise<void>;
41
- /** Hands Root to somebody else, which takes removing it first. */
42
- export declare function transferRoot(instance: Instance, fromUserId: string, toUserId: string): Promise<void>;
package/dist/grantRoot.js DELETED
@@ -1,89 +0,0 @@
1
- /**
2
- * Granting yourself Root on your own instance.
3
- *
4
- * Every console page resolves through `getCallerContext()`, which reads
5
- * `resolvedUserPermissions`. A user with no roles resolves to
6
- * all-permissions-false, so on a freshly reset database the console is simply
7
- * invisible: no error, nothing to click. Somebody has to hold the first role,
8
- * and on a throwaway instance that somebody is you.
9
- *
10
- * THIS REPLACES `platform.claim_root()`, an RPC any authenticated caller could
11
- * invoke as long as nobody already held Root, gated on the instance not
12
- * reporting itself as production. Both halves were wrong. The gate was a column
13
- * in a table: data that had to be set correctly on every instance, could not be
14
- * checked by CI, and was writable by anything holding the service key. And the
15
- * capability behind it is a privilege escalation by construction. On a freshly
16
- * reset production database with sign-up open to the university, Root would
17
- * have gone to whoever authenticated first.
18
- *
19
- * What makes this safe is a credential, not a check. It writes the row with the
20
- * service key, read from `supabase status`, so the authorization is "you
21
- * already control this database". That is the only true statement available.
22
- * By hand the equivalent is one INSERT in the Supabase dashboard.
23
- */
24
- import { adminClient, ROOT_ROLE_ID } from "./instance.js";
25
- /** Who holds Root right now, if anyone. */
26
- export async function currentRootHolder(instance) {
27
- const admin = adminClient(instance);
28
- const { data, error } = await admin
29
- .from("userRoles")
30
- .select("userId")
31
- .eq("roleId", ROOT_ROLE_ID)
32
- .limit(1);
33
- if (error)
34
- throw new Error(`Could not read platform."userRoles": ${error.message}`);
35
- const userId = data?.[0]?.userId;
36
- if (!userId)
37
- return null;
38
- const { data: user } = await admin.auth.admin.getUserById(userId);
39
- return { userId, email: user.user?.email ?? "(no email)" };
40
- }
41
- /** Everyone who could be granted it, sorted by email. */
42
- export async function listCandidates(instance) {
43
- const admin = adminClient(instance);
44
- const { data, error } = await admin.auth.admin.listUsers();
45
- if (error)
46
- throw new Error(`Could not list accounts: ${error.message}`);
47
- return data.users
48
- .map((u) => ({ userId: u.id, email: u.email ?? "(no email)" }))
49
- .sort((a, b) => a.email.localeCompare(b.email));
50
- }
51
- /**
52
- * Writes the Root grant.
53
- *
54
- * `userRoles_root_singleton` is a unique index, so a second grant fails at the
55
- * database rather than here. This check exists to fail with a sentence a
56
- * contributor can act on, the same reasoning the RPC's version had.
57
- */
58
- export async function grantRoot(instance, userId) {
59
- const admin = adminClient(instance);
60
- const { data: role, error: roleErr } = await admin
61
- .from("roles")
62
- .select("id")
63
- .eq("id", ROOT_ROLE_ID)
64
- .maybeSingle();
65
- if (roleErr)
66
- throw new Error(`Could not read platform."roles": ${roleErr.message}`);
67
- if (!role) {
68
- throw new Error("The Root role definition is missing. It comes from supabase/seed/production/01_roles.sql, " +
69
- "which runs on `pnpm devtools db reset` — seeds do not run on `db migrate`.");
70
- }
71
- const { error } = await admin
72
- .from("userRoles")
73
- .insert({ userId, roleId: ROOT_ROLE_ID });
74
- if (error)
75
- throw new Error(`Could not grant Root: ${error.message}`);
76
- }
77
- /** Hands Root to somebody else, which takes removing it first. */
78
- export async function transferRoot(instance, fromUserId, toUserId) {
79
- const admin = adminClient(instance);
80
- const { error: removeErr } = await admin
81
- .from("userRoles")
82
- .delete()
83
- .eq("roleId", ROOT_ROLE_ID)
84
- .eq("userId", fromUserId);
85
- if (removeErr) {
86
- throw new Error(`Could not release Root: ${removeErr.message}`);
87
- }
88
- await grantRoot(instance, toUserId);
89
- }
package/dist/help.d.ts DELETED
@@ -1,18 +0,0 @@
1
- /**
2
- * Renders help for a path, falling back to the root.
3
- *
4
- * An unknown path renders the root rather than an error: this is only reached
5
- * when `--help` was asked for, and answering a mistyped command with the list
6
- * it was mistyped from is more use than a refusal.
7
- */
8
- export declare function renderHelp(path?: readonly string[]): string;
9
- /**
10
- * The command names in `argv` that precede any flag.
11
- *
12
- * `env pull --help` asks about `["env", "pull"]`; `--help env` asks about
13
- * nothing, because a flag's value is not a command path. Kept separate from
14
- * `positionals()`: that one strips a known set of value-taking flags to find a
15
- * subcommand, and here anything after the first flag is not part of the path at
16
- * all.
17
- */
18
- export declare function helpPath(argv: readonly string[]): string[];