@specific.dev/spectest 0.39.0 → 0.43.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 (105) hide show
  1. package/dist/browser.d.ts +21 -8
  2. package/dist/browser.js +78 -36
  3. package/dist/components/supabase.d.ts +87 -27
  4. package/dist/components/supabase.js +352 -69
  5. package/dist/daemon.d.ts +38 -0
  6. package/dist/daemon.js +464 -987
  7. package/dist/harness/build-context.d.ts +82 -0
  8. package/dist/harness/build-context.js +113 -0
  9. package/dist/harness/buildkit-progress.d.ts +37 -0
  10. package/dist/harness/buildkit-progress.js +66 -0
  11. package/dist/harness/container-run.d.ts +89 -0
  12. package/dist/harness/container-run.js +118 -0
  13. package/dist/harness/file-mounts.d.ts +91 -0
  14. package/dist/harness/file-mounts.js +119 -0
  15. package/dist/harness/hostmatch.d.ts +65 -0
  16. package/dist/harness/hostmatch.js +108 -0
  17. package/dist/harness/http-proxy.d.ts +62 -0
  18. package/dist/harness/http-proxy.js +104 -0
  19. package/dist/harness/ingress-table.d.ts +148 -0
  20. package/dist/harness/ingress-table.js +129 -0
  21. package/dist/harness/log-delta.d.ts +54 -0
  22. package/dist/harness/log-delta.js +83 -0
  23. package/dist/harness/main.d.ts +47 -0
  24. package/dist/harness/main.js +164 -0
  25. package/dist/harness/methods.d.ts +54 -0
  26. package/dist/harness/methods.js +65 -0
  27. package/dist/harness/names-registry.d.ts +63 -0
  28. package/dist/harness/names-registry.js +90 -0
  29. package/dist/harness/protocol.d.ts +88 -0
  30. package/dist/harness/protocol.js +96 -0
  31. package/dist/harness/ready-poll.d.ts +47 -0
  32. package/dist/harness/ready-poll.js +67 -0
  33. package/dist/harness/service-graph.d.ts +29 -0
  34. package/dist/harness/service-graph.js +92 -0
  35. package/dist/harness/volume-paths.d.ts +70 -0
  36. package/dist/harness/volume-paths.js +81 -0
  37. package/dist/index.d.ts +58 -16
  38. package/dist/ingress.d.ts +1 -1
  39. package/dist/mobile.d.ts +9 -5
  40. package/dist/mobile.js +7 -6
  41. package/dist/recorder.d.ts +10 -0
  42. package/dist/resolver.js +5 -8
  43. package/dist/vendor/rrweb-plugin-console-record.umd.js +521 -0
  44. package/dist/vendor/rrweb-record.min.js +5061 -0
  45. package/package.json +7 -1
  46. package/src/aws-sigv4.ts +218 -0
  47. package/src/browser.ts +2095 -0
  48. package/src/components/aws.ts +554 -0
  49. package/src/components/email.ts +398 -0
  50. package/src/components/expo.ts +167 -0
  51. package/src/components/index.ts +81 -0
  52. package/src/components/k3s.ts +2061 -0
  53. package/src/components/postgres.ts +132 -0
  54. package/src/components/replayFake.ts +1015 -0
  55. package/src/components/s3.ts +132 -0
  56. package/src/components/supabase.ts +1699 -0
  57. package/src/daemon.ts +5537 -0
  58. package/src/harness/build-context.test.ts +0 -0
  59. package/src/harness/build-context.ts +146 -0
  60. package/src/harness/buildkit-progress.test.ts +98 -0
  61. package/src/harness/buildkit-progress.ts +74 -0
  62. package/src/harness/container-run.test.ts +209 -0
  63. package/src/harness/container-run.ts +158 -0
  64. package/src/harness/file-mounts.test.ts +185 -0
  65. package/src/harness/file-mounts.ts +145 -0
  66. package/src/harness/hostmatch.test.ts +148 -0
  67. package/src/harness/hostmatch.ts +109 -0
  68. package/src/harness/http-proxy.test.ts +156 -0
  69. package/src/harness/http-proxy.ts +119 -0
  70. package/src/harness/ingress-rebind.test.ts +125 -0
  71. package/src/harness/ingress-table.test.ts +172 -0
  72. package/src/harness/ingress-table.ts +186 -0
  73. package/src/harness/log-delta.test.ts +125 -0
  74. package/src/harness/log-delta.ts +100 -0
  75. package/src/harness/main.test.ts +211 -0
  76. package/src/harness/main.ts +196 -0
  77. package/src/harness/methods.test.ts +63 -0
  78. package/src/harness/methods.ts +92 -0
  79. package/src/harness/names-registry.test.ts +137 -0
  80. package/src/harness/names-registry.ts +108 -0
  81. package/src/harness/protocol.test.ts +148 -0
  82. package/src/harness/protocol.ts +163 -0
  83. package/src/harness/ready-poll.test.ts +172 -0
  84. package/src/harness/ready-poll.ts +93 -0
  85. package/src/harness/service-graph.test.ts +97 -0
  86. package/src/harness/service-graph.ts +97 -0
  87. package/src/harness/volume-paths.test.ts +102 -0
  88. package/src/harness/volume-paths.ts +112 -0
  89. package/src/ids.ts +89 -0
  90. package/src/index.ts +2767 -0
  91. package/src/ingress.ts +305 -0
  92. package/src/inspect.ts +739 -0
  93. package/src/locator.ts +716 -0
  94. package/src/mobile.ts +138 -0
  95. package/src/record-secrets.ts +41 -0
  96. package/src/recorder.ts +856 -0
  97. package/src/redis.ts +202 -0
  98. package/src/replay-bundle.ts +108 -0
  99. package/src/resolver.ts +348 -0
  100. package/src/s3.ts +333 -0
  101. package/src/sql.ts +243 -0
  102. package/src/terminal.ts +740 -0
  103. package/src/url-match.ts +67 -0
  104. package/src/vendor/rrweb-plugin-console-record.umd.js +521 -0
  105. package/src/vendor/rrweb-record.min.js +5061 -0
