@devdogsuga/devtools 0.1.0
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 +44 -0
- package/bin/devtools-ci-bare.mjs +48 -0
- package/bin/devtools-ci.mjs +17 -0
- package/bin/devtools.mjs +27 -0
- package/dist/args.d.ts +26 -0
- package/dist/args.js +78 -0
- package/dist/bws/bw.d.ts +9 -0
- package/dist/bws/bw.js +49 -0
- package/dist/bws/client.d.ts +43 -0
- package/dist/bws/client.js +363 -0
- package/dist/bws/environments.d.ts +96 -0
- package/dist/bws/environments.js +111 -0
- package/dist/bws/pace.d.ts +27 -0
- package/dist/bws/pace.js +43 -0
- package/dist/bws/retry.d.ts +12 -0
- package/dist/bws/retry.js +39 -0
- package/dist/bws/store.d.ts +1 -0
- package/dist/bws/store.js +32 -0
- package/dist/bws/token.d.ts +31 -0
- package/dist/bws/token.js +88 -0
- package/dist/bws/vault.d.ts +29 -0
- package/dist/bws/vault.js +200 -0
- package/dist/catalog.d.ts +55 -0
- package/dist/catalog.js +81 -0
- package/dist/cf/commands.d.ts +1 -0
- package/dist/cf/commands.js +182 -0
- package/dist/cf/local-env.d.ts +44 -0
- package/dist/cf/local-env.js +148 -0
- package/dist/ci.d.ts +1 -0
- package/dist/ci.js +319 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +1077 -0
- package/dist/commands.d.ts +255 -0
- package/dist/commands.js +1279 -0
- package/dist/completions.d.ts +4 -0
- package/dist/completions.js +110 -0
- package/dist/cron/commands.d.ts +47 -0
- package/dist/cron/commands.js +372 -0
- package/dist/cron/discovery.d.ts +55 -0
- package/dist/cron/discovery.js +85 -0
- package/dist/cron/schema.d.ts +45 -0
- package/dist/cron/schema.js +35 -0
- package/dist/db/config-push.d.ts +8 -0
- package/dist/db/config-push.js +11 -0
- package/dist/db/connection.d.ts +79 -0
- package/dist/db/connection.js +114 -0
- package/dist/db/exec.d.ts +1 -0
- package/dist/db/exec.js +11 -0
- package/dist/db/generate-migration.d.ts +1 -0
- package/dist/db/generate-migration.js +67 -0
- package/dist/db/generate-types.d.ts +1 -0
- package/dist/db/generate-types.js +4 -0
- package/dist/db/introspect.d.ts +1 -0
- package/dist/db/introspect.js +113 -0
- package/dist/db/local-env.d.ts +65 -0
- package/dist/db/local-env.js +23 -0
- package/dist/db/new-migration.d.ts +1 -0
- package/dist/db/new-migration.js +8 -0
- package/dist/db/run.d.ts +70 -0
- package/dist/db/run.js +158 -0
- package/dist/db/seed-buckets.d.ts +2 -0
- package/dist/db/seed-buckets.js +4 -0
- package/dist/db/seed-production.d.ts +8 -0
- package/dist/db/seed-production.js +73 -0
- package/dist/db/seed-roles.d.ts +7 -0
- package/dist/db/seed-roles.js +31 -0
- package/dist/db/session-refresh.d.ts +43 -0
- package/dist/db/session-refresh.js +40 -0
- package/dist/deploy/migrations.d.ts +20 -0
- package/dist/deploy/migrations.js +68 -0
- package/dist/deploy/orphans.d.ts +20 -0
- package/dist/deploy/orphans.js +177 -0
- package/dist/deploy/preflight.d.ts +53 -0
- package/dist/deploy/preflight.js +189 -0
- package/dist/deploy/report.d.ts +26 -0
- package/dist/deploy/report.js +70 -0
- package/dist/deploy/require-planner.d.ts +2 -0
- package/dist/deploy/require-planner.js +60 -0
- package/dist/deploy/require-token.d.ts +15 -0
- package/dist/deploy/require-token.js +78 -0
- package/dist/deploy/secrets-file.d.ts +17 -0
- package/dist/deploy/secrets-file.js +167 -0
- package/dist/deploy/write-env.d.ts +40 -0
- package/dist/deploy/write-env.js +393 -0
- package/dist/docs/index-pages.d.ts +36 -0
- package/dist/docs/index-pages.js +184 -0
- package/dist/doctor.d.ts +62 -0
- package/dist/doctor.js +225 -0
- package/dist/emails/commands.d.ts +15 -0
- package/dist/emails/commands.js +130 -0
- package/dist/emails/email-types.d.ts +13 -0
- package/dist/emails/email-types.js +1 -0
- package/dist/emails/fixtures.d.ts +20 -0
- package/dist/emails/fixtures.js +20 -0
- package/dist/env/audit.d.ts +206 -0
- package/dist/env/audit.js +436 -0
- package/dist/env/cloudflare.d.ts +21 -0
- package/dist/env/cloudflare.js +61 -0
- package/dist/env/commands.d.ts +99 -0
- package/dist/env/commands.js +612 -0
- package/dist/env/discovery.d.ts +15 -0
- package/dist/env/discovery.js +226 -0
- package/dist/env/document.d.ts +119 -0
- package/dist/env/document.js +323 -0
- package/dist/env/example.d.ts +68 -0
- package/dist/env/example.js +603 -0
- package/dist/env/selection.d.ts +105 -0
- package/dist/env/selection.js +226 -0
- package/dist/environment.d.ts +92 -0
- package/dist/environment.js +206 -0
- package/dist/fingerprint.d.ts +10 -0
- package/dist/fingerprint.js +18 -0
- package/dist/gen/campus-map.d.ts +1 -0
- package/dist/gen/campus-map.js +473 -0
- package/dist/gen/commands.d.ts +1 -0
- package/dist/gen/commands.js +25 -0
- package/dist/gen/hypno.d.ts +1 -0
- package/dist/gen/hypno.js +134 -0
- package/dist/gh/client.d.ts +80 -0
- package/dist/gh/client.js +258 -0
- package/dist/gh/environments.d.ts +83 -0
- package/dist/gh/environments.js +178 -0
- package/dist/gh/rulesets/actors.d.ts +14 -0
- package/dist/gh/rulesets/actors.js +68 -0
- package/dist/gh/rulesets/api.d.ts +15 -0
- package/dist/gh/rulesets/api.js +99 -0
- package/dist/gh/rulesets/commands.d.ts +3 -0
- package/dist/gh/rulesets/commands.js +203 -0
- package/dist/gh/rulesets/desired.d.ts +112 -0
- package/dist/gh/rulesets/desired.js +247 -0
- package/dist/gh/rulesets/diff.d.ts +65 -0
- package/dist/gh/rulesets/diff.js +134 -0
- package/dist/gh/rulesets/types.d.ts +76 -0
- package/dist/gh/rulesets/types.js +13 -0
- package/dist/gh/settings/api.d.ts +40 -0
- package/dist/gh/settings/api.js +172 -0
- package/dist/gh/settings/commands.d.ts +1 -0
- package/dist/gh/settings/commands.js +245 -0
- package/dist/gh/settings/desired.d.ts +53 -0
- package/dist/gh/settings/desired.js +39 -0
- package/dist/gh/settings/diff.d.ts +57 -0
- package/dist/gh/settings/diff.js +156 -0
- package/dist/gh/settings/types.d.ts +92 -0
- package/dist/gh/settings/types.js +11 -0
- package/dist/gh/settings/workflows.d.ts +41 -0
- package/dist/gh/settings/workflows.js +124 -0
- package/dist/grantRoot.d.ts +42 -0
- package/dist/grantRoot.js +89 -0
- package/dist/help.d.ts +18 -0
- package/dist/help.js +169 -0
- package/dist/images/commands.d.ts +35 -0
- package/dist/images/commands.js +233 -0
- package/dist/images/events.d.ts +36 -0
- package/dist/images/events.js +152 -0
- package/dist/images/graphics.d.ts +70 -0
- package/dist/images/graphics.js +233 -0
- package/dist/images/open-graph-types.d.ts +75 -0
- package/dist/images/open-graph-types.js +1 -0
- package/dist/images/prompts.d.ts +34 -0
- package/dist/images/prompts.js +100 -0
- package/dist/images/render.d.ts +21 -0
- package/dist/images/render.js +57 -0
- package/dist/images/select.d.ts +64 -0
- package/dist/images/select.js +96 -0
- package/dist/instance.d.ts +67 -0
- package/dist/instance.js +129 -0
- package/dist/invocation.d.ts +49 -0
- package/dist/invocation.js +78 -0
- package/dist/launch-ci.d.ts +1 -0
- package/dist/launch-ci.js +86 -0
- package/dist/launch.d.ts +29 -0
- package/dist/launch.js +371 -0
- package/dist/menu.d.ts +31 -0
- package/dist/menu.js +277 -0
- package/dist/newsletter/commands.d.ts +16 -0
- package/dist/newsletter/commands.js +311 -0
- package/dist/newsletter/imap.d.ts +17 -0
- package/dist/newsletter/imap.js +127 -0
- package/dist/newsletter/loopback.d.ts +24 -0
- package/dist/newsletter/loopback.js +100 -0
- package/dist/newsletter/oauth.d.ts +26 -0
- package/dist/newsletter/oauth.js +130 -0
- package/dist/newsletter/smtp.d.ts +23 -0
- package/dist/newsletter/smtp.js +133 -0
- package/dist/oauth/browser.d.ts +1 -0
- package/dist/oauth/browser.js +13 -0
- package/dist/oauth/config.d.ts +24 -0
- package/dist/oauth/config.js +24 -0
- package/dist/oauth/connect-url.d.ts +17 -0
- package/dist/oauth/connect-url.js +23 -0
- package/dist/oauth/db.d.ts +37 -0
- package/dist/oauth/db.js +108 -0
- package/dist/oauth/device.d.ts +93 -0
- package/dist/oauth/device.js +190 -0
- package/dist/oauth/discovery.d.ts +28 -0
- package/dist/oauth/discovery.js +54 -0
- package/dist/oauth/env-file.d.ts +6 -0
- package/dist/oauth/env-file.js +35 -0
- package/dist/oauth/exchange.d.ts +43 -0
- package/dist/oauth/exchange.js +87 -0
- package/dist/oauth/label.d.ts +1 -0
- package/dist/oauth/label.js +25 -0
- package/dist/oauth/loopback.d.ts +52 -0
- package/dist/oauth/loopback.js +162 -0
- package/dist/oauth/pkce.d.ts +6 -0
- package/dist/oauth/pkce.js +34 -0
- package/dist/oauth/target.d.ts +54 -0
- package/dist/oauth/target.js +102 -0
- package/dist/oauth/transport.d.ts +41 -0
- package/dist/oauth/transport.js +32 -0
- package/dist/oauth/wizard.d.ts +33 -0
- package/dist/oauth/wizard.js +571 -0
- package/dist/pick.d.ts +6 -0
- package/dist/pick.js +64 -0
- package/dist/planner/checks.d.ts +26 -0
- package/dist/planner/checks.js +104 -0
- package/dist/planner/commands.d.ts +10 -0
- package/dist/planner/commands.js +367 -0
- package/dist/planner/db.d.ts +15 -0
- package/dist/planner/db.js +33 -0
- package/dist/planner/role.d.ts +33 -0
- package/dist/planner/role.js +68 -0
- package/dist/repo/peers.d.ts +24 -0
- package/dist/repo/peers.js +114 -0
- package/dist/repo/preflight.d.ts +109 -0
- package/dist/repo/preflight.js +266 -0
- package/dist/repo/resolve.d.ts +41 -0
- package/dist/repo/resolve.js +152 -0
- package/dist/repo/root.d.ts +23 -0
- package/dist/repo/root.js +91 -0
- package/dist/repo/source.d.ts +14 -0
- package/dist/repo/source.js +54 -0
- package/dist/repo/tsx-loader.d.ts +6 -0
- package/dist/repo/tsx-loader.js +41 -0
- package/dist/run/pick.d.ts +60 -0
- package/dist/run/pick.js +559 -0
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +152 -0
- package/dist/stack.d.ts +48 -0
- package/dist/stack.js +290 -0
- package/dist/telemetry.d.ts +34 -0
- package/dist/telemetry.js +93 -0
- package/dist/tier.d.ts +40 -0
- package/dist/tier.js +93 -0
- package/dist/tsconfig.typecheck.tsbuildinfo +1 -0
- package/dist/ui.d.ts +25 -0
- package/dist/ui.js +51 -0
- package/dist/workers.d.ts +5 -0
- package/dist/workers.js +35 -0
- package/dist/workflows/commands.d.ts +44 -0
- package/dist/workflows/commands.js +668 -0
- package/env.ts +158 -0
- package/package.json +80 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type VaultTarget } from "../bws/environments.js";
|
|
2
|
+
export interface PushSelection {
|
|
3
|
+
push: Map<string, string>;
|
|
4
|
+
/**
|
|
5
|
+
* Public per-environment values, bound for Bitwarden and for GitHub
|
|
6
|
+
* *variables*. Plaintext at both ends, with no libsodium sealing, and
|
|
7
|
+
* readable back through the API, which is what lets `audit` compare them by
|
|
8
|
+
* value.
|
|
9
|
+
*/
|
|
10
|
+
variables: Map<string, string>;
|
|
11
|
+
refused: string[];
|
|
12
|
+
/** Present in the file, declared by no manifest. Skipped, warned about. */
|
|
13
|
+
unknown: string[];
|
|
14
|
+
/**
|
|
15
|
+
* Left to the registry: the file still holds the declared derivation itself.
|
|
16
|
+
*
|
|
17
|
+
* Its own outcome rather than part of the skipped-and-unreported majority,
|
|
18
|
+
* because `env init --target` WRITES these lines, eight of them, and a push
|
|
19
|
+
* that said nothing about them would read as "your eight `$VAR` lines were
|
|
20
|
+
* stored". They were not, deliberately. See `selectForPush()`.
|
|
21
|
+
*/
|
|
22
|
+
derived: string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Public per-environment values: Bitwarden, and then GitHub *variables*.
|
|
26
|
+
*
|
|
27
|
+
* Symmetric with `neverStore()` and `minted()`: a set, read at call time,
|
|
28
|
+
* refusing an empty registry. See `getEnvSync().variableKeys()` for why the set is narrower
|
|
29
|
+
* than "every public key" and why the five `localStack` ones are still in it.
|
|
30
|
+
*/
|
|
31
|
+
export declare function pushableVariables(): Set<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Nothing to send: the committed and per-developer non-secrets, the minted
|
|
34
|
+
* credentials, plus the apply-only ones outside production.
|
|
35
|
+
*
|
|
36
|
+
* ⚠️ This is `getEnvSync().neverSecretKeys()` MINUS the pushable variables, and the
|
|
37
|
+
* subtraction is what gave 27 public, environment-scoped values a remote home.
|
|
38
|
+
* They used to land here, as "not a secret, therefore uninteresting", which
|
|
39
|
+
* conflated two different facts: that a value must not be a GitHub *secret*
|
|
40
|
+
* (true of all of them) and that it goes nowhere (true only of the committed
|
|
41
|
+
* and per-developer ones). Every consumer of this set reads it as the second,
|
|
42
|
+
* so the first no longer belongs in it.
|
|
43
|
+
*
|
|
44
|
+
* The apply-only two exist to reshape production, so a copy in staging or
|
|
45
|
+
* preflight is a second write-capable token to rotate for no benefit.
|
|
46
|
+
*
|
|
47
|
+
* Minted credentials are here because there is nothing to send: the value is
|
|
48
|
+
* signed at deploy time and its only copy is on the Worker. Skipped rather
|
|
49
|
+
* than refused, because "not pushed" is the ordinary state of one of these. A
|
|
50
|
+
* refusal is for a credential somebody put in the file expecting it to sync,
|
|
51
|
+
* and nobody has this one to put there.
|
|
52
|
+
*
|
|
53
|
+
* ⚠️ AND, for a target no app boots from, EVERYTHING THAT DID NOT OPT IN. This
|
|
54
|
+
* is the one exclusion that inverts the default: a deployed target skips a
|
|
55
|
+
* named few, `preflight` skips all but a named few. It inverts it because the
|
|
56
|
+
* two questions are different. "Which keys does staging not want?" has a short
|
|
57
|
+
* answer; "which credentials may a CI-only tier hold?" has the answer "the
|
|
58
|
+
* ones deliberately narrowed for it, and nothing by omission". Until this
|
|
59
|
+
* existed, `env push --target preflight` uploaded `SUPABASE_JWT_SIGNING_KEY`,
|
|
60
|
+
* `SECRET_KEY` and `GH_APP_PRIVATE_KEY` into a project whose GitHub
|
|
61
|
+
* environment is reachable from `main`. Both halves are derived, the target
|
|
62
|
+
* from `deployEnv: false` and the keys from `EnvMeta.narrowed`, so neither is
|
|
63
|
+
* a list to keep up to date.
|
|
64
|
+
*/
|
|
65
|
+
export declare function ignoredFor(target: VaultTarget): Set<string>;
|
|
66
|
+
export declare function neverStore(): Set<string>;
|
|
67
|
+
/**
|
|
68
|
+
* The keys a push for this target ROUTES: its two destinations, unioned.
|
|
69
|
+
*
|
|
70
|
+
* The complement of everything above, stated positively, because one consumer
|
|
71
|
+
* needs the question the other way round. `env init --target staging` renders
|
|
72
|
+
* a file whose whole purpose is to be filled in and pushed, so the keys it
|
|
73
|
+
* should contain are exactly the keys a push for that target would carry
|
|
74
|
+
* somewhere. Derived from `ignoredFor()` rather than re-listed, so the file
|
|
75
|
+
* and the upload cannot disagree about which target a key belongs to. The
|
|
76
|
+
* generated file used to be byte-identical for all three targets, which put
|
|
77
|
+
* the apply-tier credentials in `.env.staging` where `ignoredFor()` refuses
|
|
78
|
+
* them. The same derivation shrank `env init --target preflight` from 45 keys
|
|
79
|
+
* to the narrowed ones: the exclusion is stated once, in `ignoredFor()`, and
|
|
80
|
+
* the rendered file follows for free.
|
|
81
|
+
*
|
|
82
|
+
* The `neverStore()` subtraction is unreachable today (a never-store key is
|
|
83
|
+
* neither `secrecy: "secret"` nor `"public"`, so neither selector returns it)
|
|
84
|
+
* and is written down anyway: this set decides what gets an assignable line in
|
|
85
|
+
* a file people fill in, and `BWS_ACCESS_TOKEN=` is the one line that must
|
|
86
|
+
* never appear in one.
|
|
87
|
+
*/
|
|
88
|
+
export declare function keysRoutedTo(target: VaultTarget): Set<string>;
|
|
89
|
+
/**
|
|
90
|
+
* Signed at deploy time; the deploy target holds the only copy.
|
|
91
|
+
*
|
|
92
|
+
* Passed to `audit` separately from `ignoredFor()` even though it is a subset,
|
|
93
|
+
* because the audit needs the distinction: an ignored key is uninteresting
|
|
94
|
+
* everywhere, whereas a minted one is CORRECT on Cloudflare (which is what
|
|
95
|
+
* stops it being reported as an orphan and pruned) and WRONG in Bitwarden or
|
|
96
|
+
* GitHub.
|
|
97
|
+
*/
|
|
98
|
+
export declare function minted(): Set<string>;
|
|
99
|
+
/**
|
|
100
|
+
* `entries` is the ACTIVE assignments in the local `.env`, in file order.
|
|
101
|
+
*
|
|
102
|
+
* A commented-out line is not an assignment and never reaches here, which is
|
|
103
|
+
* what makes commenting-out a safe way to retire a key.
|
|
104
|
+
*/
|
|
105
|
+
export declare function selectForPush(entries: readonly (readonly [string, string])[], target: VaultTarget): PushSelection;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import {} from "../bws/environments.js";
|
|
2
|
+
import { assertRegistryLoaded } from "./discovery.js";
|
|
3
|
+
import { getEnvSync } from "../repo/peers.js";
|
|
4
|
+
// The key sets are DERIVED from the env manifests (see `discovery.ts`), not
|
|
5
|
+
// hand-listed here, which is why every function below insists the registry is
|
|
6
|
+
// loaded. An empty registry makes each selector return `[]`, and `[]` fails
|
|
7
|
+
// open in the one direction that matters: nothing would be refused, so
|
|
8
|
+
// `BWS_ACCESS_TOKEN` would upload.
|
|
9
|
+
/**
|
|
10
|
+
* Public per-environment values: Bitwarden, and then GitHub *variables*.
|
|
11
|
+
*
|
|
12
|
+
* Symmetric with `neverStore()` and `minted()`: a set, read at call time,
|
|
13
|
+
* refusing an empty registry. See `getEnvSync().variableKeys()` for why the set is narrower
|
|
14
|
+
* than "every public key" and why the five `localStack` ones are still in it.
|
|
15
|
+
*/
|
|
16
|
+
export function pushableVariables() {
|
|
17
|
+
assertRegistryLoaded();
|
|
18
|
+
return new Set(getEnvSync().variableKeys());
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Nothing to send: the committed and per-developer non-secrets, the minted
|
|
22
|
+
* credentials, plus the apply-only ones outside production.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ This is `getEnvSync().neverSecretKeys()` MINUS the pushable variables, and the
|
|
25
|
+
* subtraction is what gave 27 public, environment-scoped values a remote home.
|
|
26
|
+
* They used to land here, as "not a secret, therefore uninteresting", which
|
|
27
|
+
* conflated two different facts: that a value must not be a GitHub *secret*
|
|
28
|
+
* (true of all of them) and that it goes nowhere (true only of the committed
|
|
29
|
+
* and per-developer ones). Every consumer of this set reads it as the second,
|
|
30
|
+
* so the first no longer belongs in it.
|
|
31
|
+
*
|
|
32
|
+
* The apply-only two exist to reshape production, so a copy in staging or
|
|
33
|
+
* preflight is a second write-capable token to rotate for no benefit.
|
|
34
|
+
*
|
|
35
|
+
* Minted credentials are here because there is nothing to send: the value is
|
|
36
|
+
* signed at deploy time and its only copy is on the Worker. Skipped rather
|
|
37
|
+
* than refused, because "not pushed" is the ordinary state of one of these. A
|
|
38
|
+
* refusal is for a credential somebody put in the file expecting it to sync,
|
|
39
|
+
* and nobody has this one to put there.
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ AND, for a target no app boots from, EVERYTHING THAT DID NOT OPT IN. This
|
|
42
|
+
* is the one exclusion that inverts the default: a deployed target skips a
|
|
43
|
+
* named few, `preflight` skips all but a named few. It inverts it because the
|
|
44
|
+
* two questions are different. "Which keys does staging not want?" has a short
|
|
45
|
+
* answer; "which credentials may a CI-only tier hold?" has the answer "the
|
|
46
|
+
* ones deliberately narrowed for it, and nothing by omission". Until this
|
|
47
|
+
* existed, `env push --target preflight` uploaded `SUPABASE_JWT_SIGNING_KEY`,
|
|
48
|
+
* `SECRET_KEY` and `GH_APP_PRIVATE_KEY` into a project whose GitHub
|
|
49
|
+
* environment is reachable from `main`. Both halves are derived, the target
|
|
50
|
+
* from `deployEnv: false` and the keys from `EnvMeta.narrowed`, so neither is
|
|
51
|
+
* a list to keep up to date.
|
|
52
|
+
*/
|
|
53
|
+
export function ignoredFor(target) {
|
|
54
|
+
assertRegistryLoaded();
|
|
55
|
+
const pushable = pushableVariables();
|
|
56
|
+
const skip = new Set(getEnvSync().neverSecretKeys().filter((key) => !pushable.has(key)));
|
|
57
|
+
for (const key of getEnvSync().mintedKeys())
|
|
58
|
+
skip.add(key);
|
|
59
|
+
if (target !== "production") {
|
|
60
|
+
for (const key of getEnvSync().applyOnlyKeys())
|
|
61
|
+
skip.add(key);
|
|
62
|
+
}
|
|
63
|
+
// Plan-tier keys are read by exactly two jobs: `main-plan` (preflight) and
|
|
64
|
+
// `production-plan` (production). Everywhere else, staging today, a copy is
|
|
65
|
+
// a credential nothing reads, so it is skipped the same way the apply-tier
|
|
66
|
+
// pair is. Preflight is NOT excluded here: the narrowed-only branch below
|
|
67
|
+
// already decides that target key by key, and a plan-tier key reaches it
|
|
68
|
+
// through its `narrowed` opt-in.
|
|
69
|
+
if (target !== "production" && !getEnvSync().holdsOnlyNarrowedKeys(target)) {
|
|
70
|
+
for (const key of getEnvSync().planOnlyKeys())
|
|
71
|
+
skip.add(key);
|
|
72
|
+
}
|
|
73
|
+
if (getEnvSync().holdsOnlyNarrowedKeys(target)) {
|
|
74
|
+
const narrow = new Set(getEnvSync().narrowedKeys());
|
|
75
|
+
// Over every DECLARED key, not over the routable ones: this set is also
|
|
76
|
+
// what `audit` treats as uninteresting and what `push` reports orphans
|
|
77
|
+
// against, and both want the full answer.
|
|
78
|
+
for (const key of getEnvSync().variables().keys()) {
|
|
79
|
+
if (!narrow.has(key))
|
|
80
|
+
skip.add(key);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return skip;
|
|
84
|
+
}
|
|
85
|
+
export function neverStore() {
|
|
86
|
+
assertRegistryLoaded();
|
|
87
|
+
return new Set(getEnvSync().neverStoreKeys());
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The keys a push for this target ROUTES: its two destinations, unioned.
|
|
91
|
+
*
|
|
92
|
+
* The complement of everything above, stated positively, because one consumer
|
|
93
|
+
* needs the question the other way round. `env init --target staging` renders
|
|
94
|
+
* a file whose whole purpose is to be filled in and pushed, so the keys it
|
|
95
|
+
* should contain are exactly the keys a push for that target would carry
|
|
96
|
+
* somewhere. Derived from `ignoredFor()` rather than re-listed, so the file
|
|
97
|
+
* and the upload cannot disagree about which target a key belongs to. The
|
|
98
|
+
* generated file used to be byte-identical for all three targets, which put
|
|
99
|
+
* the apply-tier credentials in `.env.staging` where `ignoredFor()` refuses
|
|
100
|
+
* them. The same derivation shrank `env init --target preflight` from 45 keys
|
|
101
|
+
* to the narrowed ones: the exclusion is stated once, in `ignoredFor()`, and
|
|
102
|
+
* the rendered file follows for free.
|
|
103
|
+
*
|
|
104
|
+
* The `neverStore()` subtraction is unreachable today (a never-store key is
|
|
105
|
+
* neither `secrecy: "secret"` nor `"public"`, so neither selector returns it)
|
|
106
|
+
* and is written down anyway: this set decides what gets an assignable line in
|
|
107
|
+
* a file people fill in, and `BWS_ACCESS_TOKEN=` is the one line that must
|
|
108
|
+
* never appear in one.
|
|
109
|
+
*/
|
|
110
|
+
export function keysRoutedTo(target) {
|
|
111
|
+
assertRegistryLoaded();
|
|
112
|
+
const skip = ignoredFor(target);
|
|
113
|
+
const refuse = neverStore();
|
|
114
|
+
return new Set([...getEnvSync().storableKeys(), ...getEnvSync().variableKeys()].filter((key) => !skip.has(key) && !refuse.has(key)));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Signed at deploy time; the deploy target holds the only copy.
|
|
118
|
+
*
|
|
119
|
+
* Passed to `audit` separately from `ignoredFor()` even though it is a subset,
|
|
120
|
+
* because the audit needs the distinction: an ignored key is uninteresting
|
|
121
|
+
* everywhere, whereas a minted one is CORRECT on Cloudflare (which is what
|
|
122
|
+
* stops it being reported as an orphan and pruned) and WRONG in Bitwarden or
|
|
123
|
+
* GitHub.
|
|
124
|
+
*/
|
|
125
|
+
export function minted() {
|
|
126
|
+
assertRegistryLoaded();
|
|
127
|
+
return new Set(getEnvSync().mintedKeys());
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Whether the file's value is still, exactly, the derivation the registry
|
|
131
|
+
* declares for this key.
|
|
132
|
+
*
|
|
133
|
+
* ⚠️ EXACT IDENTITY, and nothing looser. Not "looks like a formula", not
|
|
134
|
+
* "contains a `$`", not "starts with the derivation". String equality with
|
|
135
|
+
* what `getEnvSync().derivationOf()` returns. Everything else somebody could type is
|
|
136
|
+
* deliberate input: a real value, obviously, but also a DIFFERENT derivation,
|
|
137
|
+
* which is how a target says its value is built from something else. Widening
|
|
138
|
+
* this predicate by one inch silently drops a credential on the floor: the
|
|
139
|
+
* push reports success, the environment is missing a secret, and `env audit`
|
|
140
|
+
* agrees with the file it came from.
|
|
141
|
+
*
|
|
142
|
+
* `some` rather than `every` across duplicate declarations, matching
|
|
143
|
+
* `keysWhere()`: the safe reading is that a value matching ANY declared
|
|
144
|
+
* derivation is that derivation. It cannot discard a credential either way,
|
|
145
|
+
* since no usable credential is byte-identical to `$BASE_URL/auth/callback`.
|
|
146
|
+
*/
|
|
147
|
+
function isDeclaredDerivation(entries, value) {
|
|
148
|
+
// `getEnvSync().derivationOf()` returns null for everything that is not a derivation, and
|
|
149
|
+
// `value` is non-empty by the time this is called, so null never matches.
|
|
150
|
+
return entries.some((entry) => getEnvSync().derivationOf(entry.meta) === value);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* `entries` is the ACTIVE assignments in the local `.env`, in file order.
|
|
154
|
+
*
|
|
155
|
+
* A commented-out line is not an assignment and never reaches here, which is
|
|
156
|
+
* what makes commenting-out a safe way to retire a key.
|
|
157
|
+
*/
|
|
158
|
+
export function selectForPush(entries, target) {
|
|
159
|
+
const skip = ignoredFor(target);
|
|
160
|
+
const refuse = neverStore();
|
|
161
|
+
const pushable = pushableVariables();
|
|
162
|
+
const declared = getEnvSync().variables();
|
|
163
|
+
const push = new Map();
|
|
164
|
+
const publicValues = new Map();
|
|
165
|
+
const refused = [];
|
|
166
|
+
const unknown = [];
|
|
167
|
+
const derived = [];
|
|
168
|
+
for (const [key, value] of entries) {
|
|
169
|
+
// Refusal is checked FIRST and independently of the value. A blank
|
|
170
|
+
// BWS_ACCESS_TOKEN is still a line somebody is about to fill in.
|
|
171
|
+
if (refuse.has(key)) {
|
|
172
|
+
if (value !== "")
|
|
173
|
+
refused.push(key);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
// Undeclared means unclassified. Nothing says whether this is a secret,
|
|
177
|
+
// whose it is, or where it routes, so it does not leave the machine.
|
|
178
|
+
// Reported even when empty, because the problem is the missing
|
|
179
|
+
// declaration, not the value.
|
|
180
|
+
if (!declared.has(key)) {
|
|
181
|
+
unknown.push(key);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
// Checked BEFORE the variable branch, so the exclusions about *this
|
|
185
|
+
// target* keep winning: an apply-only key outside production, or a minted
|
|
186
|
+
// one anywhere, is skipped whatever its secrecy. The two sets are disjoint
|
|
187
|
+
// today, and this ordering is what keeps a future overlap failing closed
|
|
188
|
+
// rather than open.
|
|
189
|
+
if (skip.has(key))
|
|
190
|
+
continue;
|
|
191
|
+
// An empty secret reads as "configured" to every consumer that checks for
|
|
192
|
+
// presence, which is worse than an absent one. Equally true of a variable:
|
|
193
|
+
// an empty `PROJECT_REF` builds a URL pointing at nothing.
|
|
194
|
+
if (value === "")
|
|
195
|
+
continue;
|
|
196
|
+
// Still the declared derivation, so there is no VALUE here, only the
|
|
197
|
+
// shape, which the registry already holds. Storing it is harmful rather
|
|
198
|
+
// than merely redundant: a stored value BEATS the registry when the
|
|
199
|
+
// deploy composes an env file, so the literal `https://$PROJECT_REF...`
|
|
200
|
+
// arrives as `from: "variable"`, is never expanded (only `from: "derived"`
|
|
201
|
+
// is), and is written single-quoted, which dotenvx takes as fully literal.
|
|
202
|
+
// The deployed app then resolves a host called `$PROJECT_REF.supabase.co`
|
|
203
|
+
// while `env audit` reports NO DRIFT, because the stored value does match
|
|
204
|
+
// the file.
|
|
205
|
+
//
|
|
206
|
+
// ⚠️ NOT expanded here and sent, which is the tempting alternative. That
|
|
207
|
+
// would make GitHub the source of truth for these eight shapes: changing
|
|
208
|
+
// `REST_URL` from `/rest/v1` to `/rest/v2` in the registry would then never
|
|
209
|
+
// take effect anywhere. Skipping is what lets the deploy's `fromRegistry()`
|
|
210
|
+
// run and expand at compose time.
|
|
211
|
+
if (isDeclaredDerivation(declared.get(key), value)) {
|
|
212
|
+
derived.push(key);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
// Public and per-environment: a GitHub *variable*, not a secret. Routed
|
|
216
|
+
// here rather than dropped, because the value still has to reach CI, and
|
|
217
|
+
// still belongs in Bitwarden so that `pull` rebuilds a COMPLETE env file
|
|
218
|
+
// rather than the secret half of one.
|
|
219
|
+
if (pushable.has(key)) {
|
|
220
|
+
publicValues.set(key, value);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
push.set(key, value);
|
|
224
|
+
}
|
|
225
|
+
return { push, variables: publicValues, refused, unknown, derived };
|
|
226
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Condition } from "./commands.js";
|
|
2
|
+
import { findRepoRoot } from "./repo/root.js";
|
|
3
|
+
/**
|
|
4
|
+
* The repo root, which is where `supabase/config.toml` and `.env` live.
|
|
5
|
+
*
|
|
6
|
+
* Used to be a fixed `join(import.meta.dirname, "..", "..", "..")` — that
|
|
7
|
+
* only worked because devtools lived inside the repo it operated on. Now
|
|
8
|
+
* published as its own package, every former reader of that constant calls
|
|
9
|
+
* `findRepoRoot()` (re-exported here for callers that already import from
|
|
10
|
+
* this module) — see `repo/root.ts` for the marker and why it is a lazy,
|
|
11
|
+
* throwing function rather than a constant.
|
|
12
|
+
*/
|
|
13
|
+
export { findRepoRoot };
|
|
14
|
+
/** A fact that may be unreadable. See the header: unknown is not "no". */
|
|
15
|
+
export type Known = "yes" | "no" | "unknown";
|
|
16
|
+
export interface Environment {
|
|
17
|
+
/** The Docker daemon answers. Without it there is no Supabase on this machine. */
|
|
18
|
+
docker: Known;
|
|
19
|
+
/** The local Supabase stack's containers are up. */
|
|
20
|
+
stack: Known;
|
|
21
|
+
/** The root `.env` exists, so `setup` has been run at least once. */
|
|
22
|
+
envFile: Known;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The environment that adapts nothing.
|
|
26
|
+
*
|
|
27
|
+
* What the tests use, and what any caller that would rather not probe can
|
|
28
|
+
* pass. Because nothing is hidden or flagged on `"unknown"`, this is exactly
|
|
29
|
+
* the pre-adaptation menu, which makes it a safe fallback rather than a
|
|
30
|
+
* special case to remember.
|
|
31
|
+
*/
|
|
32
|
+
export declare const UNKNOWN_ENVIRONMENT: Environment;
|
|
33
|
+
/**
|
|
34
|
+
* Reads the machine.
|
|
35
|
+
*
|
|
36
|
+
* Two subprocesses at worst, one when Docker is down, plus an `existsSync`.
|
|
37
|
+
* The budget is "cheaper than the first keystroke", because this runs before
|
|
38
|
+
* the wizard's first screen and anything slower would be paid on every
|
|
39
|
+
* invocation to save a few of them later.
|
|
40
|
+
*
|
|
41
|
+
* It does NOT call `supabase status`, which is the authoritative answer but
|
|
42
|
+
* takes the better part of a second: it shells out through `pnpm exec` and
|
|
43
|
+
* then talks to every service in the stack. `docker ps` answers the only
|
|
44
|
+
* question the menu has (is it up?) in a fraction of that, and the commands
|
|
45
|
+
* that need real credentials still go through `detectLocalInstance`, which
|
|
46
|
+
* asks the authority at the moment it matters.
|
|
47
|
+
*/
|
|
48
|
+
export declare function probeEnvironment(execute?: (file: string, args: string[]) => string | null): Environment;
|
|
49
|
+
/**
|
|
50
|
+
* Whether a `Condition` from the command tree currently holds.
|
|
51
|
+
*
|
|
52
|
+
* The tree declares conditions as strings so that it stays inert data the docs
|
|
53
|
+
* build can render (see `commands.ts`); this is the one place that knows what
|
|
54
|
+
* they mean. `"unknown"` propagates rather than collapsing to false, and both
|
|
55
|
+
* callers below read it as "do nothing".
|
|
56
|
+
*/
|
|
57
|
+
export declare function holds(condition: Condition, env: Environment): Known;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the wizard should offer this command at all.
|
|
60
|
+
*
|
|
61
|
+
* False only for a command whose `when` is definitively unmet: `stop`
|
|
62
|
+
* against a stack that is already stopped. This is reserved for commands that
|
|
63
|
+
* would be *meaningless*, never merely inconvenient: one that would fail with
|
|
64
|
+
* a good error message stays in the menu and carries `needs` instead, because
|
|
65
|
+
* a contributor who cannot find a command they know exists is worse off than
|
|
66
|
+
* one who runs it and is told why it did not work.
|
|
67
|
+
*
|
|
68
|
+
* Hiding is a wizard-only decision. `--help` still lists these, the dispatcher
|
|
69
|
+
* still accepts them, and the generated reference still documents them. The
|
|
70
|
+
* menu is a guide to right now, and those three are the reference.
|
|
71
|
+
*/
|
|
72
|
+
export declare function isOffered(node: {
|
|
73
|
+
when?: Condition;
|
|
74
|
+
}, env: Environment): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Why this command will not work right now, or `null` if nothing is in the way.
|
|
77
|
+
*
|
|
78
|
+
* Appended to the entry's hint, so the menu says "needs Docker, which is not
|
|
79
|
+
* running" on the line itself, rather than after the reader has chosen it and
|
|
80
|
+
* waited for a connection attempt to fail.
|
|
81
|
+
*/
|
|
82
|
+
export declare function blockedBecause(node: {
|
|
83
|
+
needs?: Condition;
|
|
84
|
+
}, env: Environment): string | null;
|
|
85
|
+
/**
|
|
86
|
+
* The three facts, as the wizard's opening note and as `status --local`.
|
|
87
|
+
*
|
|
88
|
+
* Printed before the first question rather than discovered through failures:
|
|
89
|
+
* "Docker running no" at the top of the screen is the whole explanation for
|
|
90
|
+
* why the database commands below it are flagged, and it costs one glance.
|
|
91
|
+
*/
|
|
92
|
+
export declare function describeEnvironment(env: Environment): string;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What this machine currently is, read once before the menu draws itself.
|
|
3
|
+
*
|
|
4
|
+
* The wizard used to offer the same commands to everyone: someone whose Docker
|
|
5
|
+
* daemon was not running was invited to reset a database that could not
|
|
6
|
+
* answer, and someone whose stack was already up had no way to stop it at all,
|
|
7
|
+
* because "stop" was not in the tree. Both are the same bug. The menu
|
|
8
|
+
* described the CLI rather than the situation.
|
|
9
|
+
*
|
|
10
|
+
* So this reads three facts and `menu.ts` adapts to them. The facts are few
|
|
11
|
+
* and cheap on purpose; see `probeEnvironment` for the budget and why.
|
|
12
|
+
*
|
|
13
|
+
* ## Unknown is not "no"
|
|
14
|
+
*
|
|
15
|
+
* Every probe can come back `"unknown"`, and that is what a failed probe
|
|
16
|
+
* reports rather than guessing. Nothing is hidden or flagged on `"unknown"`: a
|
|
17
|
+
* machine this cannot read gets the full menu, exactly as before. A tool that
|
|
18
|
+
* hides a command because a `docker ps` timed out is worse than one that never
|
|
19
|
+
* adapted at all, because the command it hides is the one you were looking
|
|
20
|
+
* for.
|
|
21
|
+
*/
|
|
22
|
+
import { execFileSync } from "node:child_process";
|
|
23
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
import { findRepoRoot } from "./repo/root.js";
|
|
26
|
+
/**
|
|
27
|
+
* The repo root, which is where `supabase/config.toml` and `.env` live.
|
|
28
|
+
*
|
|
29
|
+
* Used to be a fixed `join(import.meta.dirname, "..", "..", "..")` — that
|
|
30
|
+
* only worked because devtools lived inside the repo it operated on. Now
|
|
31
|
+
* published as its own package, every former reader of that constant calls
|
|
32
|
+
* `findRepoRoot()` (re-exported here for callers that already import from
|
|
33
|
+
* this module) — see `repo/root.ts` for the marker and why it is a lazy,
|
|
34
|
+
* throwing function rather than a constant.
|
|
35
|
+
*/
|
|
36
|
+
export { findRepoRoot };
|
|
37
|
+
/**
|
|
38
|
+
* The environment that adapts nothing.
|
|
39
|
+
*
|
|
40
|
+
* What the tests use, and what any caller that would rather not probe can
|
|
41
|
+
* pass. Because nothing is hidden or flagged on `"unknown"`, this is exactly
|
|
42
|
+
* the pre-adaptation menu, which makes it a safe fallback rather than a
|
|
43
|
+
* special case to remember.
|
|
44
|
+
*/
|
|
45
|
+
export const UNKNOWN_ENVIRONMENT = {
|
|
46
|
+
docker: "unknown",
|
|
47
|
+
stack: "unknown",
|
|
48
|
+
envFile: "unknown",
|
|
49
|
+
};
|
|
50
|
+
// ── Probes ───────────────────────────────────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Every probe is bounded, silent, and total.
|
|
53
|
+
*
|
|
54
|
+
* `timeout` because a Docker daemon that is starting up (or a VM that has just
|
|
55
|
+
* been resumed) accepts the connection and then never answers, and the failure
|
|
56
|
+
* that produces is a menu that hangs before printing anything, with no output
|
|
57
|
+
* to explain what it is waiting for.
|
|
58
|
+
*/
|
|
59
|
+
function run(file, args) {
|
|
60
|
+
try {
|
|
61
|
+
return execFileSync(file, args, {
|
|
62
|
+
cwd: findRepoRoot(),
|
|
63
|
+
encoding: "utf8",
|
|
64
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
65
|
+
timeout: 3000,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The container name prefix the Supabase CLI gives this project's stack.
|
|
74
|
+
*
|
|
75
|
+
* Read with a regex rather than a TOML parser: it is one line, the shape has
|
|
76
|
+
* been stable across every CLI version this repo has seen, and a parser on a
|
|
77
|
+
* menu's startup path to read one string is a cost that stays invisible until
|
|
78
|
+
* it is why the tool feels slow.
|
|
79
|
+
*
|
|
80
|
+
* Falling back to the bare `supabase_db_` prefix means a machine whose
|
|
81
|
+
* `config.toml` cannot be read still detects *a* stack. That over-match is
|
|
82
|
+
* deliberate: over-matching offers `stop` to someone running a different
|
|
83
|
+
* Supabase project, which they can decline, while under-matching tells someone
|
|
84
|
+
* their running stack is down.
|
|
85
|
+
*/
|
|
86
|
+
function containerPrefix() {
|
|
87
|
+
try {
|
|
88
|
+
const path = join(findRepoRoot(), "supabase", "config.toml");
|
|
89
|
+
const id = /^\s*project_id\s*=\s*"([^"]+)"/m.exec(readFileSync(path, "utf8"));
|
|
90
|
+
return id?.[1] ? `supabase_db_${id[1]}` : "supabase_db_";
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return "supabase_db_";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Reads the machine.
|
|
98
|
+
*
|
|
99
|
+
* Two subprocesses at worst, one when Docker is down, plus an `existsSync`.
|
|
100
|
+
* The budget is "cheaper than the first keystroke", because this runs before
|
|
101
|
+
* the wizard's first screen and anything slower would be paid on every
|
|
102
|
+
* invocation to save a few of them later.
|
|
103
|
+
*
|
|
104
|
+
* It does NOT call `supabase status`, which is the authoritative answer but
|
|
105
|
+
* takes the better part of a second: it shells out through `pnpm exec` and
|
|
106
|
+
* then talks to every service in the stack. `docker ps` answers the only
|
|
107
|
+
* question the menu has (is it up?) in a fraction of that, and the commands
|
|
108
|
+
* that need real credentials still go through `detectLocalInstance`, which
|
|
109
|
+
* asks the authority at the moment it matters.
|
|
110
|
+
*/
|
|
111
|
+
export function probeEnvironment(execute = run) {
|
|
112
|
+
const envFile = existsSync(join(findRepoRoot(), ".env")) ? "yes" : "no";
|
|
113
|
+
// `docker info` rather than `docker version`: version answers from the
|
|
114
|
+
// client alone, so it reports success against a daemon that is not running.
|
|
115
|
+
const docker = execute("docker", ["info"]) === null ? "no" : "yes";
|
|
116
|
+
// A stack cannot be up without a daemon to hold it, so this needs no second
|
|
117
|
+
// subprocess to say "no", and asking anyway on a machine with no Docker
|
|
118
|
+
// installed would pay the whole timeout for an answer already in hand.
|
|
119
|
+
if (docker === "no")
|
|
120
|
+
return { docker, stack: "no", envFile };
|
|
121
|
+
const names = execute("docker", ["ps", "--format", "{{.Names}}"]);
|
|
122
|
+
if (names === null)
|
|
123
|
+
return { docker, stack: "unknown", envFile };
|
|
124
|
+
const prefix = containerPrefix();
|
|
125
|
+
const running = names
|
|
126
|
+
.split("\n")
|
|
127
|
+
.some((name) => name.trim().startsWith(prefix));
|
|
128
|
+
return { docker, stack: running ? "yes" : "no", envFile };
|
|
129
|
+
}
|
|
130
|
+
// ── Conditions ───────────────────────────────────────────────────────────────
|
|
131
|
+
/**
|
|
132
|
+
* Whether a `Condition` from the command tree currently holds.
|
|
133
|
+
*
|
|
134
|
+
* The tree declares conditions as strings so that it stays inert data the docs
|
|
135
|
+
* build can render (see `commands.ts`); this is the one place that knows what
|
|
136
|
+
* they mean. `"unknown"` propagates rather than collapsing to false, and both
|
|
137
|
+
* callers below read it as "do nothing".
|
|
138
|
+
*/
|
|
139
|
+
export function holds(condition, env) {
|
|
140
|
+
switch (condition) {
|
|
141
|
+
case "docker":
|
|
142
|
+
return env.docker;
|
|
143
|
+
case "instance-running":
|
|
144
|
+
return env.stack;
|
|
145
|
+
case "instance-stopped":
|
|
146
|
+
// The negation of an unreadable fact is still unreadable.
|
|
147
|
+
if (env.stack === "unknown")
|
|
148
|
+
return "unknown";
|
|
149
|
+
return env.stack === "no" ? "yes" : "no";
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** What `needs` renders as when it does not hold. Kept short: it is a hint. */
|
|
153
|
+
const UNMET = {
|
|
154
|
+
docker: "Docker is not running",
|
|
155
|
+
"instance-running": "Supabase is not running on this machine",
|
|
156
|
+
"instance-stopped": "Supabase is already running on this machine",
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Whether the wizard should offer this command at all.
|
|
160
|
+
*
|
|
161
|
+
* False only for a command whose `when` is definitively unmet: `stop`
|
|
162
|
+
* against a stack that is already stopped. This is reserved for commands that
|
|
163
|
+
* would be *meaningless*, never merely inconvenient: one that would fail with
|
|
164
|
+
* a good error message stays in the menu and carries `needs` instead, because
|
|
165
|
+
* a contributor who cannot find a command they know exists is worse off than
|
|
166
|
+
* one who runs it and is told why it did not work.
|
|
167
|
+
*
|
|
168
|
+
* Hiding is a wizard-only decision. `--help` still lists these, the dispatcher
|
|
169
|
+
* still accepts them, and the generated reference still documents them. The
|
|
170
|
+
* menu is a guide to right now, and those three are the reference.
|
|
171
|
+
*/
|
|
172
|
+
export function isOffered(node, env) {
|
|
173
|
+
return node.when === undefined || holds(node.when, env) !== "no";
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Why this command will not work right now, or `null` if nothing is in the way.
|
|
177
|
+
*
|
|
178
|
+
* Appended to the entry's hint, so the menu says "needs Docker, which is not
|
|
179
|
+
* running" on the line itself, rather than after the reader has chosen it and
|
|
180
|
+
* waited for a connection attempt to fail.
|
|
181
|
+
*/
|
|
182
|
+
export function blockedBecause(node, env) {
|
|
183
|
+
if (!node.needs)
|
|
184
|
+
return null;
|
|
185
|
+
return holds(node.needs, env) === "no" ? UNMET[node.needs] : null;
|
|
186
|
+
}
|
|
187
|
+
// ── Reporting ────────────────────────────────────────────────────────────────
|
|
188
|
+
const LABELS = {
|
|
189
|
+
yes: "yes",
|
|
190
|
+
no: "no",
|
|
191
|
+
unknown: "could not tell",
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* The three facts, as the wizard's opening note and as `status --local`.
|
|
195
|
+
*
|
|
196
|
+
* Printed before the first question rather than discovered through failures:
|
|
197
|
+
* "Docker running no" at the top of the screen is the whole explanation for
|
|
198
|
+
* why the database commands below it are flagged, and it costs one glance.
|
|
199
|
+
*/
|
|
200
|
+
export function describeEnvironment(env) {
|
|
201
|
+
return [
|
|
202
|
+
`Docker running ${LABELS[env.docker]}`,
|
|
203
|
+
`Local stack up ${LABELS[env.stack]}`,
|
|
204
|
+
`.env present ${LABELS[env.envFile]}`,
|
|
205
|
+
].join("\n");
|
|
206
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fingerprint, for showing that a value changed without showing the value.
|
|
3
|
+
*
|
|
4
|
+
* Length plus first and last character. Enough to tell "I pasted the wrong
|
|
5
|
+
* token" from "the token rotated", and not enough to reconstruct anything. The
|
|
6
|
+
* whole point of these commands is to move secrets without printing them, and a
|
|
7
|
+
* diff that renders values would undo that in the one place people paste output
|
|
8
|
+
* into a chat window.
|
|
9
|
+
*/
|
|
10
|
+
export declare function fingerprint(value: string): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fingerprint, for showing that a value changed without showing the value.
|
|
3
|
+
*
|
|
4
|
+
* Length plus first and last character. Enough to tell "I pasted the wrong
|
|
5
|
+
* token" from "the token rotated", and not enough to reconstruct anything. The
|
|
6
|
+
* whole point of these commands is to move secrets without printing them, and a
|
|
7
|
+
* diff that renders values would undo that in the one place people paste output
|
|
8
|
+
* into a chat window.
|
|
9
|
+
*/
|
|
10
|
+
export function fingerprint(value) {
|
|
11
|
+
if (value === "")
|
|
12
|
+
return "empty";
|
|
13
|
+
// Short values would be largely reconstructable from first/last, so they get
|
|
14
|
+
// no description at all.
|
|
15
|
+
if (value.length <= 4)
|
|
16
|
+
return `${value.length} chars`;
|
|
17
|
+
return `${value.length} chars, ${value[0]}…${value[value.length - 1]}`;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runGenCampusMap(): Promise<number>;
|