@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
@@ -1,25 +0,0 @@
1
- /**
2
- * The connect flow's default `label` — what a contributor sees on the
3
- * platform's approval page naming which project is asking to connect.
4
- * `package.json`'s own `name` wins when present (it is the more deliberate
5
- * answer — someone typed it into a manifest); the working directory's own
6
- * name is the fallback for a project with no `package.json` at all, or one
7
- * that failed to parse.
8
- */
9
- import { existsSync, readFileSync } from "node:fs";
10
- import { basename, join } from "node:path";
11
- export function defaultLabel(cwd) {
12
- const pkgPath = join(cwd, "package.json");
13
- if (existsSync(pkgPath)) {
14
- try {
15
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
16
- if (typeof pkg.name === "string" && pkg.name.trim() !== "") {
17
- return pkg.name.trim();
18
- }
19
- }
20
- catch {
21
- // Falls through to the directory name below.
22
- }
23
- }
24
- return basename(cwd);
25
- }
@@ -1,52 +0,0 @@
1
- /** Default: as long as this repo's OAuth wizard reasonably keeps a browser tab open unattended. */
2
- export declare const DEFAULT_CALLBACK_TIMEOUT_MS: number;
3
- /** A successful `?code=...&state=...` callback. */
4
- export interface CallbackSuccess {
5
- code: string;
6
- state: string;
7
- }
8
- /** The platform denied the request, or reported some other OAuth `error=`. */
9
- export declare class CallbackDeniedError extends Error {
10
- readonly errorCode: string;
11
- readonly state: string | undefined;
12
- constructor(errorCode: string, state: string | undefined, description?: string);
13
- }
14
- /** No callback arrived within the timeout — the browser was never opened, or the tab was abandoned. */
15
- export declare class CallbackTimeoutError extends Error {
16
- constructor(timeoutMs: number);
17
- }
18
- /** The callback hit `/callback` but had neither `code` nor `error` — not a shape this flow expects. */
19
- export declare class CallbackMalformedError extends Error {
20
- constructor();
21
- }
22
- /**
23
- * The callback's `state` did not match the one this run generated (see
24
- * `pkce.ts`'s `generateState`) — the defining CSRF defense of this whole
25
- * flow. Any mismatch is treated the same regardless of cause (a stale tab
26
- * from a previous run, a forged callback, or a bug): refuse and say so,
27
- * never silently proceed with the wrong request.
28
- */
29
- export declare class StateMismatchError extends Error {
30
- constructor();
31
- }
32
- /** Throws `StateMismatchError` unless `callback.state` matches `expected`; returns `callback` unchanged otherwise. */
33
- export declare function verifyState(expected: string, callback: CallbackSuccess): CallbackSuccess;
34
- export interface LoopbackListener {
35
- /** The port `listen(0, ...)` picked. */
36
- port: number;
37
- /** `http://127.0.0.1:<port>/callback` — pass this as the connect URL's `redirect_uri`. */
38
- redirectUri: string;
39
- /** Resolves on a successful callback; rejects on denial, malformed callback, or timeout. Always settles exactly once. */
40
- result: Promise<CallbackSuccess>;
41
- /** Closes the listener immediately. Safe to call more than once, and safe to call after `result` has already settled. */
42
- close: () => void;
43
- }
44
- /**
45
- * Starts the listener and resolves once it is actually accepting
46
- * connections (i.e. once the real port is known). `result` is the separate
47
- * promise that settles once a callback (or the timeout) has been handled —
48
- * most callers `await start(...)` once, then `await listener.result`.
49
- */
50
- export declare function start(opts?: {
51
- timeoutMs?: number;
52
- }): Promise<LoopbackListener>;
@@ -1,168 +0,0 @@
1
- /**
2
- * The loopback HTTP listener the one-click connect flow waits on.
3
- *
4
- * `devtools oauth`'s connect flow opens the platform's `/tools/oauth/connect`
5
- * page in the contributor's browser, and the platform redirects back to a
6
- * `redirect_uri` of `http://127.0.0.1:<port>/callback` this process itself
7
- * is listening on — a random, ephemeral port, requested by passing `0` to
8
- * `listen()`, so two `devtools oauth` runs on the same machine never
9
- * collide. `127.0.0.1` specifically, not `0.0.0.0`: this must never accept a
10
- * connection from anything but the browser running ON this machine.
11
- *
12
- * Every exit path — success, `error=access_denied` (or any other `error=`),
13
- * a request with neither, and the 5-minute timeout — closes the server
14
- * exactly once. That is the one invariant every caller of `start()` may
15
- * lean on: `close()` (also exposed for a caller that gives up early, e.g.
16
- * Ctrl-C) is always safe to call more than once, and the server is never
17
- * left listening after `result` settles.
18
- */
19
- import { createServer } from "node:http";
20
- /** Default: as long as this repo's OAuth wizard reasonably keeps a browser tab open unattended. */
21
- export const DEFAULT_CALLBACK_TIMEOUT_MS = 5 * 60_000;
22
- /** The platform denied the request, or reported some other OAuth `error=`. */
23
- export class CallbackDeniedError extends Error {
24
- errorCode;
25
- state;
26
- constructor(errorCode, state, description) {
27
- super(description ? `${errorCode}: ${description}` : errorCode);
28
- this.errorCode = errorCode;
29
- this.state = state;
30
- this.name = "CallbackDeniedError";
31
- }
32
- }
33
- /** No callback arrived within the timeout — the browser was never opened, or the tab was abandoned. */
34
- export class CallbackTimeoutError extends Error {
35
- constructor(timeoutMs) {
36
- super(`No response from the browser after ${Math.round(timeoutMs / 1000)}s. ` +
37
- "Run the command again, and complete the connection in the browser tab it opens.");
38
- this.name = "CallbackTimeoutError";
39
- }
40
- }
41
- /** The callback hit `/callback` but had neither `code` nor `error` — not a shape this flow expects. */
42
- export class CallbackMalformedError extends Error {
43
- constructor() {
44
- super("The browser's callback had neither a code nor an error.");
45
- this.name = "CallbackMalformedError";
46
- }
47
- }
48
- /**
49
- * The callback's `state` did not match the one this run generated (see
50
- * `pkce.ts`'s `generateState`) — the defining CSRF defense of this whole
51
- * flow. Any mismatch is treated the same regardless of cause (a stale tab
52
- * from a previous run, a forged callback, or a bug): refuse and say so,
53
- * never silently proceed with the wrong request.
54
- */
55
- export class StateMismatchError extends Error {
56
- constructor() {
57
- super("The browser's callback carried a different state than this run generated " +
58
- "— the connection may have been tampered with. Run the command again.");
59
- this.name = "StateMismatchError";
60
- }
61
- }
62
- /** Throws `StateMismatchError` unless `callback.state` matches `expected`; returns `callback` unchanged otherwise. */
63
- export function verifyState(expected, callback) {
64
- if (callback.state !== expected)
65
- throw new StateMismatchError();
66
- return callback;
67
- }
68
- function renderCallbackPage(heading, body) {
69
- return ('<!doctype html><html><head><meta charset="utf-8"><title>DevDogs devtools</title></head>' +
70
- `<body style="font-family: system-ui, sans-serif; max-width: 32rem; margin: 4rem auto; text-align: center;">` +
71
- `<h1>${heading}</h1><p>${body}</p></body></html>`);
72
- }
73
- const SUCCESS_PAGE = renderCallbackPage("Connected", "You can close this tab and return to your terminal.");
74
- const DENIED_PAGE = renderCallbackPage("Connection not completed", "You can close this tab and return to your terminal.");
75
- /**
76
- * Starts the listener and resolves once it is actually accepting
77
- * connections (i.e. once the real port is known). `result` is the separate
78
- * promise that settles once a callback (or the timeout) has been handled —
79
- * most callers `await start(...)` once, then `await listener.result`.
80
- */
81
- export function start(opts = {}) {
82
- const timeoutMs = opts.timeoutMs ?? DEFAULT_CALLBACK_TIMEOUT_MS;
83
- return new Promise((resolveListener, rejectListener) => {
84
- let settled = false;
85
- let resolveResult;
86
- let rejectResult;
87
- const result = new Promise((res, rej) => {
88
- resolveResult = res;
89
- rejectResult = rej;
90
- });
91
- // `result` settles the instant the callback request arrives, which can
92
- // be before the caller gets around to `await`ing it (e.g. this
93
- // function's own `start(...)` promise has to resolve first). Node
94
- // reports an unhandled rejection for any promise that goes a full
95
- // microtask turn with no listener at all, even one a caller attaches
96
- // moments later — this dummy handler exists purely to be that first
97
- // listener; it does not consume the rejection for real callers, who
98
- // still get their own `await`/`.catch` on the same promise.
99
- result.catch(() => undefined);
100
- const server = createServer((req, res) => {
101
- const url = new URL(req.url ?? "/", "http://127.0.0.1");
102
- if (url.pathname !== "/callback") {
103
- res.writeHead(404).end();
104
- return;
105
- }
106
- const code = url.searchParams.get("code");
107
- const state = url.searchParams.get("state") ?? undefined;
108
- const error = url.searchParams.get("error");
109
- if (error) {
110
- res
111
- .writeHead(200, { "content-type": "text/html; charset=utf-8" })
112
- .end(DENIED_PAGE);
113
- settle(() => rejectResult(new CallbackDeniedError(error, state, url.searchParams.get("error_description") ?? undefined)));
114
- return;
115
- }
116
- if (!code || !state) {
117
- res
118
- .writeHead(400, { "content-type": "text/html; charset=utf-8" })
119
- .end(DENIED_PAGE);
120
- settle(() => rejectResult(new CallbackMalformedError()));
121
- return;
122
- }
123
- res
124
- .writeHead(200, { "content-type": "text/html; charset=utf-8" })
125
- .end(SUCCESS_PAGE);
126
- settle(() => resolveResult({ code, state }));
127
- });
128
- const timer = setTimeout(() => {
129
- settle(() => rejectResult(new CallbackTimeoutError(timeoutMs)));
130
- }, timeoutMs);
131
- // Never keep the process alive on this timer alone — a caller that
132
- // already got its result (or gave up and called close()) must be able
133
- // to exit immediately.
134
- timer.unref();
135
- const close = () => {
136
- clearTimeout(timer);
137
- server.close();
138
- };
139
- /** Runs `settleFn` exactly once, always closing the listener. */
140
- function settle(settleFn) {
141
- if (settled)
142
- return;
143
- settled = true;
144
- settleFn();
145
- close();
146
- }
147
- server.once("error", (err) => {
148
- if (!settled) {
149
- settled = true;
150
- rejectListener(err);
151
- return;
152
- }
153
- // An error after the listener already started (e.g. a second request
154
- // racing the close()) has nowhere useful to go; `result` already
155
- // settled, so this is not the caller's problem.
156
- });
157
- server.listen(0, "127.0.0.1", () => {
158
- const address = server.address();
159
- const port = typeof address === "object" && address !== null ? address.port : 0;
160
- resolveListener({
161
- port,
162
- redirectUri: `http://127.0.0.1:${port}/callback`,
163
- result,
164
- close,
165
- });
166
- });
167
- });
168
- }
@@ -1,6 +0,0 @@
1
- /** A cryptographically random PKCE code verifier: 32 bytes, base64url-encoded (43 characters). */
2
- export declare function generateCodeVerifier(): string;
3
- /** The S256 code challenge for `verifier` — `BASE64URL(SHA256(verifier))`. */
4
- export declare function codeChallengeFor(verifier: string): string;
5
- /** A random `state` value, opaque to the caller, tying an authorization request to its callback. */
6
- export declare function generateState(): string;
@@ -1,34 +0,0 @@
1
- /**
2
- * PKCE (RFC 7636) helpers for the one-click OAuth connect flow.
3
- *
4
- * `devtools oauth` is a public client with no way to hold a secret (it is a
5
- * CLI running on a contributor's machine), so the platform's `/tools/oauth/
6
- * connect` flow relies on PKCE instead: a random verifier stays local, only
7
- * its SHA-256 challenge crosses the network in the authorization request,
8
- * and the verifier itself is sent once, at the very end, to the token
9
- * exchange — see `exchange.ts`. `state` is the separate, unrelated defense
10
- * against a forged callback (`loopback.ts` checks it), not part of PKCE
11
- * proper, but generated the same way and kept here since every caller wants
12
- * both together.
13
- */
14
- import { createHash, randomBytes } from "node:crypto";
15
- /** Base64url, no padding — the encoding both the verifier and challenge use (RFC 7636 §4.1, §4.2). */
16
- function base64url(buf) {
17
- return buf
18
- .toString("base64")
19
- .replace(/\+/g, "-")
20
- .replace(/\//g, "_")
21
- .replace(/=+$/, "");
22
- }
23
- /** A cryptographically random PKCE code verifier: 32 bytes, base64url-encoded (43 characters). */
24
- export function generateCodeVerifier() {
25
- return base64url(randomBytes(32));
26
- }
27
- /** The S256 code challenge for `verifier` — `BASE64URL(SHA256(verifier))`. */
28
- export function codeChallengeFor(verifier) {
29
- return base64url(createHash("sha256").update(verifier).digest());
30
- }
31
- /** A random `state` value, opaque to the caller, tying an authorization request to its callback. */
32
- export function generateState() {
33
- return base64url(randomBytes(16));
34
- }
@@ -1,54 +0,0 @@
1
- import type { DeployEnvironment } from "@devdogsuga/env";
2
- import { type LocalSupabaseConfig } from "./db.js";
3
- export type TargetKind = "local" | "hosted";
4
- export interface ConnectTarget extends LocalSupabaseConfig {
5
- kind: TargetKind;
6
- }
7
- /** The env var names a hosted target's URL/service-role key carry OUTSIDE a checkout. */
8
- export declare const HOSTED_TARGET_ENV_KEYS: {
9
- readonly url: "SUPABASE_URL";
10
- readonly serviceRoleKey: "SUPABASE_SERVICE_ROLE_KEY";
11
- };
12
- /** `supabase status` failed, or the two keys it reports are incomplete — see `db.ts`. */
13
- export declare function resolveLocalTarget(cwd: string): Promise<ConnectTarget>;
14
- /**
15
- * Reads a hosted target's URL/service-role key from `.env.local`, then
16
- * `.env`, in `cwd`. Returns an empty object (not a throw) when neither file
17
- * has both keys — that is the ordinary "prompt for them" case, not an error.
18
- */
19
- export declare function readHostedTargetFromEnvFiles(cwd: string): Partial<LocalSupabaseConfig>;
20
- /** A checkout's env handling has no `API_URL`/`SECRET_KEY` for the chosen tier. */
21
- export declare class HostedCredentialsMissingError extends Error {
22
- readonly tier: DeployEnvironment;
23
- constructor(tier: DeployEnvironment);
24
- }
25
- /** The chosen tier had no tier to resolve to (declined the picker, or none available). */
26
- export declare class NoTierResolvedError extends Error {
27
- constructor();
28
- }
29
- export interface RepoHostedTargetDeps {
30
- /** `tier.ts`'s `resolveTier`, or a test double with the same shape. */
31
- resolveTier: (tierArg: string | undefined, message: string) => Promise<DeployEnvironment | null>;
32
- /**
33
- * Loads the chosen tier's env into `process.env` (or `env`, when given) —
34
- * `@devdogsuga/env/session`'s `enterEnvironment`, called with
35
- * `override: true`: the launcher already entered `development` for this
36
- * envFree command (see `launch.ts`), and a DELIBERATE, later choice of a
37
- * different tier here must win over that earlier one, not lose to it.
38
- */
39
- enterEnvironment: (tier: DeployEnvironment) => Promise<void>;
40
- /** Injectable for tests; defaults to `process.env`. */
41
- env?: NodeJS.ProcessEnv;
42
- }
43
- /**
44
- * Resolves a hosted target from INSIDE a DevDogsUGA checkout: asks which
45
- * tier (via `resolveTier`, which itself only prompts when more than one
46
- * tier's env file is present), enters that tier's environment, and reads
47
- * the platform app's `API_URL`/`SECRET_KEY` back out of it.
48
- *
49
- * Throws `NoTierResolvedError` when no tier was resolved (an invalid
50
- * `--tier`, or a declined picker — `resolveTier` already reported the
51
- * reason on stderr) and `HostedCredentialsMissingError` when the resolved
52
- * tier's env has no `API_URL`/`SECRET_KEY` at all.
53
- */
54
- export declare function resolveHostedTargetInRepo(tierArg: string | undefined, deps: RepoHostedTargetDeps): Promise<ConnectTarget>;
@@ -1,102 +0,0 @@
1
- /**
2
- * Resolving the Supabase project `devtools oauth` configures — the "target".
3
- *
4
- * Two kinds:
5
- *
6
- * * `local` — whatever Supabase project `supabase status` reports for
7
- * `cwd` (`db.ts`'s `detectLocalSupabase`, unchanged from the original
8
- * wizard).
9
- * * `hosted` — a Supabase project this machine has no local stack for at
10
- * all: staging, production, or someone else's. Its URL and service-role
11
- * key come from wherever devtools already knows to look, in order:
12
- *
13
- * 1. Inside a DevDogsUGA checkout, from the checked-out repo's own env
14
- * handling: the platform app's `API_URL`/`SECRET_KEY` for a tier
15
- * the contributor picks (TASK-345's forward note — tier resolution
16
- * stays LAZY, done here rather than by the launcher, and only when
17
- * "hosted" is actually chosen).
18
- * 2. Outside a checkout — a workshop repo, most likely — from
19
- * `.env.local` or `.env` in `cwd`, read for `SUPABASE_URL` /
20
- * `SUPABASE_SERVICE_ROLE_KEY` (the names the wider Supabase
21
- * ecosystem already uses for exactly this pair, so a project that
22
- * already has them needs no new convention).
23
- * 3. Neither: the wizard prompts, same as the original manual flow
24
- * always did for local.
25
- *
26
- * Every function below returns data or throws; none of them prompt. Prompting
27
- * is `wizard.ts`'s job — this module stays testable without `@clack/prompts`
28
- * in the loop.
29
- */
30
- import { existsSync, readFileSync } from "node:fs";
31
- import { join } from "node:path";
32
- import { parse } from "dotenv";
33
- import { detectLocalSupabase } from "./db.js";
34
- /** The env var names a hosted target's URL/service-role key carry OUTSIDE a checkout. */
35
- export const HOSTED_TARGET_ENV_KEYS = {
36
- url: "SUPABASE_URL",
37
- serviceRoleKey: "SUPABASE_SERVICE_ROLE_KEY",
38
- };
39
- /** `supabase status` failed, or the two keys it reports are incomplete — see `db.ts`. */
40
- export async function resolveLocalTarget(cwd) {
41
- const config = detectLocalSupabase(cwd);
42
- return { ...config, kind: "local" };
43
- }
44
- /**
45
- * Reads a hosted target's URL/service-role key from `.env.local`, then
46
- * `.env`, in `cwd`. Returns an empty object (not a throw) when neither file
47
- * has both keys — that is the ordinary "prompt for them" case, not an error.
48
- */
49
- export function readHostedTargetFromEnvFiles(cwd) {
50
- for (const file of [".env.local", ".env"]) {
51
- const path = join(cwd, file);
52
- if (!existsSync(path))
53
- continue;
54
- const parsed = parse(readFileSync(path, "utf-8"));
55
- const apiUrl = parsed[HOSTED_TARGET_ENV_KEYS.url];
56
- const serviceRoleKey = parsed[HOSTED_TARGET_ENV_KEYS.serviceRoleKey];
57
- if (apiUrl && serviceRoleKey)
58
- return { apiUrl, serviceRoleKey };
59
- }
60
- return {};
61
- }
62
- /** A checkout's env handling has no `API_URL`/`SECRET_KEY` for the chosen tier. */
63
- export class HostedCredentialsMissingError extends Error {
64
- tier;
65
- constructor(tier) {
66
- super(`${tier}'s API_URL/SECRET_KEY are not available in this checkout — run ` +
67
- `\`pnpm devtools env pull --target ${tier}\` first.`);
68
- this.tier = tier;
69
- this.name = "HostedCredentialsMissingError";
70
- }
71
- }
72
- /** The chosen tier had no tier to resolve to (declined the picker, or none available). */
73
- export class NoTierResolvedError extends Error {
74
- constructor() {
75
- super("No tier was resolved for the hosted Supabase project.");
76
- this.name = "NoTierResolvedError";
77
- }
78
- }
79
- /**
80
- * Resolves a hosted target from INSIDE a DevDogsUGA checkout: asks which
81
- * tier (via `resolveTier`, which itself only prompts when more than one
82
- * tier's env file is present), enters that tier's environment, and reads
83
- * the platform app's `API_URL`/`SECRET_KEY` back out of it.
84
- *
85
- * Throws `NoTierResolvedError` when no tier was resolved (an invalid
86
- * `--tier`, or a declined picker — `resolveTier` already reported the
87
- * reason on stderr) and `HostedCredentialsMissingError` when the resolved
88
- * tier's env has no `API_URL`/`SECRET_KEY` at all.
89
- */
90
- export async function resolveHostedTargetInRepo(tierArg, deps) {
91
- const tier = await deps.resolveTier(tierArg, "Which tier's Supabase project are you connecting?");
92
- if (!tier)
93
- throw new NoTierResolvedError();
94
- await deps.enterEnvironment(tier);
95
- const env = deps.env ?? process.env;
96
- const apiUrl = env.API_URL;
97
- const serviceRoleKey = env.SECRET_KEY;
98
- if (!apiUrl || !serviceRoleKey) {
99
- throw new HostedCredentialsMissingError(tier);
100
- }
101
- return { apiUrl, serviceRoleKey, kind: "hosted" };
102
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * Chooses which connect transport `devtools oauth`'s one-click branch uses:
3
- * `loopback` (TASK-351 — a local HTTP listener catches the platform's
4
- * redirect) or `device` (TASK-352 — a short code, approved in a browser
5
- * running anywhere, polled for). The device flow's browser step need not run
6
- * on this machine at all, which is exactly what a loopback redirect cannot
7
- * offer: nothing can redirect back to a listener the approving browser can't
8
- * reach.
9
- *
10
- * Three ways this gets decided, in order:
11
- *
12
- * 1. `--device`/`--loopback` on the command line — an explicit, later
13
- * choice always wins over any inference below.
14
- * 2. The environment: SSH (`SSH_CONNECTION`/`SSH_TTY`), a GitHub Codespace
15
- * (`CODESPACES=true`), or a dev container (`REMOTE_CONTAINERS`/
16
- * `DEVCONTAINER`) all mean the browser that will approve the
17
- * connection is almost certainly NOT running on this machine, so a
18
- * loopback redirect would just hang.
19
- * 3. Neither of the above: try loopback. `wizard.ts` itself handles the
20
- * remaining case — the loopback listener failing to start — by
21
- * falling back to device at that point, since that failure is only
22
- * known once `loopback.ts`'s `start()` has actually been tried.
23
- */
24
- export type ConnectTransport = "loopback" | "device";
25
- export interface TransportDecision {
26
- transport: ConnectTransport;
27
- reason: string;
28
- }
29
- /** Human-readable reason a remote/headless environment was detected, or `null` if none was. */
30
- export declare function detectRemoteEnvironmentReason(env?: NodeJS.ProcessEnv): string | null;
31
- /**
32
- * Resolves the transport BEFORE a loopback listener is ever attempted.
33
- * `forceDevice`/`forceLoopback` come from `--device`/`--loopback`; `cli.ts`
34
- * refuses both being set at once before this is ever called, so at most one
35
- * is true here.
36
- */
37
- export declare function decideTransport({ forceDevice, forceLoopback, env, }: {
38
- forceDevice?: boolean;
39
- forceLoopback?: boolean;
40
- env?: NodeJS.ProcessEnv;
41
- }): TransportDecision;
@@ -1,32 +0,0 @@
1
- /** Human-readable reason a remote/headless environment was detected, or `null` if none was. */
2
- export function detectRemoteEnvironmentReason(env = process.env) {
3
- if (env.SSH_CONNECTION || env.SSH_TTY) {
4
- return "an SSH session (SSH_CONNECTION/SSH_TTY is set)";
5
- }
6
- if (env.CODESPACES === "true") {
7
- return "a GitHub Codespace (CODESPACES=true)";
8
- }
9
- if (env.REMOTE_CONTAINERS || env.DEVCONTAINER) {
10
- return "a dev container (REMOTE_CONTAINERS/DEVCONTAINER is set)";
11
- }
12
- return null;
13
- }
14
- /**
15
- * Resolves the transport BEFORE a loopback listener is ever attempted.
16
- * `forceDevice`/`forceLoopback` come from `--device`/`--loopback`; `cli.ts`
17
- * refuses both being set at once before this is ever called, so at most one
18
- * is true here.
19
- */
20
- export function decideTransport({ forceDevice, forceLoopback, env = process.env, }) {
21
- if (forceDevice) {
22
- return { transport: "device", reason: "--device was passed" };
23
- }
24
- if (forceLoopback) {
25
- return { transport: "loopback", reason: "--loopback was passed" };
26
- }
27
- const remoteReason = detectRemoteEnvironmentReason(env);
28
- if (remoteReason) {
29
- return { transport: "device", reason: `detected ${remoteReason}` };
30
- }
31
- return { transport: "loopback", reason: "no remote environment detected" };
32
- }
@@ -1,40 +0,0 @@
1
- import { type ExchangeResult } from "./exchange.js";
2
- import { type ConnectTransport } from "./transport.js";
3
- import { type ConnectTarget } from "./target.js";
4
- /**
5
- * Step 2 (one-click branch): decides loopback vs. device (see `transport.ts`)
6
- * and runs it.
7
- *
8
- * `transportOverride` is `--device`/`--loopback` (see `commands.ts`'s `oauth`
9
- * node); `cli.ts` refuses passing both, so at most one is set here. Left
10
- * undefined, the transport is auto-detected from the environment.
11
- *
12
- * `startLoopback()` is called HERE, not inside `connectOneClick`, and ONLY
13
- * its failure falls back to device — every failure that can happen once the
14
- * listener is actually up (denial, timeout, a bad exchange — all of which
15
- * `connectOneClick` reports via `bail()`, or an unexpected rethrow) is that
16
- * flow's own and must propagate unchanged, not be mistaken for "the listener
17
- * never started" and silently rerouted to a different transport with a
18
- * misleading warning. Loopback forced via `--loopback` never falls back at
19
- * all: a start failure there is left to propagate as-is.
20
- */
21
- export declare function connectViaOneClick(target: ConnectTarget, platformUrl: string, cwd: string, transportOverride: ConnectTransport | undefined): Promise<ExchangeResult>;
22
- /**
23
- * The wizard. `baseUrlOverride`/`platformUrlOverride`, when given, replace
24
- * the canonical API and platform URLs (used by "Paste credentials instead"
25
- * and the one-click flow respectively). `transportOverride` (`--device`/
26
- * `--loopback`) forces the one-click flow's transport — see
27
- * `connectViaOneClick` — and is ignored on the "Paste credentials instead"
28
- * branch, which has no transport of its own.
29
- *
30
- * Intro and outro are the caller's, so this composes into the devtools menu
31
- * without drawing a second box inside the first.
32
- */
33
- export declare function runOAuthSetup(baseUrlOverride?: string, platformUrlOverride?: string, transportOverride?: ConnectTransport,
34
- /**
35
- * A target already resolved by a caller — `setup.ts`'s hosted wizard,
36
- * chaining in with the same project/service-role key a contributor just
37
- * typed, so they are not asked to pick "hosted" and paste the key a
38
- * second time. Skips Step 1 entirely when given.
39
- */
40
- presetTarget?: ConnectTarget): Promise<void>;