package/dist/browser.d.ts CHANGED
@@ -60,6 +60,14 @@ export interface BrowserSessionRecorder {
60
60
  * the dashboard can link a browser event to its replay player.
61
61
  */
62
62
  readonly sessionId: string;
63
+ /**
64
+ * Author-given name of this session (`ctx.browser("alice")`), absent
65
+ * for the default unnamed one. Echoed onto every op's event so the
66
+ * step list can say WHICH browser acted without having to resolve the
67
+ * session record — which the CLI's failure detail can't do (it renders
68
+ * from the persisted events alone, with no replay bundle).
69
+ */
70
+ readonly sessionName?: string;
63
71
  /** Called for each drained chunk of rrweb events. */
64
72
  recordStep(step: BrowserSessionStep): void;
65
73
  /** Optional: called whenever `Browser.goto(url)` is invoked. */
@@ -302,6 +310,9 @@ export declare function openBrowser(opts?: BrowserOptions): Promise<Browser>;
302
310
  * the same object.
303
311
  */
304
312
  export declare function openMobileBackend(opts?: BrowserOptions): Promise<MobileBackend>;
313
+ /** Registry key for a named mobile session. NUL can't occur in a name or
314
+ * a URL, so the two halves can never run together ambiguously. */
315
+ export declare function mobileKey(name: string, url: string): string;
305
316
  /**
306
317
  * What acquiring a persistent session returns. `detach` is the test-end
307
318
  * hook (final rrweb drain, stop writing to this test's recorder, keep the
@@ -318,17 +329,19 @@ export interface PersistentBrowser {
318
329
  detach(): Promise<void>;
319
330
  }
320
331
  /**
321
- * Acquire THE persistent desktop browser (creating it on first use). There
322
- * is deliberately a single one — `ctx.browser()` always returns it — so a
323
- * test DAG shares one browsing session along each branch. The first call's
324
- * options win; later calls attach to the existing view as-is.
332
+ * Acquire the persistent desktop browser called `name` (creating it on
333
+ * first use). There is one per name — `ctx.browser()` uses the default
334
+ * name `""` — so a test DAG shares each named browsing session along each
335
+ * branch, and two names are two independent users. The first call for a
336
+ * name wins its options; later calls attach to the existing view as-is.
325
337
  */
326
- export declare function acquirePersistentBrowser(opts?: BrowserOptions): Promise<PersistentBrowser>;
338
+ export declare function acquirePersistentBrowser(name?: string, opts?: BrowserOptions): Promise<PersistentBrowser>;
327
339
  /**
328
- * Acquire the persistent mobile session for an app URL (one per app). A
329
- * fresh session navigates to the app; an attach continues on the live page.
340
+ * Acquire the persistent mobile session for an app URL under `name` (one
341
+ * per name per app; `ctx.mobile(app)` uses the default name `""`). A fresh
342
+ * session navigates to the app; an attach continues on the live page.
330
343
  */
331
- export declare function acquirePersistentMobileBackend(url: string, recorder: BrowserSessionRecorder | null, initScript?: string): Promise<PersistentBrowser>;
344
+ export declare function acquirePersistentMobileBackend(url: string, name: string, recorder: BrowserSessionRecorder | null, initScript?: string): Promise<PersistentBrowser>;
332
345
  export interface RecordableFields {
333
346
  url: string;
334
347
  selector: string;
package/dist/browser.js CHANGED
@@ -872,16 +872,45 @@ export async function openMobileBackend(opts = {}) {
872
872
  // ────────────────────────────────────────────────────────────────────────
873
873
  // Persistent sessions (the default behind ctx.browser / ctx.mobile)
874
874
  // ────────────────────────────────────────────────────────────────────────
875
- // One long-lived desktop browser plus one mobile session per app URL.
875
+ // Long-lived sessions, keyed by NAME: one desktop browser per name, one
876
+ // mobile session per (name, app URL). The default name is `""` — what
877
+ // `ctx.browser()` / `ctx.mobile(app)` use — so an unnamed project keeps
878
+ // exactly the one-session-per-device behaviour it had before names
879
+ // existed. A name is the key and nothing else: two names are two
880
+ // BrowserContexts in the one Chromium, i.e. two independent cookie jars
881
+ // and localStorage, which is what modelling two users needs.
882
+ //
876
883
  // Module state lives in daemon memory, so it forks with the snapshot the
877
- // same way fake `state` and TEST_DATA do: a test's browser — its live
878
- // page, cookies, localStorage, in-memory SPA state — is captured in the
884
+ // same way fake `state` and TEST_DATA do: a test's browsers — their live
885
+ // pages, cookies, localStorage, in-memory SPA state — are captured in the
879
886
  // post-test snapshot and inherited by `dependsOn` children, while sibling
880
887
  // forks never see each other's sessions. That's what lets a child test
881
888
  // continue where its parent left off (e.g. already signed in) instead of
882
- // re-navigating and re-authenticating.
883
- let SHARED_BROWSER = null;
889
+ // re-navigating and re-authenticating. The name is a plain string, so it
890
+ // keys the same session on both sides of a fork.
891
+ const SHARED_BROWSERS = new Map();
884
892
  const SHARED_MOBILE = new Map();
893
+ /**
894
+ * Ceiling on live persistent sessions of one kind (desktop / mobile).
895
+ * Every session is a Chromium BrowserContext that rides every snapshot
896
+ * from here down the DAG, so a test that mints names in a loop
897
+ * (`ctx.browser(userId)`) would grow the VM's memory floor for the rest
898
+ * of the run. Failing loudly at a sane count beats a wedged guest.
899
+ */
900
+ const MAX_PERSISTENT_SESSIONS = 8;
901
+ /** Registry key for a named mobile session. NUL can't occur in a name or
902
+ * a URL, so the two halves can never run together ambiguously. */
903
+ export function mobileKey(name, url) {
904
+ return `${name}\u0000${url}`;
905
+ }
906
+ /** Guard the session cap, naming the offender and what to do about it. */
907
+ function checkSessionCap(kind, live, name) {
908
+ if (live < MAX_PERSISTENT_SESSIONS)
909
+ return;
910
+ throw new Error(`too many ${kind} sessions: ${MAX_PERSISTENT_SESSIONS} are already open and "${name}" would be another. ` +
911
+ `Each named session is a live browser captured in every snapshot from here on — name them for the ` +
912
+ `roles under test (e.g. "buyer"/"seller") rather than per row of data, or close() the ones you're done with.`);
913
+ }
885
914
  async function newHolder(width, height, device) {
886
915
  const context = await newViewContext(width, height, device);
887
916
  const spawned = await spawnPage(context);
@@ -941,58 +970,65 @@ async function attachReset(holder) {
941
970
  }
942
971
  }
943
972
  /**
944
- * Acquire THE persistent desktop browser (creating it on first use). There
945
- * is deliberately a single one — `ctx.browser()` always returns it — so a
946
- * test DAG shares one browsing session along each branch. The first call's
947
- * options win; later calls attach to the existing view as-is.
973
+ * Acquire the persistent desktop browser called `name` (creating it on
974
+ * first use). There is one per name — `ctx.browser()` uses the default
975
+ * name `""` — so a test DAG shares each named browsing session along each
976
+ * branch, and two names are two independent users. The first call for a
977
+ * name wins its options; later calls attach to the existing view as-is.
948
978
  */
949
- export async function acquirePersistentBrowser(opts = {}) {
979
+ export async function acquirePersistentBrowser(name = "", opts = {}) {
950
980
  const device = opts.frame === "mobile" ? LATEST_IPHONE : null;
951
- let attached = true;
952
- if (!SHARED_BROWSER) {
953
- attached = false;
954
- SHARED_BROWSER = await newHolder(device ? device.viewport.width : opts.width ?? 1280, device ? device.viewport.height : opts.height ?? 720, device);
981
+ let holder = SHARED_BROWSERS.get(name);
982
+ const attached = holder !== undefined;
983
+ if (holder) {
984
+ await attachReset(holder);
955
985
  }
956
986
  else {
957
- await attachReset(SHARED_BROWSER);
987
+ checkSessionCap("browser", SHARED_BROWSERS.size, name);
988
+ holder = await newHolder(device ? device.viewport.width : opts.width ?? 1280, device ? device.viewport.height : opts.height ?? 720, device);
989
+ SHARED_BROWSERS.set(name, holder);
958
990
  }
959
- const holder = SHARED_BROWSER;
960
- const { backend, detach } = buildBackend(holder, opts.recorder ?? null, {
991
+ const view = holder;
992
+ const { backend, detach } = buildBackend(view, opts.recorder ?? null, {
961
993
  persistent: true,
962
994
  onDestroy: () => {
963
- if (SHARED_BROWSER === holder)
964
- SHARED_BROWSER = null;
995
+ if (SHARED_BROWSERS.get(name) === view)
996
+ SHARED_BROWSERS.delete(name);
965
997
  },
966
998
  });
967
999
  // Fresh session only: an attached view already carries the init script on
968
1000
  // its (forked) holder, and first-call-wins means a later call's options
969
1001
  // don't retroactively apply.
970
1002
  if (!attached && opts.initScript !== undefined) {
971
- await installInitScript(holder, opts.initScript);
1003
+ await installInitScript(view, opts.initScript);
972
1004
  }
973
1005
  if (!attached && opts.url !== undefined)
974
1006
  await backend.goto(opts.url);
975
1007
  return { browser: backend, attached, detach };
976
1008
  }
977
1009
  /**
978
- * Acquire the persistent mobile session for an app URL (one per app). A
979
- * fresh session navigates to the app; an attach continues on the live page.
1010
+ * Acquire the persistent mobile session for an app URL under `name` (one
1011
+ * per name per app; `ctx.mobile(app)` uses the default name `""`). A fresh
1012
+ * session navigates to the app; an attach continues on the live page.
980
1013
  */
981
- export async function acquirePersistentMobileBackend(url, recorder, initScript) {
982
- const existing = SHARED_MOBILE.get(url);
1014
+ export async function acquirePersistentMobileBackend(url, name, recorder, initScript) {
1015
+ const key = mobileKey(name, url);
1016
+ const existing = SHARED_MOBILE.get(key);
1017
+ if (!existing)
1018
+ checkSessionCap("mobile", SHARED_MOBILE.size, name);
983
1019
  const holder = existing ??
984
1020
  (await newHolder(LATEST_IPHONE.viewport.width, LATEST_IPHONE.viewport.height, LATEST_IPHONE));
985
1021
  if (existing) {
986
1022
  await attachReset(holder);
987
1023
  }
988
1024
  else {
989
- SHARED_MOBILE.set(url, holder);
1025
+ SHARED_MOBILE.set(key, holder);
990
1026
  }
991
1027
  const { backend, detach } = buildBackend(holder, recorder, {
992
1028
  persistent: true,
993
1029
  onDestroy: () => {
994
- if (SHARED_MOBILE.get(url) === holder)
995
- SHARED_MOBILE.delete(url);
1030
+ if (SHARED_MOBILE.get(key) === holder)
1031
+ SHARED_MOBILE.delete(key);
996
1032
  },
997
1033
  });
998
1034
  if (!existing) {
@@ -1069,6 +1105,18 @@ function buildBackend(holder, recorder, buildOpts) {
1069
1105
  // when rrweb's load-deferred full snapshot is most likely to be
1070
1106
  // missing from the chunk we're about to drain (see `drainExpr`).
1071
1107
  let lastDrainUrl = null;
1108
+ /** Session provenance stamped on every recorded op: which replay player
1109
+ * the step belongs to, which named browser it acted on, and where in
1110
+ * the player to seek (`endT`, in rrweb's clock). */
1111
+ function sessionFields(endT) {
1112
+ if (!recorder)
1113
+ return {};
1114
+ return {
1115
+ sessionId: recorder.sessionId,
1116
+ sessionTimestamp: endT,
1117
+ ...(recorder.sessionName ? { sessionName: recorder.sessionName } : {}),
1118
+ };
1119
+ }
1072
1120
  async function drain(action) {
1073
1121
  if (!holder.recordingInstalled || !recorder || recordingEnded)
1074
1122
  return;
@@ -1112,9 +1160,7 @@ function buildBackend(holder, recorder, buildOpts) {
1112
1160
  const seq = recordBrowser({
1113
1161
  action,
1114
1162
  ...fields,
1115
- ...(recorder
1116
- ? { sessionId: recorder.sessionId, sessionTimestamp: endT }
1117
- : {}),
1163
+ ...sessionFields(endT),
1118
1164
  durationMs: endT - t,
1119
1165
  }, resv);
1120
1166
  await drain(action);
@@ -1132,9 +1178,7 @@ function buildBackend(holder, recorder, buildOpts) {
1132
1178
  recordBrowser({
1133
1179
  action,
1134
1180
  ...fields,
1135
- ...(recorder
1136
- ? { sessionId: recorder.sessionId, sessionTimestamp: endT }
1137
- : {}),
1181
+ ...sessionFields(endT),
1138
1182
  durationMs: endT - t,
1139
1183
  error: e?.message ?? String(err),
1140
1184
  }, resv);
@@ -1459,9 +1503,7 @@ function buildBackend(holder, recorder, buildOpts) {
1459
1503
  const seq = recordBrowser({
1460
1504
  action,
1461
1505
  ...fields,
1462
- ...(recorder
1463
- ? { sessionId: recorder.sessionId, sessionTimestamp: endT }
1464
- : {}),
1506
+ ...sessionFields(endT),
1465
1507
  durationMs: waitedMs,
1466
1508
  ...(error ? { error } : {}),
1467
1509
  }, reserveBackdated(waitedMs));
@@ -50,17 +50,19 @@ export interface SupabaseOptions {
50
50
  */
51
51
  hostname?: string;
52
52
  /**
53
- * Capture GoTrue's outgoing mail (magic links, OTPs, password recovery,
54
- * invites — and signup confirmations with `autoconfirm: false`) with the
55
- * standard `email()` component. **On by default**: the stack includes the
56
- * capture server (`<name>-mail`) with GoTrue's SMTP wired at it, and tests
57
- * read the mailbox through the typed helpers at `ctx.svc.<name>.mail`
58
- * (`lastEmail` / `emails` / `clear`), each captured message rendering
59
- * mail-client-style on the dashboard timeline.
53
+ * Capture GoTrue's outgoing mail (signup confirmations, magic links, OTPs,
54
+ * password recovery, invites) with the standard `email()` component. **On
55
+ * by default**: the stack includes the capture server (`<name>-mail`) with
56
+ * GoTrue's SMTP wired at it, and tests read the mailbox through the typed
57
+ * helpers at `ctx.svc.<name>.mail` (`lastEmail` / `emails` / `clear`), each
58
+ * captured message rendering mail-client-style on the dashboard timeline.
60
59
  *
61
- * Signups stay one-step by default (`autoconfirm: true`) so the mailbox is
62
- * purely additive; pass `{ autoconfirm: false }` to test real
63
- * email-confirmation flows. Pass `{ service: "<key>" }` to point GoTrue at
60
+ * Signups complete in **one step** by default (`autoconfirm: true`), so
61
+ * the mailbox is purely additive and a suite that just needs accounts
62
+ * writes nothing extra. Pass `{ autoconfirm: false }` to run the real
63
+ * confirmation flow instead — the signup then issues no session until a
64
+ * test follows the emailed link out of the mailbox. Pass
65
+ * `{ service: "<key>" }` to point GoTrue at
64
66
  * an `email()` service the environment already declares — keep a single
65
67
  * mailbox per environment rather than one per component: if the app under
66
68
  * test also sends mail, share one `email()` between it and Supabase
@@ -81,24 +83,75 @@ export interface SupabaseOptions {
81
83
  serviceRole?: string[];
82
84
  };
83
85
  /**
84
- * Auto-apply SQL migrations found in the project before any dependent service
85
- * starts. `true` (default) applies every `*.sql` under `supabase/migrations/`
86
- * (sorted, the Supabase convention), then `supabase/seed.sql` if present — a
87
- * no-op when the directory is absent. Pass a string to point at a different
88
- * directory (relative to the project root or absolute), or `false` to skip.
89
- * An *explicitly configured* path that doesn't resolve is an error at env
90
- * start (a silent no-op there surfaces much later as a confusing
91
- * `relation … does not exist`).
86
+ * Where the project's SQL migrations live, e.g. `"supabase/migrations"`
87
+ * (relative to the project root, or absolute). **Nothing is applied unless
88
+ * you set this** — there is no assumed path, so a project whose schema
89
+ * arrives some other way (an app that migrates itself at boot, a dump
90
+ * restored in `setup`) never gets a second, surprising source of DDL.
91
+ *
92
+ * Every `*.sql` directly in the directory is applied in sorted filename
93
+ * order, then — the standard Supabase layout — the **`seed.sql` beside
94
+ * that directory**, if there is one:
95
+ *
96
+ * ```
97
+ * supabase/
98
+ * migrations/0001_init.sql ← applied, sorted
99
+ * migrations/0002_todos.sql
100
+ * seed.sql ← applied last
101
+ * ```
102
+ *
103
+ * It all runs **once the whole stack is up** — the same point `supabase db
104
+ * reset` applies migrations — so your SQL may use schema the services
105
+ * create when they boot (`storage.buckets`, the modern `auth.*` tables).
106
+ * PostgREST's schema cache is reloaded afterwards.
107
+ *
108
+ * A path that doesn't resolve is an error at env start; a silent no-op
109
+ * there surfaces much later as a confusing `relation … does not exist`.
92
110
  * Because `supabase/**` is project content, editing a migration correctly
93
111
  * forces a cold rebuild and re-apply (unlike `spectest/tests/**`).
94
112
  */
95
- migrations?: boolean | string;
113
+ migrations?: string;
96
114
  /**
97
- * Seed SQL applied after migrations. `true` (default) applies
98
- * `supabase/seed.sql` if it exists; a string overrides the path; `false`
99
- * skips it.
115
+ * Where the project's **edge functions** live, e.g.
116
+ * `"supabase/functions"` — one subdirectory per function, each with an
117
+ * entrypoint the runtime can serve (`supabase/functions/hello/index.ts`).
118
+ *
119
+ * **Nothing runs unless you set this.** Point at the directory and every
120
+ * function in it is served on the real Supabase edge runtime at
121
+ * `<url>/functions/v1/<name>` through the gateway — the same path
122
+ * `supabase.functions.invoke(...)` derives, so the app under test needs no
123
+ * override. Leave it unset and the runtime is left out of the stack
124
+ * entirely, along with the gateway's `/functions/v1` route.
125
+ *
126
+ * Function code is read straight from the repo — nothing is copied or
127
+ * bundled — so editing a function is an ordinary project edit.
128
+ *
129
+ * Pass an object instead of a path to turn JWT verification off, or to set
130
+ * the function secrets your code reads with `Deno.env.get(...)`.
100
131
  */
101
- seed?: boolean | string;
132
+ functions?: string | SupabaseFunctionsOptions;
133
+ }
134
+ /** Tuning for `SupabaseOptions.functions`. */
135
+ export interface SupabaseFunctionsOptions {
136
+ /**
137
+ * Directory holding one subdirectory per function, relative to the project
138
+ * root (or absolute). Required — there is no assumed location.
139
+ */
140
+ dir: string;
141
+ /**
142
+ * Reject a call that carries no valid JWT, exactly as hosted Supabase
143
+ * does. Default `true` — so a test that forgets the `Authorization`
144
+ * header fails here instead of in production. Set `false` for the
145
+ * `--no-verify-jwt` behaviour (webhooks, public endpoints).
146
+ */
147
+ verifyJwt?: boolean;
148
+ /**
149
+ * Extra environment variables the functions read (`Deno.env.get("…")`) —
150
+ * the local stand-in for function secrets. `SUPABASE_URL`,
151
+ * `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`, `SUPABASE_DB_URL` and
152
+ * `JWT_SECRET` are already set for you.
153
+ */
154
+ env?: Record<string, string>;
102
155
  }
103
156
  /** Tuning for `SupabaseOptions.mail`. */
104
157
  export interface SupabaseMailOptions {
@@ -110,10 +163,17 @@ export interface SupabaseMailOptions {
110
163
  */
111
164
  service?: string;
112
165
  /**
113
- * Whether GoTrue autoconfirms signups. `true` (default): signups complete
114
- * in one step and send no confirmation mail — recovery, magic-link and OTP
115
- * mail is still sent and captured. Set `false` to exercise real
116
- * email-confirmation flows (signups require the emailed verification).
166
+ * Whether GoTrue confirms a signup without the emailed verification.
167
+ * **`true` by default**: signups complete in one step and send no
168
+ * confirmation mail, which keeps account creation a single call in the
169
+ * suites where it is setup rather than the thing under test.
170
+ *
171
+ * Set `false` to run the flow a hosted project runs: the signup issues no
172
+ * session, and the test follows the emailed link out of the mailbox
173
+ * exactly as a user would (see `examples/supabase-app`). Recovery,
174
+ * magic-link, OTP and invite mail is sent either way. Forced `true` when
175
+ * the stack has no mailbox (`mail: false`), since the confirmation would
176
+ * go nowhere.
117
177
  */
118
178
  autoconfirm?: boolean;
119
179
  /** Sender address GoTrue mails from. Default `admin@example.com`. */