@devdogsuga/devtools 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/README.md +72 -11
  2. package/bin/devtools.mjs +4 -4
  3. package/dist/cli-CZKkNHK5.js +6619 -0
  4. package/dist/failure-log-BBPGejQv.js +256 -0
  5. package/dist/launch.js +267 -281
  6. package/dist/local-env-D8HC2JK_.js +351 -0
  7. package/dist/local-env-DrBCXqD8.js +2 -0
  8. package/dist/menu-CQBsQRgH.js +1684 -0
  9. package/dist/peer-redirect-hooks.js +15 -0
  10. package/dist/peers-RBplwovA.js +229 -0
  11. package/dist/root-C2OmZDwD.js +94 -0
  12. package/dist/stack-Bx-55oi_.js +2 -0
  13. package/dist/stack-BynCgRVS.js +181 -0
  14. package/dist/supabase-project-0knM0mqd.js +426 -0
  15. package/dist/telemetry-C4foD8MD.js +249 -0
  16. package/dist/telemetry.js +2 -148
  17. package/package.json +10 -29
  18. package/bin/devtools-ci-bare.mjs +0 -48
  19. package/bin/devtools-ci.mjs +0 -31
  20. package/dist/args.d.ts +0 -26
  21. package/dist/args.js +0 -78
  22. package/dist/bws/bw.d.ts +0 -18
  23. package/dist/bws/bw.js +0 -70
  24. package/dist/bws/client.d.ts +0 -43
  25. package/dist/bws/client.js +0 -363
  26. package/dist/bws/environments.d.ts +0 -96
  27. package/dist/bws/environments.js +0 -111
  28. package/dist/bws/pace.d.ts +0 -25
  29. package/dist/bws/pace.js +0 -43
  30. package/dist/bws/retry.d.ts +0 -12
  31. package/dist/bws/retry.js +0 -39
  32. package/dist/bws/store.d.ts +0 -1
  33. package/dist/bws/store.js +0 -32
  34. package/dist/bws/token.d.ts +0 -31
  35. package/dist/bws/token.js +0 -88
  36. package/dist/bws/vault.d.ts +0 -29
  37. package/dist/bws/vault.js +0 -200
  38. package/dist/catalog.d.ts +0 -57
  39. package/dist/catalog.js +0 -57
  40. package/dist/cf/build.d.ts +0 -13
  41. package/dist/cf/build.js +0 -65
  42. package/dist/cf/commands.d.ts +0 -1
  43. package/dist/cf/commands.js +0 -172
  44. package/dist/cf/local-env.d.ts +0 -58
  45. package/dist/cf/local-env.js +0 -177
  46. package/dist/ci.d.ts +0 -1
  47. package/dist/ci.js +0 -328
  48. package/dist/cli.d.ts +0 -11
  49. package/dist/cli.js +0 -1094
  50. package/dist/commands.d.ts +0 -261
  51. package/dist/commands.js +0 -1318
  52. package/dist/completions.d.ts +0 -4
  53. package/dist/completions.js +0 -110
  54. package/dist/cron/commands.d.ts +0 -47
  55. package/dist/cron/commands.js +0 -372
  56. package/dist/cron/discovery.d.ts +0 -55
  57. package/dist/cron/discovery.js +0 -85
  58. package/dist/cron/schema.d.ts +0 -45
  59. package/dist/cron/schema.js +0 -35
  60. package/dist/db/config-push.d.ts +0 -8
  61. package/dist/db/config-push.js +0 -11
  62. package/dist/db/connection.d.ts +0 -79
  63. package/dist/db/connection.js +0 -114
  64. package/dist/db/exec.d.ts +0 -1
  65. package/dist/db/exec.js +0 -11
  66. package/dist/db/generate-types.d.ts +0 -1
  67. package/dist/db/generate-types.js +0 -4
  68. package/dist/db/generated-env.d.ts +0 -69
  69. package/dist/db/generated-env.js +0 -113
  70. package/dist/db/introspect.d.ts +0 -1
  71. package/dist/db/introspect.js +0 -122
  72. package/dist/db/local-env.d.ts +0 -65
  73. package/dist/db/local-env.js +0 -23
  74. package/dist/db/new-migration.d.ts +0 -8
  75. package/dist/db/new-migration.js +0 -69
  76. package/dist/db/pooler.d.ts +0 -23
  77. package/dist/db/pooler.js +0 -55
  78. package/dist/db/run.d.ts +0 -68
  79. package/dist/db/run.js +0 -156
  80. package/dist/db/seed-buckets.d.ts +0 -2
  81. package/dist/db/seed-buckets.js +0 -4
  82. package/dist/db/seed-production.d.ts +0 -8
  83. package/dist/db/seed-production.js +0 -79
  84. package/dist/db/seed-roles.d.ts +0 -7
  85. package/dist/db/seed-roles.js +0 -33
  86. package/dist/db/session-refresh.d.ts +0 -43
  87. package/dist/db/session-refresh.js +0 -40
  88. package/dist/deploy/migrations.d.ts +0 -20
  89. package/dist/deploy/migrations.js +0 -68
  90. package/dist/deploy/orphans.d.ts +0 -20
  91. package/dist/deploy/orphans.js +0 -177
  92. package/dist/deploy/preflight.d.ts +0 -53
  93. package/dist/deploy/preflight.js +0 -189
  94. package/dist/deploy/report.d.ts +0 -26
  95. package/dist/deploy/report.js +0 -70
  96. package/dist/deploy/require-planner.d.ts +0 -2
  97. package/dist/deploy/require-planner.js +0 -60
  98. package/dist/deploy/require-token.d.ts +0 -15
  99. package/dist/deploy/require-token.js +0 -78
  100. package/dist/deploy/secrets-file.d.ts +0 -17
  101. package/dist/deploy/secrets-file.js +0 -166
  102. package/dist/deploy/write-env.d.ts +0 -40
  103. package/dist/deploy/write-env.js +0 -395
  104. package/dist/docs/index-pages.d.ts +0 -38
  105. package/dist/docs/index-pages.js +0 -186
  106. package/dist/emails/commands.d.ts +0 -15
  107. package/dist/emails/commands.js +0 -130
  108. package/dist/emails/email-types.d.ts +0 -13
  109. package/dist/emails/email-types.js +0 -1
  110. package/dist/emails/fixtures.d.ts +0 -20
  111. package/dist/emails/fixtures.js +0 -20
  112. package/dist/env/audit.d.ts +0 -206
  113. package/dist/env/audit.js +0 -436
  114. package/dist/env/cloudflare.d.ts +0 -21
  115. package/dist/env/cloudflare.js +0 -61
  116. package/dist/env/commands.d.ts +0 -99
  117. package/dist/env/commands.js +0 -613
  118. package/dist/env/discovery.d.ts +0 -15
  119. package/dist/env/discovery.js +0 -239
  120. package/dist/env/document.d.ts +0 -119
  121. package/dist/env/document.js +0 -323
  122. package/dist/env/example.d.ts +0 -68
  123. package/dist/env/example.js +0 -605
  124. package/dist/env/selection.d.ts +0 -105
  125. package/dist/env/selection.js +0 -228
  126. package/dist/env-entry.d.ts +0 -49
  127. package/dist/env-entry.js +0 -204
  128. package/dist/environment-doctor.d.ts +0 -45
  129. package/dist/environment-doctor.js +0 -429
  130. package/dist/environment.d.ts +0 -92
  131. package/dist/environment.js +0 -185
  132. package/dist/fingerprint.d.ts +0 -10
  133. package/dist/fingerprint.js +0 -18
  134. package/dist/gen/campus-map.d.ts +0 -1
  135. package/dist/gen/campus-map.js +0 -473
  136. package/dist/gen/commands.d.ts +0 -1
  137. package/dist/gen/commands.js +0 -25
  138. package/dist/gen/hypno.d.ts +0 -1
  139. package/dist/gen/hypno.js +0 -134
  140. package/dist/gh/client.d.ts +0 -80
  141. package/dist/gh/client.js +0 -259
  142. package/dist/gh/environments.d.ts +0 -83
  143. package/dist/gh/environments.js +0 -178
  144. package/dist/gh/rulesets/actors.d.ts +0 -14
  145. package/dist/gh/rulesets/actors.js +0 -69
  146. package/dist/gh/rulesets/api.d.ts +0 -15
  147. package/dist/gh/rulesets/api.js +0 -104
  148. package/dist/gh/rulesets/commands.d.ts +0 -3
  149. package/dist/gh/rulesets/commands.js +0 -203
  150. package/dist/gh/rulesets/desired.d.ts +0 -112
  151. package/dist/gh/rulesets/desired.js +0 -271
  152. package/dist/gh/rulesets/diff.d.ts +0 -65
  153. package/dist/gh/rulesets/diff.js +0 -170
  154. package/dist/gh/rulesets/types.d.ts +0 -76
  155. package/dist/gh/rulesets/types.js +0 -13
  156. package/dist/gh/settings/api.d.ts +0 -40
  157. package/dist/gh/settings/api.js +0 -180
  158. package/dist/gh/settings/commands.d.ts +0 -1
  159. package/dist/gh/settings/commands.js +0 -254
  160. package/dist/gh/settings/desired.d.ts +0 -53
  161. package/dist/gh/settings/desired.js +0 -43
  162. package/dist/gh/settings/diff.d.ts +0 -57
  163. package/dist/gh/settings/diff.js +0 -161
  164. package/dist/gh/settings/types.d.ts +0 -92
  165. package/dist/gh/settings/types.js +0 -11
  166. package/dist/gh/settings/workflows.d.ts +0 -41
  167. package/dist/gh/settings/workflows.js +0 -128
  168. package/dist/grantRoot.d.ts +0 -42
  169. package/dist/grantRoot.js +0 -89
  170. package/dist/help.d.ts +0 -18
  171. package/dist/help.js +0 -169
  172. package/dist/images/commands.d.ts +0 -32
  173. package/dist/images/commands.js +0 -231
  174. package/dist/images/events.d.ts +0 -47
  175. package/dist/images/events.js +0 -116
  176. package/dist/images/graphics.d.ts +0 -70
  177. package/dist/images/graphics.js +0 -234
  178. package/dist/images/open-graph-types.d.ts +0 -75
  179. package/dist/images/open-graph-types.js +0 -1
  180. package/dist/images/prompts.d.ts +0 -34
  181. package/dist/images/prompts.js +0 -100
  182. package/dist/images/render.d.ts +0 -21
  183. package/dist/images/render.js +0 -61
  184. package/dist/images/select.d.ts +0 -64
  185. package/dist/images/select.js +0 -96
  186. package/dist/instance.d.ts +0 -69
  187. package/dist/instance.js +0 -102
  188. package/dist/invocation.d.ts +0 -49
  189. package/dist/invocation.js +0 -78
  190. package/dist/launch-ci.d.ts +0 -1
  191. package/dist/launch-ci.js +0 -90
  192. package/dist/launch.d.ts +0 -18
  193. package/dist/menu.d.ts +0 -43
  194. package/dist/menu.js +0 -300
  195. package/dist/moderation.d.ts +0 -70
  196. package/dist/moderation.js +0 -128
  197. package/dist/oauth/browser.d.ts +0 -1
  198. package/dist/oauth/browser.js +0 -13
  199. package/dist/oauth/config.d.ts +0 -24
  200. package/dist/oauth/config.js +0 -24
  201. package/dist/oauth/connect-url.d.ts +0 -17
  202. package/dist/oauth/connect-url.js +0 -23
  203. package/dist/oauth/db.d.ts +0 -37
  204. package/dist/oauth/db.js +0 -108
  205. package/dist/oauth/device.d.ts +0 -93
  206. package/dist/oauth/device.js +0 -198
  207. package/dist/oauth/discovery.d.ts +0 -28
  208. package/dist/oauth/discovery.js +0 -54
  209. package/dist/oauth/env-file.d.ts +0 -6
  210. package/dist/oauth/env-file.js +0 -35
  211. package/dist/oauth/exchange.d.ts +0 -43
  212. package/dist/oauth/exchange.js +0 -93
  213. package/dist/oauth/label.d.ts +0 -1
  214. package/dist/oauth/label.js +0 -25
  215. package/dist/oauth/loopback.d.ts +0 -52
  216. package/dist/oauth/loopback.js +0 -168
  217. package/dist/oauth/pkce.d.ts +0 -6
  218. package/dist/oauth/pkce.js +0 -34
  219. package/dist/oauth/target.d.ts +0 -54
  220. package/dist/oauth/target.js +0 -102
  221. package/dist/oauth/transport.d.ts +0 -41
  222. package/dist/oauth/transport.js +0 -32
  223. package/dist/oauth/wizard.d.ts +0 -40
  224. package/dist/oauth/wizard.js +0 -578
  225. package/dist/persona.d.ts +0 -18
  226. package/dist/persona.js +0 -221
  227. package/dist/pick.d.ts +0 -6
  228. package/dist/pick.js +0 -64
  229. package/dist/pipes.d.ts +0 -1
  230. package/dist/pipes.js +0 -25
  231. package/dist/planner/checks.d.ts +0 -26
  232. package/dist/planner/checks.js +0 -104
  233. package/dist/planner/commands.d.ts +0 -10
  234. package/dist/planner/commands.js +0 -367
  235. package/dist/planner/db.d.ts +0 -15
  236. package/dist/planner/db.js +0 -33
  237. package/dist/planner/role.d.ts +0 -33
  238. package/dist/planner/role.js +0 -68
  239. package/dist/repo/peer-redirect-hooks.d.ts +0 -16
  240. package/dist/repo/peer-redirect-hooks.js +0 -10
  241. package/dist/repo/peer-redirect.d.ts +0 -4
  242. package/dist/repo/peer-redirect.js +0 -26
  243. package/dist/repo/peers.d.ts +0 -39
  244. package/dist/repo/peers.js +0 -139
  245. package/dist/repo/resolve.d.ts +0 -41
  246. package/dist/repo/resolve.js +0 -152
  247. package/dist/repo/root.d.ts +0 -23
  248. package/dist/repo/root.js +0 -91
  249. package/dist/repo/source.d.ts +0 -14
  250. package/dist/repo/source.js +0 -54
  251. package/dist/repo/supabase-project.d.ts +0 -60
  252. package/dist/repo/supabase-project.js +0 -124
  253. package/dist/repo/tsx-loader.d.ts +0 -6
  254. package/dist/repo/tsx-loader.js +0 -41
  255. package/dist/run/pick.d.ts +0 -90
  256. package/dist/run/pick.js +0 -664
  257. package/dist/setup.d.ts +0 -1
  258. package/dist/setup.js +0 -280
  259. package/dist/stack.d.ts +0 -48
  260. package/dist/stack.js +0 -403
  261. package/dist/telemetry.d.ts +0 -59
  262. package/dist/tier.d.ts +0 -40
  263. package/dist/tier.js +0 -96
  264. package/dist/ui.d.ts +0 -40
  265. package/dist/ui.js +0 -71
  266. package/dist/version.d.ts +0 -7
  267. package/dist/version.js +0 -17
  268. package/dist/workers.d.ts +0 -5
  269. package/dist/workers.js +0 -33
  270. package/dist/workflows/commands.d.ts +0 -54
  271. package/dist/workflows/commands.js +0 -763
