@devdogsuga/devtools 0.1.25 → 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,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;
|
package/dist/gh/settings/diff.js
DELETED
|
@@ -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
|
-
}
|
package/dist/grantRoot.d.ts
DELETED
|
@@ -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[];
|