@devdogsuga/devtools 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/README.md +44 -0
  2. package/bin/devtools-ci-bare.mjs +48 -0
  3. package/bin/devtools-ci.mjs +17 -0
  4. package/bin/devtools.mjs +27 -0
  5. package/dist/args.d.ts +26 -0
  6. package/dist/args.js +78 -0
  7. package/dist/bws/bw.d.ts +9 -0
  8. package/dist/bws/bw.js +49 -0
  9. package/dist/bws/client.d.ts +43 -0
  10. package/dist/bws/client.js +363 -0
  11. package/dist/bws/environments.d.ts +96 -0
  12. package/dist/bws/environments.js +111 -0
  13. package/dist/bws/pace.d.ts +27 -0
  14. package/dist/bws/pace.js +43 -0
  15. package/dist/bws/retry.d.ts +12 -0
  16. package/dist/bws/retry.js +39 -0
  17. package/dist/bws/store.d.ts +1 -0
  18. package/dist/bws/store.js +32 -0
  19. package/dist/bws/token.d.ts +31 -0
  20. package/dist/bws/token.js +88 -0
  21. package/dist/bws/vault.d.ts +29 -0
  22. package/dist/bws/vault.js +200 -0
  23. package/dist/catalog.d.ts +55 -0
  24. package/dist/catalog.js +81 -0
  25. package/dist/cf/commands.d.ts +1 -0
  26. package/dist/cf/commands.js +182 -0
  27. package/dist/cf/local-env.d.ts +44 -0
  28. package/dist/cf/local-env.js +148 -0
  29. package/dist/ci.d.ts +1 -0
  30. package/dist/ci.js +319 -0
  31. package/dist/cli.d.ts +11 -0
  32. package/dist/cli.js +1077 -0
  33. package/dist/commands.d.ts +255 -0
  34. package/dist/commands.js +1279 -0
  35. package/dist/completions.d.ts +4 -0
  36. package/dist/completions.js +110 -0
  37. package/dist/cron/commands.d.ts +47 -0
  38. package/dist/cron/commands.js +372 -0
  39. package/dist/cron/discovery.d.ts +55 -0
  40. package/dist/cron/discovery.js +85 -0
  41. package/dist/cron/schema.d.ts +45 -0
  42. package/dist/cron/schema.js +35 -0
  43. package/dist/db/config-push.d.ts +8 -0
  44. package/dist/db/config-push.js +11 -0
  45. package/dist/db/connection.d.ts +79 -0
  46. package/dist/db/connection.js +114 -0
  47. package/dist/db/exec.d.ts +1 -0
  48. package/dist/db/exec.js +11 -0
  49. package/dist/db/generate-migration.d.ts +1 -0
  50. package/dist/db/generate-migration.js +67 -0
  51. package/dist/db/generate-types.d.ts +1 -0
  52. package/dist/db/generate-types.js +4 -0
  53. package/dist/db/introspect.d.ts +1 -0
  54. package/dist/db/introspect.js +113 -0
  55. package/dist/db/local-env.d.ts +65 -0
  56. package/dist/db/local-env.js +23 -0
  57. package/dist/db/new-migration.d.ts +1 -0
  58. package/dist/db/new-migration.js +8 -0
  59. package/dist/db/run.d.ts +70 -0
  60. package/dist/db/run.js +158 -0
  61. package/dist/db/seed-buckets.d.ts +2 -0
  62. package/dist/db/seed-buckets.js +4 -0
  63. package/dist/db/seed-production.d.ts +8 -0
  64. package/dist/db/seed-production.js +73 -0
  65. package/dist/db/seed-roles.d.ts +7 -0
  66. package/dist/db/seed-roles.js +31 -0
  67. package/dist/db/session-refresh.d.ts +43 -0
  68. package/dist/db/session-refresh.js +40 -0
  69. package/dist/deploy/migrations.d.ts +20 -0
  70. package/dist/deploy/migrations.js +68 -0
  71. package/dist/deploy/orphans.d.ts +20 -0
  72. package/dist/deploy/orphans.js +177 -0
  73. package/dist/deploy/preflight.d.ts +53 -0
  74. package/dist/deploy/preflight.js +189 -0
  75. package/dist/deploy/report.d.ts +26 -0
  76. package/dist/deploy/report.js +70 -0
  77. package/dist/deploy/require-planner.d.ts +2 -0
  78. package/dist/deploy/require-planner.js +60 -0
  79. package/dist/deploy/require-token.d.ts +15 -0
  80. package/dist/deploy/require-token.js +78 -0
  81. package/dist/deploy/secrets-file.d.ts +17 -0
  82. package/dist/deploy/secrets-file.js +167 -0
  83. package/dist/deploy/write-env.d.ts +40 -0
  84. package/dist/deploy/write-env.js +393 -0
  85. package/dist/docs/index-pages.d.ts +36 -0
  86. package/dist/docs/index-pages.js +184 -0
  87. package/dist/doctor.d.ts +62 -0
  88. package/dist/doctor.js +225 -0
  89. package/dist/emails/commands.d.ts +15 -0
  90. package/dist/emails/commands.js +130 -0
  91. package/dist/emails/email-types.d.ts +13 -0
  92. package/dist/emails/email-types.js +1 -0
  93. package/dist/emails/fixtures.d.ts +20 -0
  94. package/dist/emails/fixtures.js +20 -0
  95. package/dist/env/audit.d.ts +206 -0
  96. package/dist/env/audit.js +436 -0
  97. package/dist/env/cloudflare.d.ts +21 -0
  98. package/dist/env/cloudflare.js +61 -0
  99. package/dist/env/commands.d.ts +99 -0
  100. package/dist/env/commands.js +612 -0
  101. package/dist/env/discovery.d.ts +15 -0
  102. package/dist/env/discovery.js +226 -0
  103. package/dist/env/document.d.ts +119 -0
  104. package/dist/env/document.js +323 -0
  105. package/dist/env/example.d.ts +68 -0
  106. package/dist/env/example.js +603 -0
  107. package/dist/env/selection.d.ts +105 -0
  108. package/dist/env/selection.js +226 -0
  109. package/dist/environment.d.ts +92 -0
  110. package/dist/environment.js +206 -0
  111. package/dist/fingerprint.d.ts +10 -0
  112. package/dist/fingerprint.js +18 -0
  113. package/dist/gen/campus-map.d.ts +1 -0
  114. package/dist/gen/campus-map.js +473 -0
  115. package/dist/gen/commands.d.ts +1 -0
  116. package/dist/gen/commands.js +25 -0
  117. package/dist/gen/hypno.d.ts +1 -0
  118. package/dist/gen/hypno.js +134 -0
  119. package/dist/gh/client.d.ts +80 -0
  120. package/dist/gh/client.js +258 -0
  121. package/dist/gh/environments.d.ts +83 -0
  122. package/dist/gh/environments.js +178 -0
  123. package/dist/gh/rulesets/actors.d.ts +14 -0
  124. package/dist/gh/rulesets/actors.js +68 -0
  125. package/dist/gh/rulesets/api.d.ts +15 -0
  126. package/dist/gh/rulesets/api.js +99 -0
  127. package/dist/gh/rulesets/commands.d.ts +3 -0
  128. package/dist/gh/rulesets/commands.js +203 -0
  129. package/dist/gh/rulesets/desired.d.ts +112 -0
  130. package/dist/gh/rulesets/desired.js +247 -0
  131. package/dist/gh/rulesets/diff.d.ts +65 -0
  132. package/dist/gh/rulesets/diff.js +134 -0
  133. package/dist/gh/rulesets/types.d.ts +76 -0
  134. package/dist/gh/rulesets/types.js +13 -0
  135. package/dist/gh/settings/api.d.ts +40 -0
  136. package/dist/gh/settings/api.js +172 -0
  137. package/dist/gh/settings/commands.d.ts +1 -0
  138. package/dist/gh/settings/commands.js +245 -0
  139. package/dist/gh/settings/desired.d.ts +53 -0
  140. package/dist/gh/settings/desired.js +39 -0
  141. package/dist/gh/settings/diff.d.ts +57 -0
  142. package/dist/gh/settings/diff.js +156 -0
  143. package/dist/gh/settings/types.d.ts +92 -0
  144. package/dist/gh/settings/types.js +11 -0
  145. package/dist/gh/settings/workflows.d.ts +41 -0
  146. package/dist/gh/settings/workflows.js +124 -0
  147. package/dist/grantRoot.d.ts +42 -0
  148. package/dist/grantRoot.js +89 -0
  149. package/dist/help.d.ts +18 -0
  150. package/dist/help.js +169 -0
  151. package/dist/images/commands.d.ts +35 -0
  152. package/dist/images/commands.js +233 -0
  153. package/dist/images/events.d.ts +36 -0
  154. package/dist/images/events.js +152 -0
  155. package/dist/images/graphics.d.ts +70 -0
  156. package/dist/images/graphics.js +233 -0
  157. package/dist/images/open-graph-types.d.ts +75 -0
  158. package/dist/images/open-graph-types.js +1 -0
  159. package/dist/images/prompts.d.ts +34 -0
  160. package/dist/images/prompts.js +100 -0
  161. package/dist/images/render.d.ts +21 -0
  162. package/dist/images/render.js +57 -0
  163. package/dist/images/select.d.ts +64 -0
  164. package/dist/images/select.js +96 -0
  165. package/dist/instance.d.ts +67 -0
  166. package/dist/instance.js +129 -0
  167. package/dist/invocation.d.ts +49 -0
  168. package/dist/invocation.js +78 -0
  169. package/dist/launch-ci.d.ts +1 -0
  170. package/dist/launch-ci.js +86 -0
  171. package/dist/launch.d.ts +29 -0
  172. package/dist/launch.js +371 -0
  173. package/dist/menu.d.ts +31 -0
  174. package/dist/menu.js +277 -0
  175. package/dist/newsletter/commands.d.ts +16 -0
  176. package/dist/newsletter/commands.js +311 -0
  177. package/dist/newsletter/imap.d.ts +17 -0
  178. package/dist/newsletter/imap.js +127 -0
  179. package/dist/newsletter/loopback.d.ts +24 -0
  180. package/dist/newsletter/loopback.js +100 -0
  181. package/dist/newsletter/oauth.d.ts +26 -0
  182. package/dist/newsletter/oauth.js +130 -0
  183. package/dist/newsletter/smtp.d.ts +23 -0
  184. package/dist/newsletter/smtp.js +133 -0
  185. package/dist/oauth/browser.d.ts +1 -0
  186. package/dist/oauth/browser.js +13 -0
  187. package/dist/oauth/config.d.ts +24 -0
  188. package/dist/oauth/config.js +24 -0
  189. package/dist/oauth/connect-url.d.ts +17 -0
  190. package/dist/oauth/connect-url.js +23 -0
  191. package/dist/oauth/db.d.ts +37 -0
  192. package/dist/oauth/db.js +108 -0
  193. package/dist/oauth/device.d.ts +93 -0
  194. package/dist/oauth/device.js +190 -0
  195. package/dist/oauth/discovery.d.ts +28 -0
  196. package/dist/oauth/discovery.js +54 -0
  197. package/dist/oauth/env-file.d.ts +6 -0
  198. package/dist/oauth/env-file.js +35 -0
  199. package/dist/oauth/exchange.d.ts +43 -0
  200. package/dist/oauth/exchange.js +87 -0
  201. package/dist/oauth/label.d.ts +1 -0
  202. package/dist/oauth/label.js +25 -0
  203. package/dist/oauth/loopback.d.ts +52 -0
  204. package/dist/oauth/loopback.js +162 -0
  205. package/dist/oauth/pkce.d.ts +6 -0
  206. package/dist/oauth/pkce.js +34 -0
  207. package/dist/oauth/target.d.ts +54 -0
  208. package/dist/oauth/target.js +102 -0
  209. package/dist/oauth/transport.d.ts +41 -0
  210. package/dist/oauth/transport.js +32 -0
  211. package/dist/oauth/wizard.d.ts +33 -0
  212. package/dist/oauth/wizard.js +571 -0
  213. package/dist/pick.d.ts +6 -0
  214. package/dist/pick.js +64 -0
  215. package/dist/planner/checks.d.ts +26 -0
  216. package/dist/planner/checks.js +104 -0
  217. package/dist/planner/commands.d.ts +10 -0
  218. package/dist/planner/commands.js +367 -0
  219. package/dist/planner/db.d.ts +15 -0
  220. package/dist/planner/db.js +33 -0
  221. package/dist/planner/role.d.ts +33 -0
  222. package/dist/planner/role.js +68 -0
  223. package/dist/repo/peers.d.ts +24 -0
  224. package/dist/repo/peers.js +114 -0
  225. package/dist/repo/preflight.d.ts +109 -0
  226. package/dist/repo/preflight.js +266 -0
  227. package/dist/repo/resolve.d.ts +41 -0
  228. package/dist/repo/resolve.js +152 -0
  229. package/dist/repo/root.d.ts +23 -0
  230. package/dist/repo/root.js +91 -0
  231. package/dist/repo/source.d.ts +14 -0
  232. package/dist/repo/source.js +54 -0
  233. package/dist/repo/tsx-loader.d.ts +6 -0
  234. package/dist/repo/tsx-loader.js +41 -0
  235. package/dist/run/pick.d.ts +60 -0
  236. package/dist/run/pick.js +559 -0
  237. package/dist/setup.d.ts +1 -0
  238. package/dist/setup.js +152 -0
  239. package/dist/stack.d.ts +48 -0
  240. package/dist/stack.js +290 -0
  241. package/dist/telemetry.d.ts +34 -0
  242. package/dist/telemetry.js +93 -0
  243. package/dist/tier.d.ts +40 -0
  244. package/dist/tier.js +93 -0
  245. package/dist/tsconfig.typecheck.tsbuildinfo +1 -0
  246. package/dist/ui.d.ts +25 -0
  247. package/dist/ui.js +51 -0
  248. package/dist/workers.d.ts +5 -0
  249. package/dist/workers.js +35 -0
  250. package/dist/workflows/commands.d.ts +44 -0
  251. package/dist/workflows/commands.js +668 -0
  252. package/env.ts +158 -0
  253. package/package.json +80 -0
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Zod schemas for the two cron-trigger exports an app's
3
+ * `cloudflare/scheduled.ts` may declare: `CRON_ROUTES` (a schedule that fetches
4
+ * one or more of the app's own routes) and the optional `WORKFLOW_CRONS`
5
+ * (audit metadata for a native Cloudflare Workflow schedule).
6
+ *
7
+ * devtools enforces both at its import boundary (see `discovery.ts`): an app
8
+ * that drifts from the shape fails the audit and a conformance test, rather
9
+ * than reading as an empty (silently unaudited) map.
10
+ *
11
+ * Invariants, each a test rather than a hope:
12
+ * - Every route starts `/` and lives on the app's own origin.
13
+ * - `routes` is non-empty: a key that fires nothing is a config bug.
14
+ * - `label` is non-blank: the audit's English description; never empty.
15
+ * - Keyed by the cron expression exactly as wrangler carries it, so
16
+ * reconciliation is a set comparison, never a parse.
17
+ */
18
+ import { z } from "zod";
19
+ declare const CronEntry: z.ZodObject<{
20
+ routes: z.ZodArray<z.ZodString>;
21
+ label: z.ZodString;
22
+ }, z.core.$strip>;
23
+ export declare const CronRoutes: z.ZodRecord<z.ZodString, z.ZodObject<{
24
+ routes: z.ZodArray<z.ZodString>;
25
+ label: z.ZodString;
26
+ }, z.core.$strip>>;
27
+ export type CronRoutes = z.infer<typeof CronRoutes>;
28
+ export type CronEntry = z.infer<typeof CronEntry>;
29
+ /**
30
+ * A native workflow schedule. `binding` names the Workflows binding (e.g.
31
+ * `SCRAPE_WORKFLOW`); `cron list` reconciles the expression against that
32
+ * binding's `workflows[].schedules` in the tier's `wrangler.jsonc`. `label` is
33
+ * the audit's English description, same contract as routes.
34
+ */
35
+ declare const WorkflowCronEntry: z.ZodObject<{
36
+ binding: z.ZodString;
37
+ label: z.ZodString;
38
+ }, z.core.$strip>;
39
+ export declare const WorkflowCrons: z.ZodRecord<z.ZodString, z.ZodObject<{
40
+ binding: z.ZodString;
41
+ label: z.ZodString;
42
+ }, z.core.$strip>>;
43
+ export type WorkflowCrons = z.infer<typeof WorkflowCrons>;
44
+ export type WorkflowCronEntry = z.infer<typeof WorkflowCronEntry>;
45
+ export {};
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Zod schemas for the two cron-trigger exports an app's
3
+ * `cloudflare/scheduled.ts` may declare: `CRON_ROUTES` (a schedule that fetches
4
+ * one or more of the app's own routes) and the optional `WORKFLOW_CRONS`
5
+ * (audit metadata for a native Cloudflare Workflow schedule).
6
+ *
7
+ * devtools enforces both at its import boundary (see `discovery.ts`): an app
8
+ * that drifts from the shape fails the audit and a conformance test, rather
9
+ * than reading as an empty (silently unaudited) map.
10
+ *
11
+ * Invariants, each a test rather than a hope:
12
+ * - Every route starts `/` and lives on the app's own origin.
13
+ * - `routes` is non-empty: a key that fires nothing is a config bug.
14
+ * - `label` is non-blank: the audit's English description; never empty.
15
+ * - Keyed by the cron expression exactly as wrangler carries it, so
16
+ * reconciliation is a set comparison, never a parse.
17
+ */
18
+ import { z } from "zod";
19
+ const CronRoute = z.string().startsWith("/");
20
+ const CronEntry = z.object({
21
+ routes: z.array(CronRoute).min(1),
22
+ label: z.string().min(1),
23
+ });
24
+ export const CronRoutes = z.record(z.string(), CronEntry);
25
+ /**
26
+ * A native workflow schedule. `binding` names the Workflows binding (e.g.
27
+ * `SCRAPE_WORKFLOW`); `cron list` reconciles the expression against that
28
+ * binding's `workflows[].schedules` in the tier's `wrangler.jsonc`. `label` is
29
+ * the audit's English description, same contract as routes.
30
+ */
31
+ const WorkflowCronEntry = z.object({
32
+ binding: z.string().min(1),
33
+ label: z.string().min(1),
34
+ });
35
+ export const WorkflowCrons = z.record(z.string(), WorkflowCronEntry);
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `config.toml` lives on a hosted project only — there is no local
3
+ * `--project-ref` to push to — so this takes the ref directly rather than a
4
+ * `Target`. The caller (`cli.ts`) resolves it from the tier, exactly like
5
+ * every other remote db operation now does, instead of letting the supabase
6
+ * CLI fall back to whatever project it has `--linked`.
7
+ */
8
+ export declare function runConfigPush(projectRef: string): Promise<number>;
@@ -0,0 +1,11 @@
1
+ import { supabase } from "./run.js";
2
+ /**
3
+ * `config.toml` lives on a hosted project only — there is no local
4
+ * `--project-ref` to push to — so this takes the ref directly rather than a
5
+ * `Target`. The caller (`cli.ts`) resolves it from the tier, exactly like
6
+ * every other remote db operation now does, instead of letting the supabase
7
+ * CLI fall back to whatever project it has `--linked`.
8
+ */
9
+ export async function runConfigPush(projectRef) {
10
+ return supabase("config", "push", "--project-ref", projectRef);
11
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Resolves the database a `db` command acts on — from the SESSION, never
3
+ * from a flag.
4
+ *
5
+ * There used to be two connection mechanisms here (`db/remote.ts`'s
6
+ * `--target remote` tier resolver, and `db/local-env.ts`'s "whatever this
7
+ * process entered"), joined by a `--target local|remote` flag that asked a
8
+ * question the launcher had already answered: `launch.ts` settles a session
9
+ * (`--tier development:local|development:remote|staging|production`, or the
10
+ * picker) and enters its env BEFORE any command dispatches, so
11
+ * `process.env.DB_URL` already names the one database this session means.
12
+ * This module reads that — the same `DB_URL` every app and script uses —
13
+ * and every mechanism-one caller now goes through it. `resolveLocalToolingEnv`
14
+ * remains for the two commands that hand a whole ENVIRONMENT to a child
15
+ * process rather than needing a connection of their own.
16
+ *
17
+ * What it adds over a raw `process.env.DB_URL` read is the two sanity
18
+ * guards a session can still violate:
19
+ *
20
+ * * development meaning the LOCAL stack, but `DB_URL` pointing somewhere
21
+ * that is not loopback — the stack went down after launch (the overlay
22
+ * is applied at entry; see `load.ts`'s probe table) or the degraded
23
+ * `db`-exempt entry ran without it. Refused with `db start`
24
+ * troubleshooting rather than letting `.env`'s hosted `DB_URL` answer
25
+ * for a command that asked for Docker.
26
+ * * a DEPLOYED tier whose `DB_URL` points AT loopback — the signature of
27
+ * inherited development values winning under `override: false` (see
28
+ * `launch.ts`). Refused rather than "migrating staging" against a local
29
+ * container.
30
+ *
31
+ * Reports the reason on stderr and returns `null` on every failure path —
32
+ * never throws for an expected failure — so a caller can stop without
33
+ * printing a second explanation of its own.
34
+ *
35
+ * ## `db connect` — plausibly vestigial
36
+ *
37
+ * `connectRemoteProject` (`stack.ts`) still runs `supabase link
38
+ * --project-ref <ref>`, the pre-session way of naming "the remote project",
39
+ * remembered by the supabase CLI on disk and read by nothing in this CLI:
40
+ * every operation here passes `--db-url`/`--project-ref` explicitly. It may
41
+ * still matter to someone driving the bare `supabase` CLI by hand, so
42
+ * removing it is a separate decision from this refactor.
43
+ */
44
+ import type { DeployEnvironment } from "@devdogsuga/env";
45
+ import type { DevDatabase } from "@devdogsuga/env/load";
46
+ export declare function isLocalDbUrl(url: string): boolean;
47
+ /** The hostname for messages — never the URL itself, which carries the
48
+ * password. */
49
+ export declare function dbHost(url: string): string;
50
+ /** What the session resolved to: the one database every `db` data command
51
+ * in this process acts on. `projectRef` is only validated where it is
52
+ * actually needed (`seed buckets`, `config push`). */
53
+ export interface DbConnection {
54
+ tier: DeployEnvironment;
55
+ /** The session's development qualifier, when one was entered. */
56
+ devDatabase: DevDatabase | undefined;
57
+ dbUrl: string;
58
+ projectRef: string | undefined;
59
+ }
60
+ /** Whether this connection is the Docker stack on this machine — the one
61
+ * database whose erasure is routine rather than gated sternly. */
62
+ export declare function isLocalConnection(conn: DbConnection): boolean;
63
+ /**
64
+ * How gates and reports name the target. Deliberately tier-and-host only —
65
+ * never the DB_URL, which carries the password — and "remote development"
66
+ * is spelled out because `staging`/`production` are also remote: the whole
67
+ * reason the session vocabulary qualifies development.
68
+ */
69
+ export declare function describeDbTarget(conn: DbConnection): string;
70
+ export interface ResolveDbConnectionOptions {
71
+ /** Stderr prefix, e.g. "devtools db reset". */
72
+ label?: string;
73
+ /** Injectable for tests; defaults to `process.env`. */
74
+ env?: NodeJS.ProcessEnv;
75
+ /** Suppress the stderr reason — for callers like `db status` that treat
76
+ * "no resolvable database" as an answer, not a failure. */
77
+ quiet?: boolean;
78
+ }
79
+ export declare function resolveDbConnection(opts?: ResolveDbConnectionOptions): Promise<DbConnection | null>;
@@ -0,0 +1,114 @@
1
+ import { loadEnv, loadEnvLoad } from "../repo/peers.js";
2
+ /**
3
+ * Hostnames that mean "the Docker stack on this machine". The same list
4
+ * `docs/index-pages.ts` keeps for its delete-acknowledgment; duplicated
5
+ * because that module answers a different question ("may I delete rows
6
+ * here without --target remote?") and coupling the two would let a change
7
+ * for one silently reshape the other.
8
+ */
9
+ const LOCAL_DB_HOSTS = [
10
+ "localhost",
11
+ "127.0.0.1",
12
+ "[::1]",
13
+ "host.docker.internal",
14
+ ];
15
+ export function isLocalDbUrl(url) {
16
+ try {
17
+ return LOCAL_DB_HOSTS.includes(new URL(url).hostname);
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ /** The hostname for messages — never the URL itself, which carries the
24
+ * password. */
25
+ export function dbHost(url) {
26
+ try {
27
+ return new URL(url).hostname || "unknown host";
28
+ }
29
+ catch {
30
+ return "unknown host";
31
+ }
32
+ }
33
+ /** Whether this connection is the Docker stack on this machine — the one
34
+ * database whose erasure is routine rather than gated sternly. */
35
+ export function isLocalConnection(conn) {
36
+ return conn.tier === "development" && isLocalDbUrl(conn.dbUrl);
37
+ }
38
+ /**
39
+ * How gates and reports name the target. Deliberately tier-and-host only —
40
+ * never the DB_URL, which carries the password — and "remote development"
41
+ * is spelled out because `staging`/`production` are also remote: the whole
42
+ * reason the session vocabulary qualifies development.
43
+ */
44
+ export function describeDbTarget(conn) {
45
+ if (conn.tier !== "development")
46
+ return `the ${conn.tier} database`;
47
+ if (isLocalDbUrl(conn.dbUrl))
48
+ return "your local database";
49
+ return `the remote development database at ${dbHost(conn.dbUrl)}`;
50
+ }
51
+ export async function resolveDbConnection(opts = {}) {
52
+ const { DEPLOY_ENVIRONMENTS, fileFor, isDeployEnvironment } = await loadEnv();
53
+ const { DEV_DB_ENV, isDevDatabase } = await loadEnvLoad();
54
+ const env = opts.env ?? process.env;
55
+ const label = opts.label ?? "devtools db";
56
+ const report = (message) => {
57
+ if (!opts.quiet)
58
+ process.stderr.write(message);
59
+ };
60
+ const rawTier = env.DEPLOY_ENV;
61
+ let tier;
62
+ if (rawTier === undefined || rawTier === "") {
63
+ tier = "development";
64
+ }
65
+ else if (isDeployEnvironment(rawTier)) {
66
+ tier = rawTier;
67
+ }
68
+ else {
69
+ report(`${label}: DEPLOY_ENV="${rawTier}" is not one of ${DEPLOY_ENVIRONMENTS.join(", ")}.\n`);
70
+ return null;
71
+ }
72
+ let devDatabase;
73
+ const rawDevDb = env[DEV_DB_ENV];
74
+ if (tier === "development" && rawDevDb !== undefined && rawDevDb !== "") {
75
+ if (!isDevDatabase(rawDevDb)) {
76
+ report(`${label}: ${DEV_DB_ENV}="${rawDevDb}" is not one of: local, remote.\n`);
77
+ return null;
78
+ }
79
+ devDatabase = rawDevDb;
80
+ }
81
+ const dbUrl = env.DB_URL;
82
+ if (dbUrl === undefined || dbUrl === "") {
83
+ if (tier !== "development") {
84
+ report(`${label}: ${fileFor(tier)} has no DB_URL.\n`);
85
+ }
86
+ else if (devDatabase === "remote") {
87
+ report(`${label}: this session targets the remote development database, but .env has no DB_URL.\n`);
88
+ }
89
+ else {
90
+ report(`${label}: no DB_URL in this session — the local Supabase stack is ` +
91
+ "not running. Run `pnpm devtools db start`, or relaunch with " +
92
+ "`--tier development:remote` (or staging/production) to act on a " +
93
+ "hosted database.\n");
94
+ }
95
+ return null;
96
+ }
97
+ if (tier === "development" &&
98
+ devDatabase !== "remote" &&
99
+ !isLocalDbUrl(dbUrl)) {
100
+ report(`${label}: DB_URL points at ${dbHost(dbUrl)}, not the local stack — ` +
101
+ "it is offline (or .env.generated is stale). Run `pnpm devtools db " +
102
+ "start`, or relaunch with `--tier development:remote` if you mean " +
103
+ "that database.\n");
104
+ return null;
105
+ }
106
+ if (tier !== "development" && isLocalDbUrl(dbUrl)) {
107
+ report(`${label}: the session tier is ${tier} but DB_URL points at ` +
108
+ `${dbHost(dbUrl)} — a local address, the signature of inherited ` +
109
+ "development values. Relaunch with `pnpm devtools --tier " +
110
+ `${tier}\` from a clean shell.\n`);
111
+ return null;
112
+ }
113
+ return { tier, devDatabase, dbUrl, projectRef: env.PROJECT_REF };
114
+ }
@@ -0,0 +1 @@
1
+ export declare function runDbExec(args: string[]): Promise<number>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `db exec -- <args…>` — the escape hatch.
3
+ *
4
+ * Everything after `--` passes straight to the Supabase CLI, unmodified. Same
5
+ * stance as `bw` and `cf exec`: the wrapped CLI owns its own targeting and its
6
+ * own flags, so this declares no subcommands and does no parsing of its own.
7
+ */
8
+ import { supabase } from "./run.js";
9
+ export async function runDbExec(args) {
10
+ return supabase(...args);
11
+ }
@@ -0,0 +1 @@
1
+ export declare function runGenerateMigration(appSlug?: string): Promise<number>;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * `db migration generate [--app <slug>]` — draft a migration from an app's
3
+ * Drizzle schema drift.
4
+ *
5
+ * Absorbs schedule-builder's `db:generate` package script (`with-env
6
+ * drizzle-kit generate --config drizzle-migrations.config.ts`), the same way
7
+ * `db introspect` absorbed platform's `db:pull`. It touches no database: it
8
+ * diffs the schema files against drizzle-kit's own snapshot and writes SQL
9
+ * under the app's `drizzle-generated/`, a draft to be carried by hand into a
10
+ * real migration under `supabase/migrations/` — never something that runs on
11
+ * its own. `DB_URL` is still required because drizzle-kit's Postgres
12
+ * introspection needs a live connection to compare against, matching what
13
+ * `with-env` supplied before — sourced via `resolveLocalToolingEnv`, the
14
+ * tier this process already entered rather than a raw `.env` parse. See
15
+ * `db/local-env.ts`'s header.
16
+ */
17
+ import { spawn } from "node:child_process";
18
+ import { join } from "node:path";
19
+ import { select } from "@clack/prompts";
20
+ import { findRepoRoot } from "../repo/root.js";
21
+ import { unwrap } from "../ui.js";
22
+ import { resolveLocalToolingEnv } from "./local-env.js";
23
+ // ── Per-app config ────────────────────────────────────────────────────────────
24
+ /** Apps whose Drizzle schema drafts migrations via `drizzle-kit generate`. */
25
+ const APP_CONFIGS = {
26
+ "schedule-builder": "drizzle-migrations.config.ts",
27
+ };
28
+ async function pickApp() {
29
+ const names = Object.keys(APP_CONFIGS);
30
+ if (names.length === 1)
31
+ return names[0];
32
+ return unwrap(await select({
33
+ message: "Whose schema drift should I draft a migration from?",
34
+ options: names.map((value) => ({ value, label: value })),
35
+ }));
36
+ }
37
+ function runDrizzleGenerate(appDir, configFile, env) {
38
+ return new Promise((resolve) => {
39
+ const child = spawn("pnpm", ["exec", "drizzle-kit", "generate", "--config", configFile], { stdio: "inherit", cwd: appDir, env });
40
+ child.on("exit", (code) => resolve(code ?? 1));
41
+ });
42
+ }
43
+ // ── Entry ─────────────────────────────────────────────────────────────────────
44
+ export async function runGenerateMigration(appSlug) {
45
+ const app = appSlug ?? (await pickApp());
46
+ const configFile = APP_CONFIGS[app];
47
+ if (!configFile) {
48
+ const valid = Object.keys(APP_CONFIGS).join(", ");
49
+ process.stderr.write(`devtools db migration generate: unknown app "${app}". Expected: ${valid}.\n`);
50
+ return 1;
51
+ }
52
+ const appDir = join(findRepoRoot(), "apps", app);
53
+ // Matching `db introspect`'s approach: `pnpm devtools` enters a tier into
54
+ // `process.env` before dispatch (`launch.ts`), but drizzle-kit here runs as
55
+ // its own child process in the app's directory, so that entered
56
+ // environment needs to be injected explicitly rather than inherited
57
+ // implicitly.
58
+ const env = await resolveLocalToolingEnv();
59
+ if (!env["DB_URL"]) {
60
+ process.stderr.write("devtools db migration generate: DB_URL is not set. Run " +
61
+ "`pnpm devtools db start` first (for local — its connection string " +
62
+ "lives in .env.generated once the stack is up), or set DB_URL in " +
63
+ "the deploy tier's env file this process entered.\n");
64
+ return 1;
65
+ }
66
+ return runDrizzleGenerate(appDir, configFile, env);
67
+ }
@@ -0,0 +1 @@
1
+ export declare function runGenerateTypes(dbUrl: string): Promise<number>;
@@ -0,0 +1,4 @@
1
+ import { generateTypes } from "./run.js";
2
+ export async function runGenerateTypes(dbUrl) {
3
+ return generateTypes(dbUrl);
4
+ }
@@ -0,0 +1 @@
1
+ export declare function runIntrospect(app?: string): Promise<number>;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * `db introspect --app <slug>` — pull an app's live schema into its generated
3
+ * Drizzle files, then apply post-pull fixups.
4
+ *
5
+ * Replicates the old `db:pull` package script chain:
6
+ * 1. drizzle-kit pull --config drizzle-introspection.config.ts (auth/storage schemas)
7
+ * 2. drizzle-kit pull --config drizzle.config.ts (platform schema)
8
+ * 3. Post-pull fixups on the generated schema.ts:
9
+ * a. Delete relations.ts (we maintain hand-written relations)
10
+ * b. Re-inject the cross-schema FK-target import (auth.users etc.)
11
+ * c. Append InPlatform-suffix aliases so existing imports stay stable
12
+ *
13
+ * `DB_URL` comes from `resolveLocalToolingEnv` — the tier this process
14
+ * already entered (`launch.ts`, before `cli.ts` ever dispatched this
15
+ * command), `.env.generated` overlay included — and is injected into the
16
+ * subprocess environment. See `db/local-env.ts`'s header for why this is
17
+ * NOT a raw `.env` parse.
18
+ */
19
+ import { spawn } from "node:child_process";
20
+ import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
21
+ import { join } from "node:path";
22
+ import { findRepoRoot } from "../repo/root.js";
23
+ import { resolveLocalToolingEnv } from "./local-env.js";
24
+ const APP_CONFIGS = {
25
+ platform: {
26
+ configs: ["drizzle-introspection.config.ts", "drizzle.config.ts"],
27
+ schemaFile: "src/server/db/schema/generated/schema.ts",
28
+ schemaSuffix: "InPlatform",
29
+ crossSchemaImport: 'import { usersInAuth as users, oauthClientsInAuth as oauthClients } from "~/supabase/drizzle/schema"',
30
+ },
31
+ };
32
+ // ── drizzle-kit pull ──────────────────────────────────────────────────────────
33
+ function runDrizzlePull(appDir, configFile, env) {
34
+ return new Promise((resolve) => {
35
+ const child = spawn("pnpm", ["exec", "drizzle-kit", "pull", "--config", configFile], { stdio: "inherit", cwd: appDir, env });
36
+ child.on("exit", (code) => resolve(code ?? 1));
37
+ });
38
+ }
39
+ // ── Post-pull fixups ──────────────────────────────────────────────────────────
40
+ function applyPostPullFixups(appDir, cfg) {
41
+ const schemaPath = join(appDir, cfg.schemaFile);
42
+ const relationsPath = join(appDir, cfg.schemaFile.replace("schema.ts", "relations.ts"));
43
+ // 1. Delete relations.ts
44
+ if (existsSync(relationsPath))
45
+ rmSync(relationsPath);
46
+ if (!existsSync(schemaPath))
47
+ return;
48
+ let src = readFileSync(schemaPath, "utf8");
49
+ // 2. Re-inject cross-schema FK-target import (idempotent)
50
+ const IMPORT_MARKER = "// Cross-schema FK targets";
51
+ if (cfg.crossSchemaImport && !src.includes(IMPORT_MARKER)) {
52
+ const lines = src.split("\n");
53
+ let insertAt = 0;
54
+ for (let i = 0; i < lines.length; i++) {
55
+ const line = lines[i] ?? "";
56
+ if (line.startsWith("import ") && line.includes("drizzle-orm")) {
57
+ insertAt = i + 1;
58
+ }
59
+ else if (line.trim() !== "" && !line.startsWith("import ")) {
60
+ break;
61
+ }
62
+ }
63
+ lines.splice(insertAt, 0, `${IMPORT_MARKER} — re-injected by devtools db introspect after each drizzle-kit pull`, cfg.crossSchemaImport);
64
+ src = lines.join("\n");
65
+ }
66
+ // 3. Append base-name aliases for every `<name><suffix>` export (idempotent)
67
+ const ALIAS_MARKER = "// Schema-suffix aliases";
68
+ if (cfg.schemaSuffix && !src.includes(ALIAS_MARKER)) {
69
+ const names = new Set();
70
+ const re = new RegExp(`export const (\\w+)${cfg.schemaSuffix}\\b`, "g");
71
+ for (const m of src.matchAll(re))
72
+ if (m[1])
73
+ names.add(m[1]);
74
+ if (names.size > 0) {
75
+ const aliases = [...names]
76
+ .sort()
77
+ .map((n) => `export { ${n}${cfg.schemaSuffix} as ${n} };`)
78
+ .join("\n");
79
+ src = `${src.trimEnd()}\n\n${ALIAS_MARKER} — appended by devtools db introspect\n${aliases}\n`;
80
+ }
81
+ }
82
+ writeFileSync(schemaPath, src);
83
+ process.stdout.write(`[introspect] patched ${cfg.schemaFile}\n`);
84
+ }
85
+ // ── Entry ─────────────────────────────────────────────────────────────────────
86
+ export async function runIntrospect(app) {
87
+ if (!app) {
88
+ process.stderr.write("devtools db introspect: --app <slug> is required.\n");
89
+ return 1;
90
+ }
91
+ const cfg = APP_CONFIGS[app];
92
+ if (!cfg) {
93
+ const valid = Object.keys(APP_CONFIGS).join(", ");
94
+ process.stderr.write(`devtools db introspect: unknown app "${app}". Expected: ${valid}.\n`);
95
+ return 1;
96
+ }
97
+ const appDir = join(findRepoRoot(), "apps", app);
98
+ const env = await resolveLocalToolingEnv();
99
+ if (!env["DB_URL"]) {
100
+ process.stderr.write("devtools db introspect: DB_URL is not set. Run " +
101
+ "`pnpm devtools db start` first (for local — its connection string " +
102
+ "lives in .env.generated once the stack is up), or set DB_URL in " +
103
+ "the deploy tier's env file this process entered.\n");
104
+ return 1;
105
+ }
106
+ for (const configFile of cfg.configs) {
107
+ const code = await runDrizzlePull(appDir, configFile, env);
108
+ if (code !== 0)
109
+ return code;
110
+ }
111
+ applyPostPullFixups(appDir, cfg);
112
+ return 0;
113
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The environment `db introspect` and `db migration generate` hand to the
3
+ * `drizzle-kit` child process they spawn.
4
+ *
5
+ * ## How this relates to `db/connection.ts`
6
+ *
7
+ * `db/connection.ts` resolves the session's one database CONNECTION (its
8
+ * `DB_URL`, with sanity guards) for commands that open one themselves. This
9
+ * is the other need: a whole ENVIRONMENT to hand a child process. Both read
10
+ * the same source — whatever THIS PROCESS already entered — `launch.ts`
11
+ * runs before
12
+ * `cli.ts`'s command dispatch even imports, and enters a tier (development,
13
+ * by default) into `process.env` via `@devdogsuga/env/session`'s
14
+ * `enterEnvironment`, applying `selectEnvFiles`'s overlay-plus-liveness-probe
15
+ * logic (`.env.generated`, the running local stack's connection block,
16
+ * ahead of `.env` — see `load.ts`'s probe table) along the way.
17
+ *
18
+ * These two commands used to bypass all of that: each parsed `.env` directly
19
+ * with raw `dotenv`, and merged it OVER `process.env`
20
+ * (`{...process.env, ...tierEnv}`). Two bugs followed from that shortcut:
21
+ *
22
+ * a. `.env.generated` — where the running local stack's `DB_URL` actually
23
+ * lives — was never read, so with the stack up these commands reported
24
+ * "DB_URL is not set in .env. Run `supabase start` first" (wrong
25
+ * command AND wrong file) or silently connected to a stale/hosted URL,
26
+ * while every other command in this CLI, going through `selectEnvFiles`,
27
+ * worked fine.
28
+ * b. The precedence was inverted: a raw `.env` parse merged OVER
29
+ * `process.env` clobbers whatever tier this process actually entered —
30
+ * backwards from the documented expectation (now `db/connection.ts`'s)
31
+ * that a command reads the tier the process is already running
32
+ * under rather than reimposing development on top of it.
33
+ *
34
+ * `resolveLocalToolingEnv` fixes both by returning `process.env` itself
35
+ * (fresh values, `.env.generated` included, whatever tier was entered)
36
+ * instead of re-deriving anything. The only case it still loads a tier
37
+ * itself is a caller that reaches these commands WITHOUT going through
38
+ * `launch.ts` first — `process.env.DEPLOY_ENV` unset is the tell, since
39
+ * `enterEnvironment` always sets it, even for `development` — and even then
40
+ * it falls back to `loadEnvironment("development")`, never a raw dotenv
41
+ * parse, so the overlay-plus-probe logic still applies.
42
+ */
43
+ import type { LoadedEnvironment } from "@devdogsuga/env/load";
44
+ export interface ResolveLocalToolingEnvOptions {
45
+ /** Injectable for tests; defaults to `process.env`. */
46
+ processEnv?: NodeJS.ProcessEnv;
47
+ /** Injectable for tests; defaults to the real `loadEnvironment`. */
48
+ loadEnvironment?: (deployEnv?: string, options?: {
49
+ override?: boolean;
50
+ }) => Promise<LoadedEnvironment>;
51
+ }
52
+ /**
53
+ * Returns the environment a local-tooling child process (`drizzle-kit`,
54
+ * spawned by `db introspect` / `db migration generate`) should inherit.
55
+ *
56
+ * Normal path (the process went through `launch.ts`, as `pnpm devtools`
57
+ * always does): returns `process.env` unchanged — it already carries
58
+ * whatever tier was entered, `.env.generated` overlay included.
59
+ *
60
+ * Fallback path (no tier was ever entered — a direct import, e.g. from a
61
+ * test, or a future caller that skips `launch.ts`): loads development
62
+ * through the shared `loadEnvironment`, which applies the same overlay and
63
+ * liveness probe `selectEnvFiles` always has, then returns that snapshot.
64
+ */
65
+ export declare function resolveLocalToolingEnv(opts?: ResolveLocalToolingEnvOptions): Promise<NodeJS.ProcessEnv>;
@@ -0,0 +1,23 @@
1
+ import { loadEnvLoad } from "../repo/peers.js";
2
+ /**
3
+ * Returns the environment a local-tooling child process (`drizzle-kit`,
4
+ * spawned by `db introspect` / `db migration generate`) should inherit.
5
+ *
6
+ * Normal path (the process went through `launch.ts`, as `pnpm devtools`
7
+ * always does): returns `process.env` unchanged — it already carries
8
+ * whatever tier was entered, `.env.generated` overlay included.
9
+ *
10
+ * Fallback path (no tier was ever entered — a direct import, e.g. from a
11
+ * test, or a future caller that skips `launch.ts`): loads development
12
+ * through the shared `loadEnvironment`, which applies the same overlay and
13
+ * liveness probe `selectEnvFiles` always has, then returns that snapshot.
14
+ */
15
+ export async function resolveLocalToolingEnv(opts = {}) {
16
+ const processEnv = opts.processEnv ?? process.env;
17
+ if (processEnv.DEPLOY_ENV !== undefined && processEnv.DEPLOY_ENV !== "") {
18
+ return processEnv;
19
+ }
20
+ const load = opts.loadEnvironment ?? (await loadEnvLoad()).loadEnvironment;
21
+ const loaded = await load("development");
22
+ return loaded.env;
23
+ }
@@ -0,0 +1 @@
1
+ export declare function runNewMigration(name: string | undefined): Promise<number>;
@@ -0,0 +1,8 @@
1
+ import { supabase } from "./run.js";
2
+ export async function runNewMigration(name) {
3
+ if (!name) {
4
+ process.stderr.write("devtools new-migration: a migration name is required.\n");
5
+ return 1;
6
+ }
7
+ return supabase("migration", "new", name);
8
+ }