package/dist/oauth/db.js DELETED
@@ -1,108 +0,0 @@
1
- import { createClient } from "@supabase/supabase-js";
2
- import { execFileSync } from "node:child_process";
3
- import { parse } from "dotenv";
4
- import { PROVIDER_SCOPES } from "./config.js";
5
- function runStatus(argv, cwd) {
6
- return execFileSync(argv[0], argv.slice(1), {
7
- cwd,
8
- encoding: "utf-8",
9
- stdio: ["pipe", "pipe", "pipe"],
10
- });
11
- }
12
- /**
13
- * Runs `supabase status -o env` in `cwd` and extracts the local API URL and
14
- * service role key. Throws a descriptive error if Supabase isn't running or
15
- * the CLI isn't installed.
16
- *
17
- * Two invocations are tried, because this wizard runs in two different worlds:
18
- * a sibling project, where the Supabase CLI is normally installed globally, and
19
- * this monorepo, where it is a workspace devDependency and not on PATH at all.
20
- * A global install wins, since `pnpm exec` in a non-pnpm project would fail for
21
- * reasons that have nothing to do with Supabase.
22
- */
23
- export function detectLocalSupabase(cwd) {
24
- let output;
25
- try {
26
- output = runStatus(["supabase", "status", "-o", "env"], cwd);
27
- }
28
- catch (first) {
29
- try {
30
- output = runStatus(["pnpm", "exec", "supabase", "status", "-o", "env"], cwd);
31
- }
32
- catch {
33
- // The first failure is the one worth reporting: it is the one that says
34
- // "not installed" rather than "pnpm could not find a workspace".
35
- if (first.code === "ENOENT") {
36
- throw new Error("supabase CLI not found — install it from https://supabase.com/docs/guides/cli");
37
- }
38
- const stderr = (first.stderr ?? "").trim();
39
- throw new Error(stderr || (first instanceof Error ? first.message : String(first)));
40
- }
41
- }
42
- const env = parse(output);
43
- const apiUrl = env["API_URL"];
44
- const serviceRoleKey = env["SERVICE_ROLE_KEY"];
45
- if (!apiUrl || !serviceRoleKey) {
46
- throw new Error("Could not find API_URL or SERVICE_ROLE_KEY in `supabase status` output");
47
- }
48
- return { apiUrl, serviceRoleKey };
49
- }
50
- /**
51
- * Looks up a custom auth provider on a local Supabase instance through the
52
- * Admin SDK, and reports whether it exists along with its name and issuer.
53
- *
54
- * A 404 means it does not exist yet. Any other error is real and rethrown.
55
- */
56
- export async function checkProvider({ apiUrl, serviceRoleKey }, identifier) {
57
- const supabase = createClient(apiUrl, serviceRoleKey, {
58
- auth: { autoRefreshToken: false, persistSession: false },
59
- });
60
- const { data, error } = await supabase.auth.admin.customProviders.getProvider(identifier);
61
- if (error && error.status !== 404)
62
- throw error;
63
- return {
64
- exists: !!data,
65
- name: data?.name ?? undefined,
66
- issuer: data?.issuer ?? undefined,
67
- };
68
- }
69
- export async function upsertDevDogsProvider({ apiUrl, serviceRoleKey }, { identifier, name, clientId, clientSecret, issuer, }) {
70
- const supabase = createClient(apiUrl, serviceRoleKey, {
71
- auth: { autoRefreshToken: false, persistSession: false },
72
- });
73
- const { data: existing, error: getError } = await supabase.auth.admin.customProviders.getProvider(identifier);
74
- // 404 just means the provider doesn't exist yet. Any other error is real.
75
- if (getError && getError.status !== 404) {
76
- throw getError;
77
- }
78
- if (existing) {
79
- const { data, error } = await supabase.auth.admin.customProviders.updateProvider(identifier, {
80
- name,
81
- client_id: clientId,
82
- client_secret: clientSecret,
83
- issuer,
84
- scopes: PROVIDER_SCOPES,
85
- enabled: true,
86
- });
87
- if (error)
88
- throw error;
89
- if (!data)
90
- throw new Error("updateProvider returned no data");
91
- return { identifier: data.identifier, issuer: data.issuer ?? issuer };
92
- }
93
- const { data, error } = await supabase.auth.admin.customProviders.createProvider({
94
- provider_type: "oidc",
95
- identifier,
96
- name,
97
- client_id: clientId,
98
- client_secret: clientSecret,
99
- issuer,
100
- scopes: PROVIDER_SCOPES,
101
- enabled: true,
102
- });
103
- if (error)
104
- throw error;
105
- if (!data)
106
- throw new Error("createProvider returned no data");
107
- return { identifier: data.identifier, issuer: data.issuer ?? issuer };
108
- }
@@ -1,93 +0,0 @@
1
- /**
2
- * The device-code fallback for the one-click connect flow (TASK-352): for a
3
- * contributor whose terminal cannot open a browser tab that can complete a
4
- * loopback redirect back to THIS machine — SSH, a Codespace, a dev container,
5
- * or any environment where the loopback listener itself fails to start (see
6
- * `transport.ts`) — this trades a short user code, approved in a browser
7
- * running anywhere, for the same OAuth client credentials `exchange.ts`
8
- * hands back. The server side of this (TASK-352) lives in the DevDogsUGA
9
- * repo, at `apps/platform/src/app/(site)/tools/oauth/device/` — this is the
10
- * fixed wire contract the two sides agreed on:
11
- *
12
- * POST <platform>/tools/oauth/device/code
13
- * { "label", "callback_uri" }
14
- *
15
- * 200 { "device_code", "user_code", "verification_uri",
16
- * "verification_uri_complete", "expires_in", "interval" }
17
- * 400 { "error": "invalid_request", "error_description" }
18
- * 429 { "error": "rate_limited" } (+ Retry-After header)
19
- *
20
- * POST <platform>/tools/oauth/device/token
21
- * { "device_code" }
22
- *
23
- * 200 { "client_id", "client_secret", "issuer" }
24
- * 400 { "error": "authorization_pending" | "slow_down" | "access_denied" |
25
- * "expired_token" | "invalid_grant" | "invalid_request",
26
- * "error_description" }
27
- * 429 { "error": "rate_limited" } (+ Retry-After header)
28
- *
29
- * `requestDeviceCode` mints the code; `pollForToken` polls `.../token` on
30
- * the server's own cadence until it settles one way or the other. Both take
31
- * an injectable `fetchImpl` so tests never touch the network, and
32
- * `pollForToken` additionally takes injectable `sleep`/`now` so tests never
33
- * touch the wall clock either.
34
- */
35
- import type { ExchangeResult } from "./exchange.js";
36
- export type DeviceErrorKind = "invalid_request" | "rate_limited" | "network" | "malformed";
37
- export declare class DeviceError extends Error {
38
- readonly kind: DeviceErrorKind;
39
- readonly retryAfterSeconds?: number | undefined;
40
- constructor(kind: DeviceErrorKind, message: string, retryAfterSeconds?: number | undefined);
41
- }
42
- export interface DeviceCodeResult {
43
- deviceCode: string;
44
- userCode: string;
45
- verificationUri: string;
46
- verificationUriComplete: string;
47
- expiresIn: number;
48
- interval: number;
49
- }
50
- /** The `/token` endpoint's terminal (non-retryable) outcomes, plus `network`/`malformed`. */
51
- export type PollErrorKind = "access_denied" | "expired_token" | "invalid_grant" | "invalid_request" | "network" | "malformed";
52
- export declare class PollError extends Error {
53
- readonly kind: PollErrorKind;
54
- constructor(kind: PollErrorKind, message: string);
55
- }
56
- /**
57
- * Requests a device code. Mirrors `exchange.ts`'s `exchangeCode` in shape:
58
- * every failure is a distinct `DeviceError` kind, a 429 carries
59
- * `Retry-After` when the platform sent one, and a 200 whose body is not
60
- * valid JSON or is missing a required field is `malformed` rather than
61
- * handing the caller `undefined` fields.
62
- */
63
- export declare function requestDeviceCode({ platformUrl, label, callbackUri, fetchImpl, }: {
64
- platformUrl: string;
65
- label: string;
66
- callbackUri: string;
67
- fetchImpl?: typeof fetch;
68
- }): Promise<DeviceCodeResult>;
69
- /**
70
- * Polls `.../tools/oauth/device/token` on the server's cadence until the
71
- * user approves (or denies) the request in their browser, the device code
72
- * expires, or the server rejects it outright.
73
- *
74
- * `authorization_pending` keeps polling at the current interval;
75
- * `slow_down` adds 5 seconds to it and keeps polling (per the wire
76
- * contract); a 429 waits out `Retry-After` (or the current interval, if the
77
- * platform sent none) and keeps polling; every other error is terminal.
78
- * Local expiry (`now() - start >= expiresInSeconds`) is reported the same
79
- * way as the server's own `expired_token` — the caller does not need to
80
- * tell the two apart.
81
- *
82
- * A network failure while polling retries with backoff up to
83
- * `MAX_NETWORK_RETRIES` times before it is reported as `PollError("network")`.
84
- */
85
- export declare function pollForToken({ platformUrl, deviceCode, intervalSeconds, expiresInSeconds, fetchImpl, sleep, now, }: {
86
- platformUrl: string;
87
- deviceCode: string;
88
- intervalSeconds: number;
89
- expiresInSeconds: number;
90
- fetchImpl?: typeof fetch;
91
- sleep?: (ms: number) => Promise<void>;
92
- now?: () => number;
93
- }): Promise<ExchangeResult>;
@@ -1,198 +0,0 @@
1
- export class DeviceError extends Error {
2
- kind;
3
- retryAfterSeconds;
4
- constructor(kind, message, retryAfterSeconds) {
5
- super(message);
6
- this.kind = kind;
7
- this.retryAfterSeconds = retryAfterSeconds;
8
- this.name = "DeviceError";
9
- }
10
- }
11
- export class PollError extends Error {
12
- kind;
13
- constructor(kind, message) {
14
- super(message);
15
- this.kind = kind;
16
- this.name = "PollError";
17
- }
18
- }
19
- function deviceCodeUrl(platformUrl) {
20
- return `${platformUrl.replace(/\/+$/, "")}/tools/oauth/device/code`;
21
- }
22
- function deviceTokenUrl(platformUrl) {
23
- return `${platformUrl.replace(/\/+$/, "")}/tools/oauth/device/token`;
24
- }
25
- function retryAfterSecondsFrom(response) {
26
- const header = response.headers.get("retry-after");
27
- const seconds = header ? Number(header) : undefined;
28
- return Number.isFinite(seconds) ? seconds : undefined;
29
- }
30
- /**
31
- * Requests a device code. Mirrors `exchange.ts`'s `exchangeCode` in shape:
32
- * every failure is a distinct `DeviceError` kind, a 429 carries
33
- * `Retry-After` when the platform sent one, and a 200 whose body is not
34
- * valid JSON or is missing a required field is `malformed` rather than
35
- * handing the caller `undefined` fields.
36
- */
37
- export async function requestDeviceCode({ platformUrl, label, callbackUri, fetchImpl = fetch, }) {
38
- const url = deviceCodeUrl(platformUrl);
39
- let response;
40
- try {
41
- response = await fetchImpl(url, {
42
- method: "POST",
43
- headers: { "content-type": "application/json" },
44
- body: JSON.stringify({ label, callback_uri: callbackUri }),
45
- });
46
- }
47
- catch (err) {
48
- throw new DeviceError("network", `Could not reach ${url}: ${err instanceof Error ? err.message : String(err)}`);
49
- }
50
- if (response.status === 429) {
51
- const retryAfterSeconds = retryAfterSecondsFrom(response);
52
- throw new DeviceError("rate_limited", `${url} is rate-limited` +
53
- (retryAfterSeconds !== undefined
54
- ? ` — retry after ${retryAfterSeconds}s.`
55
- : "."), retryAfterSeconds);
56
- }
57
- let body;
58
- try {
59
- body = await response.json();
60
- }
61
- catch {
62
- throw new DeviceError("malformed", `${url} did not return valid JSON (status ${response.status}).`);
63
- }
64
- if (!response.ok) {
65
- const errBody = (body ?? {});
66
- const description = typeof errBody.error_description === "string"
67
- ? errBody.error_description
68
- : undefined;
69
- const code = typeof errBody.error === "string"
70
- ? errBody.error
71
- : `HTTP ${response.status}`;
72
- throw new DeviceError("invalid_request", description ? `${code}: ${description}` : code);
73
- }
74
- const result = (body ?? {});
75
- if (typeof result.device_code !== "string" ||
76
- typeof result.user_code !== "string" ||
77
- typeof result.verification_uri !== "string" ||
78
- typeof result.verification_uri_complete !== "string" ||
79
- typeof result.expires_in !== "number" ||
80
- typeof result.interval !== "number") {
81
- throw new DeviceError("malformed", `${url} returned 200 but was missing a required device-code field.`);
82
- }
83
- return {
84
- deviceCode: result.device_code,
85
- userCode: result.user_code,
86
- verificationUri: result.verification_uri,
87
- verificationUriComplete: result.verification_uri_complete,
88
- expiresIn: result.expires_in,
89
- interval: result.interval,
90
- };
91
- }
92
- /** Network failures while polling get this many retries before `pollForToken` gives up. */
93
- const MAX_NETWORK_RETRIES = 3;
94
- /** Exponential backoff for a network retry, capped well under the poll interval's own cadence. */
95
- function networkBackoffSeconds(attempt) {
96
- return Math.min(2 ** attempt, 30);
97
- }
98
- /**
99
- * Polls `.../tools/oauth/device/token` on the server's cadence until the
100
- * user approves (or denies) the request in their browser, the device code
101
- * expires, or the server rejects it outright.
102
- *
103
- * `authorization_pending` keeps polling at the current interval;
104
- * `slow_down` adds 5 seconds to it and keeps polling (per the wire
105
- * contract); a 429 waits out `Retry-After` (or the current interval, if the
106
- * platform sent none) and keeps polling; every other error is terminal.
107
- * Local expiry (`now() - start >= expiresInSeconds`) is reported the same
108
- * way as the server's own `expired_token` — the caller does not need to
109
- * tell the two apart.
110
- *
111
- * A network failure while polling retries with backoff up to
112
- * `MAX_NETWORK_RETRIES` times before it is reported as `PollError("network")`.
113
- */
114
- export async function pollForToken({ platformUrl, deviceCode, intervalSeconds, expiresInSeconds, fetchImpl = fetch, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), now = () => Date.now(), }) {
115
- const url = deviceTokenUrl(platformUrl);
116
- const deadline = now() + expiresInSeconds * 1000;
117
- let interval = intervalSeconds;
118
- let networkFailures = 0;
119
- // Set only by a branch that already slept its own wait (a network retry's
120
- // backoff, or a 429's Retry-After) — skips the ordinary top-of-loop
121
- // interval sleep so those waits are not doubled up with it.
122
- let skipIntervalSleep = false;
123
- while (true) {
124
- if (now() >= deadline) {
125
- throw new PollError("expired_token", "The device code expired before it was approved. Run the command again.");
126
- }
127
- if (!skipIntervalSleep)
128
- await sleep(interval * 1000);
129
- skipIntervalSleep = false;
130
- let response;
131
- try {
132
- response = await fetchImpl(url, {
133
- method: "POST",
134
- headers: { "content-type": "application/json" },
135
- body: JSON.stringify({ device_code: deviceCode }),
136
- });
137
- }
138
- catch (err) {
139
- networkFailures += 1;
140
- if (networkFailures > MAX_NETWORK_RETRIES) {
141
- throw new PollError("network", `Could not reach ${url}: ${err instanceof Error ? err.message : String(err)}`);
142
- }
143
- await sleep(networkBackoffSeconds(networkFailures) * 1000);
144
- skipIntervalSleep = true;
145
- continue;
146
- }
147
- networkFailures = 0;
148
- if (response.status === 429) {
149
- const retryAfterSeconds = retryAfterSecondsFrom(response);
150
- await sleep((retryAfterSeconds ?? interval) * 1000);
151
- skipIntervalSleep = true;
152
- continue;
153
- }
154
- let body;
155
- try {
156
- body = await response.json();
157
- }
158
- catch {
159
- throw new PollError("malformed", `${url} did not return valid JSON (status ${response.status}).`);
160
- }
161
- if (response.ok) {
162
- const result = (body ?? {});
163
- if (typeof result.client_id !== "string" ||
164
- typeof result.client_secret !== "string" ||
165
- typeof result.issuer !== "string") {
166
- throw new PollError("malformed", `${url} returned 200 but was missing client_id, client_secret, or issuer.`);
167
- }
168
- return {
169
- clientId: result.client_id,
170
- clientSecret: result.client_secret,
171
- issuer: result.issuer,
172
- };
173
- }
174
- const errBody = (body ?? {});
175
- const errorCode = typeof errBody.error === "string" ? errBody.error : undefined;
176
- const description = typeof errBody.error_description === "string"
177
- ? errBody.error_description
178
- : undefined;
179
- switch (errorCode) {
180
- case "authorization_pending":
181
- continue;
182
- case "slow_down":
183
- interval += 5;
184
- continue;
185
- case "access_denied":
186
- throw new PollError("access_denied", description ?? "Connection declined in the browser.");
187
- case "expired_token":
188
- throw new PollError("expired_token", description ?? "The device code expired. Run the command again.");
189
- case "invalid_grant":
190
- throw new PollError("invalid_grant", description ?? "invalid_grant");
191
- case "invalid_request":
192
- default: {
193
- const code = errorCode ?? `HTTP ${response.status}`;
194
- throw new PollError("invalid_request", description ? `${code}: ${description}` : code);
195
- }
196
- }
197
- }
198
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * OIDC discovery for the DevDogs OAuth server.
3
- *
4
- * The wizard used to build the issuer it registered with Supabase as
5
- * `${baseUrl}/auth/v1` — an assumption, not a fact. Supabase Auth's own
6
- * discovery document at that same base advertises a DIFFERENT issuer (its
7
- * project ref host, e.g. `https://<ref>.supabase.co/auth/v1`, rather than
8
- * the custom domain a caller reaches it through), and Supabase's OIDC
9
- * provider refuses to register a custom provider whose declared issuer
10
- * disagrees with what discovery reports for it. Reading the issuer FROM
11
- * discovery, rather than constructing it, keeps this correct regardless of
12
- * which host the platform's own discovery document happens to advertise —
13
- * including once the platform starts advertising its custom domain instead
14
- * of the underlying project host.
15
- */
16
- /** Thrown when discovery cannot be fetched or does not shape up as expected. */
17
- export declare class DiscoveryError extends Error {
18
- }
19
- /**
20
- * Fetches `${baseUrl}/auth/v1/.well-known/openid-configuration` and returns
21
- * the `issuer` it advertises.
22
- *
23
- * Every failure names the discovery URL it was trying to reach — a network
24
- * failure, a non-2xx response, unparsable JSON, and a missing/non-string
25
- * `issuer` field are each reported distinctly, so the wizard can print
26
- * something more useful than "fetch failed".
27
- */
28
- export declare function fetchIssuer(baseUrl: string): Promise<string>;
@@ -1,54 +0,0 @@
1
- /**
2
- * OIDC discovery for the DevDogs OAuth server.
3
- *
4
- * The wizard used to build the issuer it registered with Supabase as
5
- * `${baseUrl}/auth/v1` — an assumption, not a fact. Supabase Auth's own
6
- * discovery document at that same base advertises a DIFFERENT issuer (its
7
- * project ref host, e.g. `https://<ref>.supabase.co/auth/v1`, rather than
8
- * the custom domain a caller reaches it through), and Supabase's OIDC
9
- * provider refuses to register a custom provider whose declared issuer
10
- * disagrees with what discovery reports for it. Reading the issuer FROM
11
- * discovery, rather than constructing it, keeps this correct regardless of
12
- * which host the platform's own discovery document happens to advertise —
13
- * including once the platform starts advertising its custom domain instead
14
- * of the underlying project host.
15
- */
16
- /** Thrown when discovery cannot be fetched or does not shape up as expected. */
17
- export class DiscoveryError extends Error {
18
- }
19
- /**
20
- * Fetches `${baseUrl}/auth/v1/.well-known/openid-configuration` and returns
21
- * the `issuer` it advertises.
22
- *
23
- * Every failure names the discovery URL it was trying to reach — a network
24
- * failure, a non-2xx response, unparsable JSON, and a missing/non-string
25
- * `issuer` field are each reported distinctly, so the wizard can print
26
- * something more useful than "fetch failed".
27
- */
28
- export async function fetchIssuer(baseUrl) {
29
- const discoveryUrl = `${baseUrl}/auth/v1/.well-known/openid-configuration`;
30
- let response;
31
- try {
32
- response = await fetch(discoveryUrl);
33
- }
34
- catch (err) {
35
- throw new DiscoveryError(`Could not reach ${discoveryUrl}: ${err instanceof Error ? err.message : String(err)}`);
36
- }
37
- if (!response.ok) {
38
- throw new DiscoveryError(`${discoveryUrl} returned ${response.status} ${response.statusText}`);
39
- }
40
- let body;
41
- try {
42
- body = await response.json();
43
- }
44
- catch {
45
- throw new DiscoveryError(`${discoveryUrl} did not return valid JSON`);
46
- }
47
- const issuer = body && typeof body === "object"
48
- ? body.issuer
49
- : undefined;
50
- if (typeof issuer !== "string" || issuer.length === 0) {
51
- throw new DiscoveryError(`${discoveryUrl} response had no "issuer" field`);
52
- }
53
- return issuer;
54
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Writes `values` into `.env.local` in `cwd`, updating any existing
3
- * `KEY=...` lines in place and appending keys that aren't present yet.
4
- * Leaves all other lines (comments, unrelated vars) untouched.
5
- */
6
- export declare function upsertEnvLocal(cwd: string, values: Record<string, string>): void;
@@ -1,35 +0,0 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- /**
4
- * Writes `values` into `.env.local` in `cwd`, updating any existing
5
- * `KEY=...` lines in place and appending keys that aren't present yet.
6
- * Leaves all other lines (comments, unrelated vars) untouched.
7
- */
8
- export function upsertEnvLocal(cwd, values) {
9
- const path = join(cwd, ".env.local");
10
- const existing = existsSync(path) ? readFileSync(path, "utf-8") : "";
11
- const lines = existing.length > 0 ? existing.split("\n") : [];
12
- const remaining = new Map(Object.entries(values));
13
- const updated = lines.map((line) => {
14
- const match = /^([A-Za-z_][A-Za-z0-9_]*)=/.exec(line);
15
- if (!match)
16
- return line;
17
- // The regex has exactly one capture group, so a match always fills it;
18
- // noUncheckedIndexedAccess can't see that, hence the explicit guard.
19
- const key = match[1];
20
- if (key === undefined)
21
- return line;
22
- const value = remaining.get(key);
23
- if (value === undefined)
24
- return line;
25
- remaining.delete(key);
26
- return `${key}=${formatValue(value)}`;
27
- });
28
- for (const [key, value] of remaining) {
29
- updated.push(`${key}=${formatValue(value)}`);
30
- }
31
- writeFileSync(path, `${updated.join("\n").replace(/\n*$/, "")}\n`);
32
- }
33
- function formatValue(value) {
34
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
35
- }
@@ -1,43 +0,0 @@
1
- /**
2
- * The token exchange half of the one-click connect flow: trades the
3
- * authorization `code` the loopback listener caught, plus the PKCE
4
- * verifier that matches the challenge sent up front, for the OAuth client
5
- * credentials the platform minted — see `commands.ts`'s `oauth` node and
6
- * this repo's launch instructions for the full wire contract this speaks:
7
- *
8
- * POST <platform>/tools/oauth/connect/exchange
9
- * { "code", "code_verifier" }
10
- *
11
- * 200 { "client_id", "client_secret", "issuer" }
12
- * 400 { "error": "invalid_request" | "invalid_grant", "error_description" }
13
- * 429 { "error": "rate_limited" } (+ Retry-After header)
14
- */
15
- export type ExchangeErrorKind = "invalid_request" | "invalid_grant" | "rate_limited" | "network" | "malformed";
16
- export declare class ExchangeError extends Error {
17
- readonly kind: ExchangeErrorKind;
18
- readonly retryAfterSeconds?: number | undefined;
19
- constructor(kind: ExchangeErrorKind, message: string, retryAfterSeconds?: number | undefined);
20
- }
21
- export interface ExchangeResult {
22
- clientId: string;
23
- clientSecret: string;
24
- issuer: string;
25
- }
26
- /**
27
- * Exchanges `code` + `codeVerifier` for OAuth client credentials.
28
- *
29
- * Every failure is a distinct `ExchangeError` kind so the caller (the
30
- * wizard) can print something specific rather than a bare "request
31
- * failed": a network failure names the URL and underlying error, a 429
32
- * carries `Retry-After` when the platform sent one, a 400 distinguishes
33
- * `invalid_grant` (the code was wrong, expired, or already used — the
34
- * ordinary "try the flow again" case) from any other `invalid_request`, and
35
- * a 200 with a body that is not valid JSON or is missing a required field
36
- * is reported as `malformed` rather than silently returning `undefined`
37
- * fields to the caller.
38
- */
39
- export declare function exchangeCode({ platformUrl, code, codeVerifier, }: {
40
- platformUrl: string;
41
- code: string;
42
- codeVerifier: string;
43
- }): Promise<ExchangeResult>;
@@ -1,93 +0,0 @@
1
- /**
2
- * The token exchange half of the one-click connect flow: trades the
3
- * authorization `code` the loopback listener caught, plus the PKCE
4
- * verifier that matches the challenge sent up front, for the OAuth client
5
- * credentials the platform minted — see `commands.ts`'s `oauth` node and
6
- * this repo's launch instructions for the full wire contract this speaks:
7
- *
8
- * POST <platform>/tools/oauth/connect/exchange
9
- * { "code", "code_verifier" }
10
- *
11
- * 200 { "client_id", "client_secret", "issuer" }
12
- * 400 { "error": "invalid_request" | "invalid_grant", "error_description" }
13
- * 429 { "error": "rate_limited" } (+ Retry-After header)
14
- */
15
- export class ExchangeError extends Error {
16
- kind;
17
- retryAfterSeconds;
18
- constructor(kind, message, retryAfterSeconds) {
19
- super(message);
20
- this.kind = kind;
21
- this.retryAfterSeconds = retryAfterSeconds;
22
- this.name = "ExchangeError";
23
- }
24
- }
25
- function exchangeUrl(platformUrl) {
26
- return `${platformUrl.replace(/\/+$/, "")}/tools/oauth/connect/exchange`;
27
- }
28
- /**
29
- * Exchanges `code` + `codeVerifier` for OAuth client credentials.
30
- *
31
- * Every failure is a distinct `ExchangeError` kind so the caller (the
32
- * wizard) can print something specific rather than a bare "request
33
- * failed": a network failure names the URL and underlying error, a 429
34
- * carries `Retry-After` when the platform sent one, a 400 distinguishes
35
- * `invalid_grant` (the code was wrong, expired, or already used — the
36
- * ordinary "try the flow again" case) from any other `invalid_request`, and
37
- * a 200 with a body that is not valid JSON or is missing a required field
38
- * is reported as `malformed` rather than silently returning `undefined`
39
- * fields to the caller.
40
- */
41
- export async function exchangeCode({ platformUrl, code, codeVerifier, }) {
42
- const url = exchangeUrl(platformUrl);
43
- let response;
44
- try {
45
- response = await fetch(url, {
46
- method: "POST",
47
- headers: { "content-type": "application/json" },
48
- body: JSON.stringify({ code, code_verifier: codeVerifier }),
49
- });
50
- }
51
- catch (err) {
52
- throw new ExchangeError("network", `Could not reach ${url}: ${err instanceof Error ? err.message : String(err)}`);
53
- }
54
- if (response.status === 429) {
55
- const retryAfterHeader = response.headers.get("retry-after");
56
- const retryAfterSeconds = retryAfterHeader
57
- ? Number(retryAfterHeader)
58
- : undefined;
59
- throw new ExchangeError("rate_limited", `${url} is rate-limited` +
60
- (Number.isFinite(retryAfterSeconds)
61
- ? ` — retry after ${retryAfterSeconds}s.`
62
- : "."), Number.isFinite(retryAfterSeconds) ? retryAfterSeconds : undefined);
63
- }
64
- let body;
65
- try {
66
- body = await response.json();
67
- }
68
- catch {
69
- throw new ExchangeError("malformed", `${url} did not return valid JSON (status ${response.status}).`);
70
- }
71
- if (!response.ok) {
72
- const errBody = (body ?? {});
73
- const kind = errBody.error === "invalid_grant" ? "invalid_grant" : "invalid_request";
74
- const description = typeof errBody.error_description === "string"
75
- ? errBody.error_description
76
- : undefined;
77
- const code = typeof errBody.error === "string"
78
- ? errBody.error
79
- : `HTTP ${response.status}`;
80
- throw new ExchangeError(kind, description ? `${code}: ${description}` : code);
81
- }
82
- const result = (body ?? {});
83
- if (typeof result.client_id !== "string" ||
84
- typeof result.client_secret !== "string" ||
85
- typeof result.issuer !== "string") {
86
- throw new ExchangeError("malformed", `${url} returned 200 but was missing client_id, client_secret, or issuer.`);
87
- }
88
- return {
89
- clientId: result.client_id,
90
- clientSecret: result.client_secret,
91
- issuer: result.issuer,
92
- };
93
- }
@@ -1 +0,0 @@
1
- export declare function defaultLabel(cwd: string): string;