@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.
Files changed (271) hide show
  1. package/README.md +72 -11
  2. package/bin/devtools.mjs +4 -4
  3. package/dist/cli-CZKkNHK5.js +6619 -0
  4. package/dist/failure-log-BBPGejQv.js +256 -0
  5. package/dist/launch.js +267 -281
  6. package/dist/local-env-D8HC2JK_.js +351 -0
  7. package/dist/local-env-DrBCXqD8.js +2 -0
  8. package/dist/menu-CQBsQRgH.js +1684 -0
  9. package/dist/peer-redirect-hooks.js +15 -0
  10. package/dist/peers-RBplwovA.js +229 -0
  11. package/dist/root-C2OmZDwD.js +94 -0
  12. package/dist/stack-Bx-55oi_.js +2 -0
  13. package/dist/stack-BynCgRVS.js +181 -0
  14. package/dist/supabase-project-0knM0mqd.js +426 -0
  15. package/dist/telemetry-C4foD8MD.js +249 -0
  16. package/dist/telemetry.js +2 -148
  17. package/package.json +10 -29
  18. package/bin/devtools-ci-bare.mjs +0 -48
  19. package/bin/devtools-ci.mjs +0 -31
  20. package/dist/args.d.ts +0 -26
  21. package/dist/args.js +0 -78
  22. package/dist/bws/bw.d.ts +0 -18
  23. package/dist/bws/bw.js +0 -70
  24. package/dist/bws/client.d.ts +0 -43
  25. package/dist/bws/client.js +0 -363
  26. package/dist/bws/environments.d.ts +0 -96
  27. package/dist/bws/environments.js +0 -111
  28. package/dist/bws/pace.d.ts +0 -25
  29. package/dist/bws/pace.js +0 -43
  30. package/dist/bws/retry.d.ts +0 -12
  31. package/dist/bws/retry.js +0 -39
  32. package/dist/bws/store.d.ts +0 -1
  33. package/dist/bws/store.js +0 -32
  34. package/dist/bws/token.d.ts +0 -31
  35. package/dist/bws/token.js +0 -88
  36. package/dist/bws/vault.d.ts +0 -29
  37. package/dist/bws/vault.js +0 -200
  38. package/dist/catalog.d.ts +0 -57
  39. package/dist/catalog.js +0 -57
  40. package/dist/cf/build.d.ts +0 -13
  41. package/dist/cf/build.js +0 -65
  42. package/dist/cf/commands.d.ts +0 -1
  43. package/dist/cf/commands.js +0 -172
  44. package/dist/cf/local-env.d.ts +0 -58
  45. package/dist/cf/local-env.js +0 -177
  46. package/dist/ci.d.ts +0 -1
  47. package/dist/ci.js +0 -328
  48. package/dist/cli.d.ts +0 -11
  49. package/dist/cli.js +0 -1094
  50. package/dist/commands.d.ts +0 -261
  51. package/dist/commands.js +0 -1318
  52. package/dist/completions.d.ts +0 -4
  53. package/dist/completions.js +0 -110
  54. package/dist/cron/commands.d.ts +0 -47
  55. package/dist/cron/commands.js +0 -372
  56. package/dist/cron/discovery.d.ts +0 -55
  57. package/dist/cron/discovery.js +0 -85
  58. package/dist/cron/schema.d.ts +0 -45
  59. package/dist/cron/schema.js +0 -35
  60. package/dist/db/config-push.d.ts +0 -8
  61. package/dist/db/config-push.js +0 -11
  62. package/dist/db/connection.d.ts +0 -79
  63. package/dist/db/connection.js +0 -114
  64. package/dist/db/exec.d.ts +0 -1
  65. package/dist/db/exec.js +0 -11
  66. package/dist/db/generate-types.d.ts +0 -1
  67. package/dist/db/generate-types.js +0 -4
  68. package/dist/db/generated-env.d.ts +0 -69
  69. package/dist/db/generated-env.js +0 -113
  70. package/dist/db/introspect.d.ts +0 -1
  71. package/dist/db/introspect.js +0 -122
  72. package/dist/db/local-env.d.ts +0 -65
  73. package/dist/db/local-env.js +0 -23
  74. package/dist/db/new-migration.d.ts +0 -8
  75. package/dist/db/new-migration.js +0 -69
  76. package/dist/db/pooler.d.ts +0 -23
  77. package/dist/db/pooler.js +0 -55
  78. package/dist/db/run.d.ts +0 -68
  79. package/dist/db/run.js +0 -156
  80. package/dist/db/seed-buckets.d.ts +0 -2
  81. package/dist/db/seed-buckets.js +0 -4
  82. package/dist/db/seed-production.d.ts +0 -8
  83. package/dist/db/seed-production.js +0 -79
  84. package/dist/db/seed-roles.d.ts +0 -7
  85. package/dist/db/seed-roles.js +0 -33
  86. package/dist/db/session-refresh.d.ts +0 -43
  87. package/dist/db/session-refresh.js +0 -40
  88. package/dist/deploy/migrations.d.ts +0 -20
  89. package/dist/deploy/migrations.js +0 -68
  90. package/dist/deploy/orphans.d.ts +0 -20
  91. package/dist/deploy/orphans.js +0 -177
  92. package/dist/deploy/preflight.d.ts +0 -53
  93. package/dist/deploy/preflight.js +0 -189
  94. package/dist/deploy/report.d.ts +0 -26
  95. package/dist/deploy/report.js +0 -70
  96. package/dist/deploy/require-planner.d.ts +0 -2
  97. package/dist/deploy/require-planner.js +0 -60
  98. package/dist/deploy/require-token.d.ts +0 -15
  99. package/dist/deploy/require-token.js +0 -78
  100. package/dist/deploy/secrets-file.d.ts +0 -17
  101. package/dist/deploy/secrets-file.js +0 -166
  102. package/dist/deploy/write-env.d.ts +0 -40
  103. package/dist/deploy/write-env.js +0 -395
  104. package/dist/docs/index-pages.d.ts +0 -38
  105. package/dist/docs/index-pages.js +0 -186
  106. package/dist/emails/commands.d.ts +0 -15
  107. package/dist/emails/commands.js +0 -130
  108. package/dist/emails/email-types.d.ts +0 -13
  109. package/dist/emails/email-types.js +0 -1
  110. package/dist/emails/fixtures.d.ts +0 -20
  111. package/dist/emails/fixtures.js +0 -20
  112. package/dist/env/audit.d.ts +0 -206
  113. package/dist/env/audit.js +0 -436
  114. package/dist/env/cloudflare.d.ts +0 -21
  115. package/dist/env/cloudflare.js +0 -61
  116. package/dist/env/commands.d.ts +0 -99
  117. package/dist/env/commands.js +0 -613
  118. package/dist/env/discovery.d.ts +0 -15
  119. package/dist/env/discovery.js +0 -239
  120. package/dist/env/document.d.ts +0 -119
  121. package/dist/env/document.js +0 -323
  122. package/dist/env/example.d.ts +0 -68
  123. package/dist/env/example.js +0 -605
  124. package/dist/env/selection.d.ts +0 -105
  125. package/dist/env/selection.js +0 -228
  126. package/dist/env-entry.d.ts +0 -49
  127. package/dist/env-entry.js +0 -204
  128. package/dist/environment-doctor.d.ts +0 -45
  129. package/dist/environment-doctor.js +0 -429
  130. package/dist/environment.d.ts +0 -92
  131. package/dist/environment.js +0 -185
  132. package/dist/fingerprint.d.ts +0 -10
  133. package/dist/fingerprint.js +0 -18
  134. package/dist/gen/campus-map.d.ts +0 -1
  135. package/dist/gen/campus-map.js +0 -473
  136. package/dist/gen/commands.d.ts +0 -1
  137. package/dist/gen/commands.js +0 -25
  138. package/dist/gen/hypno.d.ts +0 -1
  139. package/dist/gen/hypno.js +0 -134
  140. package/dist/gh/client.d.ts +0 -80
  141. package/dist/gh/client.js +0 -259
  142. package/dist/gh/environments.d.ts +0 -83
  143. package/dist/gh/environments.js +0 -178
  144. package/dist/gh/rulesets/actors.d.ts +0 -14
  145. package/dist/gh/rulesets/actors.js +0 -69
  146. package/dist/gh/rulesets/api.d.ts +0 -15
  147. package/dist/gh/rulesets/api.js +0 -104
  148. package/dist/gh/rulesets/commands.d.ts +0 -3
  149. package/dist/gh/rulesets/commands.js +0 -203
  150. package/dist/gh/rulesets/desired.d.ts +0 -112
  151. package/dist/gh/rulesets/desired.js +0 -271
  152. package/dist/gh/rulesets/diff.d.ts +0 -65
  153. package/dist/gh/rulesets/diff.js +0 -170
  154. package/dist/gh/rulesets/types.d.ts +0 -76
  155. package/dist/gh/rulesets/types.js +0 -13
  156. package/dist/gh/settings/api.d.ts +0 -40
  157. package/dist/gh/settings/api.js +0 -180
  158. package/dist/gh/settings/commands.d.ts +0 -1
  159. package/dist/gh/settings/commands.js +0 -254
  160. package/dist/gh/settings/desired.d.ts +0 -53
  161. package/dist/gh/settings/desired.js +0 -43
  162. package/dist/gh/settings/diff.d.ts +0 -57
  163. package/dist/gh/settings/diff.js +0 -161
  164. package/dist/gh/settings/types.d.ts +0 -92
  165. package/dist/gh/settings/types.js +0 -11
  166. package/dist/gh/settings/workflows.d.ts +0 -41
  167. package/dist/gh/settings/workflows.js +0 -128
  168. package/dist/grantRoot.d.ts +0 -42
  169. package/dist/grantRoot.js +0 -89
  170. package/dist/help.d.ts +0 -18
  171. package/dist/help.js +0 -169
  172. package/dist/images/commands.d.ts +0 -32
  173. package/dist/images/commands.js +0 -231
  174. package/dist/images/events.d.ts +0 -47
  175. package/dist/images/events.js +0 -116
  176. package/dist/images/graphics.d.ts +0 -70
  177. package/dist/images/graphics.js +0 -234
  178. package/dist/images/open-graph-types.d.ts +0 -75
  179. package/dist/images/open-graph-types.js +0 -1
  180. package/dist/images/prompts.d.ts +0 -34
  181. package/dist/images/prompts.js +0 -100
  182. package/dist/images/render.d.ts +0 -21
  183. package/dist/images/render.js +0 -61
  184. package/dist/images/select.d.ts +0 -64
  185. package/dist/images/select.js +0 -96
  186. package/dist/instance.d.ts +0 -69
  187. package/dist/instance.js +0 -102
  188. package/dist/invocation.d.ts +0 -49
  189. package/dist/invocation.js +0 -78
  190. package/dist/launch-ci.d.ts +0 -1
  191. package/dist/launch-ci.js +0 -90
  192. package/dist/launch.d.ts +0 -18
  193. package/dist/menu.d.ts +0 -43
  194. package/dist/menu.js +0 -300
  195. package/dist/moderation.d.ts +0 -70
  196. package/dist/moderation.js +0 -128
  197. package/dist/oauth/browser.d.ts +0 -1
  198. package/dist/oauth/browser.js +0 -13
  199. package/dist/oauth/config.d.ts +0 -24
  200. package/dist/oauth/config.js +0 -24
  201. package/dist/oauth/connect-url.d.ts +0 -17
  202. package/dist/oauth/connect-url.js +0 -23
  203. package/dist/oauth/db.d.ts +0 -37
  204. package/dist/oauth/db.js +0 -108
  205. package/dist/oauth/device.d.ts +0 -93
  206. package/dist/oauth/device.js +0 -198
  207. package/dist/oauth/discovery.d.ts +0 -28
  208. package/dist/oauth/discovery.js +0 -54
  209. package/dist/oauth/env-file.d.ts +0 -6
  210. package/dist/oauth/env-file.js +0 -35
  211. package/dist/oauth/exchange.d.ts +0 -43
  212. package/dist/oauth/exchange.js +0 -93
  213. package/dist/oauth/label.d.ts +0 -1
  214. package/dist/oauth/label.js +0 -25
  215. package/dist/oauth/loopback.d.ts +0 -52
  216. package/dist/oauth/loopback.js +0 -168
  217. package/dist/oauth/pkce.d.ts +0 -6
  218. package/dist/oauth/pkce.js +0 -34
  219. package/dist/oauth/target.d.ts +0 -54
  220. package/dist/oauth/target.js +0 -102
  221. package/dist/oauth/transport.d.ts +0 -41
  222. package/dist/oauth/transport.js +0 -32
  223. package/dist/oauth/wizard.d.ts +0 -40
  224. package/dist/oauth/wizard.js +0 -578
  225. package/dist/persona.d.ts +0 -18
  226. package/dist/persona.js +0 -221
  227. package/dist/pick.d.ts +0 -6
  228. package/dist/pick.js +0 -64
  229. package/dist/pipes.d.ts +0 -1
  230. package/dist/pipes.js +0 -25
  231. package/dist/planner/checks.d.ts +0 -26
  232. package/dist/planner/checks.js +0 -104
  233. package/dist/planner/commands.d.ts +0 -10
  234. package/dist/planner/commands.js +0 -367
  235. package/dist/planner/db.d.ts +0 -15
  236. package/dist/planner/db.js +0 -33
  237. package/dist/planner/role.d.ts +0 -33
  238. package/dist/planner/role.js +0 -68
  239. package/dist/repo/peer-redirect-hooks.d.ts +0 -16
  240. package/dist/repo/peer-redirect-hooks.js +0 -10
  241. package/dist/repo/peer-redirect.d.ts +0 -4
  242. package/dist/repo/peer-redirect.js +0 -26
  243. package/dist/repo/peers.d.ts +0 -39
  244. package/dist/repo/peers.js +0 -139
  245. package/dist/repo/resolve.d.ts +0 -41
  246. package/dist/repo/resolve.js +0 -152
  247. package/dist/repo/root.d.ts +0 -23
  248. package/dist/repo/root.js +0 -91
  249. package/dist/repo/source.d.ts +0 -14
  250. package/dist/repo/source.js +0 -54
  251. package/dist/repo/supabase-project.d.ts +0 -60
  252. package/dist/repo/supabase-project.js +0 -124
  253. package/dist/repo/tsx-loader.d.ts +0 -6
  254. package/dist/repo/tsx-loader.js +0 -41
  255. package/dist/run/pick.d.ts +0 -90
  256. package/dist/run/pick.js +0 -664
  257. package/dist/setup.d.ts +0 -1
  258. package/dist/setup.js +0 -280
  259. package/dist/stack.d.ts +0 -48
  260. package/dist/stack.js +0 -403
  261. package/dist/telemetry.d.ts +0 -59
  262. package/dist/tier.d.ts +0 -40
  263. package/dist/tier.js +0 -96
  264. package/dist/ui.d.ts +0 -40
  265. package/dist/ui.js +0 -71
  266. package/dist/version.d.ts +0 -7
  267. package/dist/version.js +0 -17
  268. package/dist/workers.d.ts +0 -5
  269. package/dist/workers.js +0 -33
  270. package/dist/workflows/commands.d.ts +0 -54
  271. package/dist/workflows/commands.js +0 -763
package/dist/launch.js CHANGED
@@ -1,294 +1,280 @@
1
+ import { a as cliName, t as installFailureLog } from "./failure-log-BBPGejQv.js";
2
+ import { C as catalog, S as unwrap, _ as setMenuEnvHook, d as nonEmpty, g as realEnvEntryDeps, h as enterSessionEnvironment, t as bareGroupStartPath, w as helpPath, y as errorMessage } from "./menu-CQBsQRgH.js";
3
+ import { b as setDryRun, m as formatCommand, v as isDryRun, y as resolveDryRun } from "./supabase-project-0knM0mqd.js";
4
+ import { n as discoverRepoRoot, r as findRepoRoot, t as RepoNotFoundError } from "./root-C2OmZDwD.js";
5
+ import { i as loadEnvSession, r as loadEnvLoad } from "./peers-RBplwovA.js";
6
+ import { d as hasYes, f as isNonInteractive, m as stripTierFlag, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as stripNoEnvFlag, s as lastSentryEventId } from "./telemetry-C4foD8MD.js";
7
+ import { confirm, select } from "@clack/prompts";
8
+ //#region ../cli-core/src/session.ts
1
9
  /**
2
- * `pnpm devtools`'s actual entry point, run by the `bin/devtools.mjs`
3
- * bootstrap BEFORE `cli.ts` — and therefore every command it dispatches — is
4
- * even imported.
5
- *
6
- * ## Why this has to run first
7
- *
8
- * A deploy tier used to be resolved deep inside `menu.ts`, after the wizard
9
- * had already opened, and a typed command's tier lived wherever that command
10
- * happened to resolve one — `cf preview`, `cron run` and `db --target remote`
11
- * each asking their own question. That meant `--tier` only worked for the
12
- * commands that had been taught to parse it, and a contributor who wanted
13
- * `pnpm devtools db status --target remote --tier staging` piped through a
14
- * wizard question first anyway.
15
- *
16
- * This module settles the tier ONCE, before any command's imports — let alone
17
- * its code — run, using the shared policy in `@devdogsuga/env/session`. By
18
- * the time `cli.ts` is imported, `process.env.DEPLOY_ENV` already names the
19
- * tier, so `cli.ts`, `menu.ts` and every runner underneath them just read
20
- * `process.env.DEPLOY_ENV`/`DEV_DB` like any other command would under
21
- * `with-env` — this module IS the replacement for `with-env`'s job.
22
- *
23
- * ## Entering the environment: at launch, or deferred to the menu
24
- *
25
- * "Settling the tier" and "entering it" (loading `.env.<tier>` and its
26
- * `DEV_DB` overlay onto `process.env`, via `@devdogsuga/env/session`'s
27
- * `enterEnvironment`) are two different steps, and a MENU invocation —
28
- * a bare `pnpm devtools`, or a bare group like `pnpm devtools db` resumed
29
- * through `bareGroupStartPath` — only gets the first one here. Entering holds
30
- * the env files hostage to whatever was true at the moment this process
31
- * started; a contributor who starts the local stack in another terminal
32
- * while still choosing from the wizard would otherwise have that missed
33
- * entirely. So for a menu invocation this module resolves the tier, exports
34
- * `DEPLOY_ENV`/`DEV_DB` directly (skipping `enterEnvironment` itself), and
35
- * registers `env-entry.ts`'s menu hook instead of entering — `menu.ts`'s
36
- * `runMenu` calls it right before dispatching the ONE command the reader
37
- * chose, so the env files loaded are current as of that moment, not this
38
- * one. A typed command line still enters right here, unchanged: there is no
39
- * wizard delay to protect it from.
40
- *
41
- * ## `--tier`, a global flag
42
- *
43
- * `--tier <t>` is accepted at ANY position in argv and stripped here before
44
- * `cli.ts` ever sees the rest, precisely so it cannot collide with a
45
- * command's own flag of the same name (`db --target remote --tier staging`,
46
- * `cron run --tier production`) — those still parse their OWN `--tier` out of
47
- * the argv `cli.ts` receives, but by then the session's tier decision has
48
- * already been made, and `resolveTier` (see `tier.ts`) falls back to reading
49
- * it off `process.env.DEPLOY_ENV` rather than asking again.
50
- */
51
- import { select } from "@clack/prompts";
52
- import { findCommand } from "./commands.js";
53
- import { enterSessionEnvironment, realEnvEntryDeps, setMenuEnvHook, } from "./env-entry.js";
54
- import { bareGroupStartPath } from "./menu.js";
55
- import { discoverRepoRoot, findRepoRoot, RepoNotFoundError, } from "./repo/root.js";
56
- import { loadEnvLoad, loadEnvSession } from "./repo/peers.js";
57
- import { captureDevtoolsError, initDevtoolsTelemetry } from "./telemetry.js";
58
- import { ignoreClosedPipes } from "./pipes.js";
59
- import { errorMessage, unwrap } from "./ui.js";
10
+ * Pieces of tier resolution both launchers use.
11
+ */
12
+ /** The real interactive picker: a clack `select`, unwrapped so Ctrl-C exits
13
+ * cleanly instead of leaking a cancel symbol into `resolveSessionTier`.
14
+ * Values are session selector words (`"development:local"`, `"staging"`…). */
15
+ async function promptTier(message, choices) {
16
+ return unwrap(await select({
17
+ message,
18
+ options: choices
19
+ }));
20
+ }
21
+ /**
22
+ * The session for `--no-env`: the named tier, parsed and nothing more.
23
+ * `--tier` wins over `DEPLOY_ENV`, and a run that names neither is plain
24
+ * development.
25
+ */
26
+ function resolveWithoutEnv(envSession, explicit) {
27
+ const selector = explicit ?? nonEmpty(process.env.DEPLOY_ENV) ?? "development";
28
+ const parsed = envSession.parseSessionSelector(selector);
29
+ if (parsed === null) return {
30
+ ok: false,
31
+ reason: `unknown tier "${selector}". Expected: development, ${envSession.SESSION_SELECTORS.join(", ")}.`
32
+ };
33
+ return {
34
+ ok: true,
35
+ ...parsed,
36
+ resolvedBy: "explicit"
37
+ };
38
+ }
39
+ //#endregion
40
+ //#region ../cli-core/src/safety-gate.ts
41
+ /**
42
+ * The production and staging safety gate.
43
+ *
44
+ * devtools can reach any tier, and it cannot tell which `supabase`
45
+ * subcommands write, so the question is asked before ANY command runs against
46
+ * a hosted tier, passthroughs and presets included. This replaces the
47
+ * per-command confirmations `db reset`, `db migrate`, `seed production` and
48
+ * `cf preview` each used to carry.
49
+ *
50
+ * * production: shows the tier and project ref (never the `DB_URL`, which
51
+ * holds the password) and asks once, defaulting to no.
52
+ * * staging: the same, with milder wording.
53
+ * * non-interactive runs need `--yes`; without it the gate refuses instead
54
+ * of hanging on a prompt nobody can answer. clack's `confirm()` never
55
+ * resolves without a TTY, so `--yes` is checked before anything that
56
+ * could wait on one.
57
+ * * development (local or remote): no prompt.
58
+ */
59
+ /** Set once the gate passed, so a devtools run spawned by devtools does not ask again. */
60
+ const GATE_PASSED_ENV = "DEVTOOLS_GATE_PASSED";
61
+ const HOSTED = /* @__PURE__ */ new Set(["staging", "production"]);
62
+ function isHostedTier(tier) {
63
+ return HOSTED.has(tier);
64
+ }
65
+ async function askClack(message) {
66
+ return unwrap(await confirm({
67
+ message,
68
+ initialValue: false
69
+ }));
70
+ }
60
71
  /**
61
- * Pulls a global `--tier <t>` out of `argv`, wherever it sits, leaving every
62
- * other argument untouched and in its original order. Exported for its own
63
- * unit tests; `launch()` below is the only real caller.
64
- */
65
- export function stripTierFlag(argv) {
66
- const rest = [...argv];
67
- const index = rest.indexOf("--tier");
68
- if (index === -1)
69
- return { explicit: undefined, rest };
70
- const value = rest[index + 1];
71
- // A trailing `--tier` with nothing after it removes just the flag; the
72
- // missing value then reaches `resolveSessionTier` as `explicit: undefined`,
73
- // which falls through to `DEPLOY_ENV`/the sole tier/the prompt exactly as
74
- // if `--tier` had never been typed, rather than this function guessing.
75
- //
76
- // A following token that is itself a flag is treated the same way, NOT
77
- // consumed as the value — the guard every other flag-value reader in this
78
- // CLI keeps (`cli.ts`'s `flagValue`). Without it,
79
- // `--tier --help` or `--tier -h` would swallow the flag as a bogus tier and
80
- // refuse with "unknown tier" instead of reaching the help bypass below.
81
- const missing = value === undefined || value.startsWith("-");
82
- rest.splice(index, missing ? 1 : 2);
83
- return { explicit: missing ? undefined : value, rest };
72
+ * Decides whether a command may run against `tier`. Writes its own refusal to
73
+ * stderr, so the caller only has to exit non-zero on `proceed: false`.
74
+ */
75
+ async function gateHostedTier(input) {
76
+ const env = input.env ?? process.env;
77
+ if (!isHostedTier(input.tier)) return { proceed: true };
78
+ if (env["DEVTOOLS_GATE_PASSED"] === input.tier) return { proceed: true };
79
+ const production = input.tier === "production";
80
+ const target = `${production ? "PRODUCTION" : "staging"} (project ${input.projectRef ?? "unknown"})`;
81
+ const cli = cliName();
82
+ const command = formatCommand(cli, input.argv);
83
+ if (input.yes) {
84
+ if (input.nonInteractive) process.stderr.write(`${cli}: running \`${command}\` against ${target}.\n`);
85
+ return { proceed: true };
86
+ }
87
+ if (input.nonInteractive) {
88
+ process.stderr.write(`${cli}: refusing to run \`${command}\` against ${target} without --yes. Nobody is here to confirm it.
89
+ `);
90
+ return {
91
+ proceed: false,
92
+ reason: "refused"
93
+ };
94
+ }
95
+ if (!await (input.ask ?? askClack)(production ? `This runs \`${command}\` against PRODUCTION, project ${input.projectRef ?? "unknown"}. It is live data. Continue?` : `This runs \`${command}\` against staging, project ${input.projectRef ?? "unknown"}. Continue?`)) {
96
+ process.stderr.write(`${cli}: left ${input.tier} alone.\n`);
97
+ return {
98
+ proceed: false,
99
+ reason: "declined"
100
+ };
101
+ }
102
+ return { proceed: true };
84
103
  }
104
+ //#endregion
105
+ //#region ../cli-core/src/pipes.ts
85
106
  /**
86
- * Whether the command `rest` dispatches to is declared `envFree` in
87
- * `commands.ts`'s catalog — the leading run of non-flag tokens is the
88
- * command path (`["github", "rulesets"]` out of `["github", "rulesets",
89
- * "--apply"]`), the same convention `stripTierFlag` already uses for
90
- * pulling a flag out of argv wherever it sits.
91
- *
92
- * Catalog-driven rather than a second hardcoded name list: `setup` and
93
- * `completions` below stay their own explicit branch (each skips tier
94
- * resolution for a DIFFERENT reason worth spelling out at the call site —
95
- * see the comment above), but a plain "this command touches no env at all"
96
- * exemption reads once, from the same tree `--help` and the wizard already
97
- * render, rather than as a name a future GitHub-only command has to
98
- * remember to add here too.
99
- */
107
+ * Keeps a closed stdout/stderr from crashing devtools.
108
+ *
109
+ * When whatever reads devtools' output goes away first (`devtools … | head`,
110
+ * or a wrapper that stopped listening), the next write fails with EPIPE. The
111
+ * stream emits that as an `'error'` event nobody handles, so Node throws it as
112
+ * an uncaught exception and Sentry files it as a fatal. Nobody is left to read
113
+ * the output, so dropping it is the right outcome. Any other stream error is
114
+ * rethrown and crashes as it did before.
115
+ *
116
+ * Idempotent, because `launch`, `launchCi` and `ci.ts`'s `main` can all run in
117
+ * one process.
118
+ */
119
+ let installed = false;
120
+ function ignoreClosedPipes() {
121
+ if (installed) return;
122
+ installed = true;
123
+ for (const stream of [process.stdout, process.stderr]) stream.on("error", (err) => {
124
+ if (err.code !== "EPIPE") throw err;
125
+ });
126
+ }
127
+ //#endregion
128
+ //#region src/launch.ts
129
+ /**
130
+ * Whether the command `rest` dispatches to is declared `envFree` in
131
+ * the command catalog — the leading run of non-flag tokens is the
132
+ * command path (`["oauth"]` out of `["oauth", "--json"]`), the same convention `stripTierFlag` already uses for
133
+ * pulling a flag out of argv wherever it sits.
134
+ *
135
+ * Catalog-driven rather than a second hardcoded name list: `setup` and
136
+ * `completions` below stay their own explicit branch (each skips tier
137
+ * resolution for a DIFFERENT reason worth spelling out at the call site —
138
+ * see the comment above), but a plain "this command touches no env at all"
139
+ * exemption reads once, from the same tree `--help` and the wizard already
140
+ * render, rather than as a name a future env-free command has to
141
+ * remember to add here too.
142
+ */
100
143
  function isEnvFreeCommand(rest) {
101
- const path = [];
102
- for (const arg of rest) {
103
- if (arg.startsWith("-"))
104
- break;
105
- path.push(arg);
106
- }
107
- return findCommand(path)?.envFree === true;
144
+ return catalog.findCommand(helpPath(rest))?.envFree === true;
108
145
  }
109
- /** The real interactive picker: a clack `select`, unwrapped so Ctrl-C exits
110
- * cleanly instead of leaking a cancel symbol into `resolveSessionTier`.
111
- * Values are session selector words (`"development:local"`, `"staging"`…). */
112
- async function promptTier(message, choices) {
113
- return unwrap(await select({ message, options: choices }));
146
+ /**
147
+ * Whether the command reads nothing but the checkout, so the session behaves
148
+ * as if `--no-env` was typed: development is named and no env file is
149
+ * looked for or complained about. The `check` commands, which CI runs on a
150
+ * runner that has no `.env` at all.
151
+ */
152
+ function skipsEnvEntry(rest) {
153
+ return catalog.findCommand(helpPath(rest))?.noEnv === true;
154
+ }
155
+ /** The offer's stack start (`env-entry.ts` holds the TTY prompt, this holds
156
+ * the lifecycle command), imported lazily like the rest of the commands. */
157
+ async function startStack() {
158
+ const { runStackCommand } = await import("./stack-Bx-55oi_.js");
159
+ return runStackCommand("start");
114
160
  }
115
161
  /**
116
- * Imports `cli.ts` and runs it, reporting a thrown rejection the same way
117
- * `cli.ts`'s own top level used to before `main` became an export instead of
118
- * an auto-running IIFE (see `main`'s own header). Shared by both places
119
- * `launch()` hands off to it: the `--help`/`-h` bypass below, and the normal
120
- * post-tier-resolution path.
121
- */
162
+ * Imports `cli.ts` and runs it, reporting a thrown rejection the same way
163
+ * `cli.ts`'s own top level used to before `main` became an export instead of
164
+ * an auto-running IIFE (see `main`'s own header). Shared by both places
165
+ * `launch()` hands off to it: the `--help`/`-h` bypass below, and the normal
166
+ * post-tier-resolution path.
167
+ */
122
168
  async function dispatch(argv) {
123
- const { main } = await import("./cli.js");
124
- try {
125
- await main(argv);
126
- }
127
- catch (err) {
128
- process.stderr.write(`devtools: ${errorMessage(err)}\n`);
129
- // Report BEFORE exiting — see `captureDevtoolsError`'s header.
130
- await captureDevtoolsError(err);
131
- process.exit(1);
132
- }
169
+ const { main } = await import("./cli-CZKkNHK5.js");
170
+ try {
171
+ await main(argv);
172
+ } catch (err) {
173
+ process.stderr.write(`devtools: ${errorMessage(err)}\n`);
174
+ await captureDevtoolsError(err);
175
+ process.exit(1);
176
+ }
133
177
  }
134
178
  /**
135
- * Resolves the session's deploy tier, enters it (or defers entry to the
136
- * menu — see this file's header), and hands off to `cli.ts`.
137
- *
138
- * Exits the process directly on a refusal from `resolveSessionTier` — there
139
- * is no command dispatched yet for a caller to fall back to, so there is
140
- * nothing this function could return that would mean anything.
141
- */
142
- export async function launch(argv) {
143
- const { explicit, rest } = stripTierFlag(argv);
144
- // Here rather than only in `cli.ts`'s `main()`, so a failure while
145
- // resolving or entering the tier below is reported too. `main()`'s own
146
- // call is then a no-op (`initDevtoolsTelemetry` is idempotent) and this
147
- // tag, computed from the same argv `main()` receives, stands.
148
- ignoreClosedPipes();
149
- initDevtoolsTelemetry(rest[0] ?? "menu");
150
- // Same test `cli.ts`'s own `main()` uses to decide whether a bare command
151
- // group resumes the wizard instead of dispatching — computed here too so
152
- // this function can decide, before resolving anything, whether entry gets
153
- // deferred to `menu.ts`'s hook. A non-TTY caller never resumes the wizard
154
- // (see `bareGroupStartPath`'s own doc), so `rest.length === 0` is the only
155
- // menu shape it can hit — mirroring `main()`'s own `process.stdin.isTTY ?
156
- // bareGroupStartPath(argv) : null`.
157
- const isMenuInvocation = rest.length === 0 ||
158
- (process.stdin.isTTY === true && bareGroupStartPath(rest) !== null);
159
- // `--help`/`-h` bypasses tier resolution entirely, BEFORE it can refuse.
160
- // `cli.ts`'s own `main()` already answers these with no env in play (see
161
- // its header comment); resolving a tier first anyway meant `pnpm devtools
162
- // --help` refused outright on a machine with two-or-more tier files
163
- // present — the ordinary state for anyone who has ever run `env pull` for
164
- // staging or production — even though the command that would have run
165
- // needs no database, credential, or `DEPLOY_ENV` at all. `bw --help` is
166
- // NOT special-cased here the way `cli.ts` special-cases it ahead of its own
167
- // `--help` check: `main()` still sees `bw` first and hands off to Bitwarden
168
- // before ever reaching its `--help` branch, so routing straight to `main()`
169
- // below reproduces that passthrough correctly either way.
170
- if (rest.includes("--help") || rest.includes("-h")) {
171
- await dispatch(rest);
172
- return;
173
- }
174
- const envLoad = await loadEnvLoad();
175
- const envSession = await loadEnvSession();
176
- // Records, once, which keys THIS shell actually exported before devtools
177
- // touched anything — the fix for the staleness bug `enterEnvironment`'s own
178
- // `override: false` comment below describes. `Object.keys(process.env)` at
179
- // this exact point is still a faithful snapshot of the calling shell: the
180
- // imports above load code, not env; nothing before this line has mutated
181
- // `process.env`. See `SHELL_KEYS_ENV` (`@devdogsuga/env/load`) for how
182
- // `loadEnvironment` narrows "a shell var beats the file" to just this list.
183
- //
184
- // Guarded on absence so a NESTED devtools launcher (one `pnpm devtools`
185
- // command spawning another, e.g. the `db start` offer below re-dispatching
186
- // through `dispatch()`) keeps the ORIGINAL list instead of recomputing one:
187
- // by the time a nested launcher's `launch()` runs, `process.env` already
188
- // holds this session's entered values, not the outer shell's, and
189
- // recomputing from it would misclassify every one of them as a genuine
190
- // shell export — the exact bug this marker exists to prevent, one layer in.
191
- process.env[envLoad.SHELL_KEYS_ENV] ??= Object.keys(process.env).join(",");
192
- let tier;
193
- let devDatabase;
194
- if (rest[0] === "setup" ||
195
- rest[0] === "completions" ||
196
- isEnvFreeCommand(rest)) {
197
- // Two commands run BEFORE there is a tier to resolve, and forcing the
198
- // mandate on them breaks each in its own way:
199
- //
200
- // * `setup` exists to CREATE a missing env file. A stale
201
- // `DEPLOY_ENV=staging` in the shell with no `.env.staging` on disk
202
- // would resolve that tier, fail `enterEnvironment` fatally below, and
203
- // lock a new contributor out of the one command that fixes their
204
- // state — a bootstrap deadlock. It is development-only by nature
205
- // (it writes `.env`), so the session tier question has one answer.
206
- // * `completions` runs from shell rc files (`eval "$(pnpm devtools
207
- // completions bash)"`), always non-TTY, and reads no env at all; the
208
- // multi-tier refusal would exit 1 in every new shell on exactly the
209
- // machines of the people working on the deploy workflow.
210
- //
211
- // A third, open-ended case joins them here via `isEnvFreeCommand`:
212
- // `github rulesets` and `github settings` (TASK-322, TASK-342) touch no
213
- // DevDogsUGA env file or database at all — every write either one makes
214
- // is a `gh api` call resolved from its own `--org`/`--repo` flags — so
215
- // demanding a `--tier` before either could run was never a real
216
- // requirement, only every command sharing one dispatch gate. See
217
- // `isEnvFreeCommand`'s own doc for why this is catalog-driven rather
218
- // than a third name joining the `rest[0] ===` checks above.
219
- //
220
- // Development is still ENTERED below (missing-file tolerated), not
221
- // skipped: `setup` under the old `with-env` wrapper saw whatever `.env`
222
- // already existed, and keeping that means it can read current values
223
- // when offering to rewrite them.
224
- tier = "development";
225
- }
226
- else if (discoverRepoRoot() === null) {
227
- // Every command past this point needs a real repo to resolve a tier
228
- // against (`findRepoRoot()` below would throw `RepoNotFoundError`
229
- // anyway) — surfaced here as the same clean, expected refusal every
230
- // other repo-dependent command gives, rather than an uncaught throw
231
- // with a stack trace pointing at internal module paths.
232
- process.stderr.write(`devtools: ${new RepoNotFoundError().message}\n`);
233
- process.exit(1);
234
- }
235
- else {
236
- // The `.env`-names-a-remote-database lookup (and the port probe that
237
- // phrases the local hint) are only paid when the resolution could
238
- // actually land on BARE development — an explicit staging/production or
239
- // qualified selector, an already-deployed DEPLOY_ENV, or a DEV_DB answer
240
- // all settle the question without them, and the lookup imports dotenvx.
241
- const deployEnv = process.env.DEPLOY_ENV ?? "";
242
- const couldBeBareDevelopment = (process.env.DEV_DB ?? "") === "" &&
243
- (explicit === "development" ||
244
- (explicit === undefined &&
245
- (deployEnv === "" || deployEnv === "development")));
246
- const remoteCandidate = couldBeBareDevelopment
247
- ? await envSession.developmentRemoteCandidate(findRepoRoot())
248
- : undefined;
249
- const localStackOnline = typeof remoteCandidate === "string"
250
- ? await envLoad.probeLocalStack()
251
- : undefined;
252
- const resolution = await envSession.resolveSessionTier({
253
- explicit,
254
- deployEnv: process.env.DEPLOY_ENV,
255
- devDb: process.env.DEV_DB,
256
- available: await envSession.availableTiers(findRepoRoot()),
257
- isTTY: process.stdin.isTTY === true,
258
- prompt: promptTier,
259
- promptMessage: "Which environment should this session use?",
260
- remoteCandidate,
261
- localStackOnline,
262
- });
263
- if (!resolution.ok) {
264
- process.stderr.write(`devtools: ${resolution.reason}\n`);
265
- process.exit(1);
266
- }
267
- tier = resolution.tier;
268
- devDatabase = resolution.devDatabase;
269
- }
270
- if (isMenuInvocation) {
271
- // Export the decision, not the files: `DEPLOY_ENV`/`DEV_DB` name the
272
- // session the same way `enterEnvironment` would set them, but loading
273
- // `.env.<tier>` itself — and everything `enterSessionEnvironment` does
274
- // around that, including the offline-stack offer — waits for a command to
275
- // actually be chosen. See this file's header.
276
- process.env.DEPLOY_ENV = tier;
277
- if (devDatabase !== undefined)
278
- process.env.DEV_DB = devDatabase;
279
- setMenuEnvHook((commandArgv, dispatchCommand) => enterSessionEnvironment(tier, devDatabase, commandArgv, realEnvEntryDeps(envLoad, envSession), dispatchCommand));
280
- await dispatch(rest);
281
- return;
282
- }
283
- // Caught here rather than left to `bin/devtools.mjs`'s `child.on("error",
284
- // …)`, which only ever sees a spawn failure, never a rejection thrown
285
- // inside this process.
286
- await enterSessionEnvironment(tier, devDatabase, rest, realEnvEntryDeps(envLoad, envSession), () => dispatch(rest));
179
+ * Runs `proceed` only if the hosted-tier gate lets the command through (see
180
+ * `@devdogsuga/cli-core/safety-gate`). Called after the environment is
181
+ * entered, so the project ref it shows is the tier's own. A command the gate
182
+ * stops exits non-zero and returns `blocked`.
183
+ */
184
+ async function gated(tier, commandArgv, proceed, blocked) {
185
+ if (isDryRun()) return proceed();
186
+ if (!(await gateHostedTier({
187
+ tier,
188
+ projectRef: nonEmpty(process.env.PROJECT_REF),
189
+ argv: commandArgv,
190
+ yes: hasYes(commandArgv),
191
+ nonInteractive: isNonInteractive()
192
+ })).proceed) {
193
+ process.exitCode = 1;
194
+ return blocked;
195
+ }
196
+ process.env[GATE_PASSED_ENV] = tier;
197
+ return proceed();
287
198
  }
288
- // `bin/devtools.mjs` runs this file directly through tsx — `tsx
289
- // --conditions=devdogs-source src/launch.ts <argv…>` — so `process.argv`
290
- // carries this file's own path where a bare `node` invocation would, and the
291
- // real argv starts one slot later.
292
- if (import.meta.url === `file://${process.argv[1]}`) {
293
- await launch(process.argv.slice(2));
199
+ /**
200
+ * Resolves the session's deploy tier, enters it (or defers entry to the
201
+ * menu — see this file's header), and hands off to `cli.ts`.
202
+ *
203
+ * Exits the process directly on a refusal from `resolveSessionTier` — there
204
+ * is no command dispatched yet for a caller to fall back to, so there is
205
+ * nothing this function could return that would mean anything.
206
+ */
207
+ async function launch(argv) {
208
+ const { noEnv, rest: withoutNoEnv } = stripNoEnvFlag(argv);
209
+ const { explicit, rest: withoutTier } = stripTierFlag(withoutNoEnv);
210
+ const { dryRun, rest } = resolveDryRun(withoutTier);
211
+ setDryRun(dryRun);
212
+ installFailureLog({
213
+ argv,
214
+ eventId: lastSentryEventId
215
+ });
216
+ ignoreClosedPipes();
217
+ initDevtoolsTelemetry(rest[0] ?? "menu");
218
+ const isMenuInvocation = rest.length === 0 || process.stdin.isTTY === true && bareGroupStartPath(catalog, rest) !== null;
219
+ if (rest.includes("--help") || rest.includes("-h")) {
220
+ await dispatch(rest);
221
+ return;
222
+ }
223
+ if (rest[0] === "version") {
224
+ await dispatch(rest);
225
+ return;
226
+ }
227
+ const envLoad = await loadEnvLoad();
228
+ const envSession = await loadEnvSession();
229
+ process.env[envLoad.SHELL_KEYS_ENV] ??= Object.keys(process.env).join(",");
230
+ let tier;
231
+ let devDatabase;
232
+ if (rest[0] === "setup" || rest[0] === "completions" || isEnvFreeCommand(rest)) tier = "development";
233
+ else if (discoverRepoRoot() === null) {
234
+ process.stderr.write(`devtools: ${new RepoNotFoundError().message}\n`);
235
+ process.exit(1);
236
+ } else {
237
+ if (isNonInteractive() && rest.length > 0 && explicit === void 0 && !process.env.DEPLOY_ENV && !process.env.DEV_DB) {
238
+ process.stderr.write("devtools: no tier named. With no terminal (or CI=true) the tier must be explicit: pass --tier <development:local|development:remote|staging|production> or set DEPLOY_ENV.\n");
239
+ process.exit(1);
240
+ }
241
+ const deployEnv = process.env.DEPLOY_ENV ?? "";
242
+ const remoteCandidate = !noEnv && (process.env.DEV_DB ?? "") === "" && (explicit === "development" || explicit === void 0 && (deployEnv === "" || deployEnv === "development")) ? await envSession.developmentRemoteCandidate(findRepoRoot()) : void 0;
243
+ const localStackOnline = typeof remoteCandidate === "string" ? await envLoad.probeLocalStack() : void 0;
244
+ const resolution = noEnv ? resolveWithoutEnv(envSession, explicit) : await envSession.resolveSessionTier({
245
+ explicit,
246
+ deployEnv: process.env.DEPLOY_ENV,
247
+ devDb: process.env.DEV_DB,
248
+ available: await envSession.availableTiers(findRepoRoot()),
249
+ isTTY: process.stdin.isTTY === true,
250
+ prompt: promptTier,
251
+ promptMessage: "Which environment should this session use?",
252
+ remoteCandidate,
253
+ localStackOnline
254
+ });
255
+ if (!resolution.ok) {
256
+ process.stderr.write(`devtools: ${resolution.reason}\n`);
257
+ process.exit(1);
258
+ }
259
+ tier = resolution.tier;
260
+ devDatabase = resolution.devDatabase;
261
+ }
262
+ if (isMenuInvocation) {
263
+ process.env.DEPLOY_ENV = tier;
264
+ if (devDatabase !== void 0) process.env.DEV_DB = devDatabase;
265
+ setMenuEnvHook((commandArgv, dispatchCommand) => enterSessionEnvironment(tier, devDatabase, commandArgv, realEnvEntryDeps(envLoad, envSession, startStack), () => gated(tier, commandArgv, dispatchCommand, null)));
266
+ await dispatch(rest);
267
+ return;
268
+ }
269
+ const run = () => gated(tier, rest, () => dispatch(rest), void 0);
270
+ if (noEnv || skipsEnvEntry(rest)) {
271
+ process.env.DEPLOY_ENV = tier;
272
+ if (devDatabase !== void 0) process.env.DEV_DB = devDatabase;
273
+ await run();
274
+ return;
275
+ }
276
+ await enterSessionEnvironment(tier, devDatabase, rest, realEnvEntryDeps(envLoad, envSession, startStack), run);
294
277
  }
278
+ if (import.meta.url === `file://${process.argv[1]}`) await launch(process.argv.slice(2));
279
+ //#endregion
280
+ export { launch, stripTierFlag };