@specific.dev/spectest 0.24.0 → 0.27.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 (74) hide show
  1. package/dist/aws-sigv4.d.ts +42 -0
  2. package/dist/aws-sigv4.js +166 -0
  3. package/dist/browser.d.ts +314 -0
  4. package/dist/browser.js +1320 -0
  5. package/dist/components/email.d.ts +135 -0
  6. package/dist/components/email.js +271 -0
  7. package/dist/components/expo.d.ts +69 -0
  8. package/dist/components/expo.js +125 -0
  9. package/dist/components/index.d.ts +8 -0
  10. package/dist/components/index.js +18 -0
  11. package/dist/components/k3s.d.ts +143 -0
  12. package/dist/components/k3s.js +1067 -0
  13. package/dist/components/postgres.d.ts +93 -0
  14. package/dist/components/postgres.js +58 -0
  15. package/dist/components/replayFake.d.ts +169 -0
  16. package/dist/components/replayFake.js +738 -0
  17. package/dist/components/s3.d.ts +99 -0
  18. package/dist/components/s3.js +81 -0
  19. package/dist/components/supabase.d.ts +197 -0
  20. package/dist/components/supabase.js +1003 -0
  21. package/dist/daemon.d.ts +1 -0
  22. package/dist/daemon.js +4223 -0
  23. package/dist/ids.d.ts +2 -0
  24. package/{src/ids.ts → dist/ids.js} +46 -50
  25. package/dist/index.d.ts +1183 -0
  26. package/dist/index.js +769 -0
  27. package/dist/ingress.d.ts +114 -0
  28. package/dist/ingress.js +210 -0
  29. package/dist/inspect.d.ts +228 -0
  30. package/dist/inspect.js +429 -0
  31. package/dist/locator.d.ts +260 -0
  32. package/dist/locator.js +293 -0
  33. package/dist/mobile.d.ts +71 -0
  34. package/dist/mobile.js +65 -0
  35. package/dist/record-secrets.d.ts +9 -0
  36. package/{src/record-secrets.ts → dist/record-secrets.js} +13 -15
  37. package/dist/recorder.d.ts +516 -0
  38. package/dist/recorder.js +219 -0
  39. package/dist/redis.d.ts +54 -0
  40. package/dist/redis.js +126 -0
  41. package/dist/replay-bundle.d.ts +38 -0
  42. package/{src/replay-bundle.ts → dist/replay-bundle.js} +29 -47
  43. package/dist/resolver.d.ts +1 -0
  44. package/dist/resolver.js +309 -0
  45. package/dist/s3.d.ts +89 -0
  46. package/dist/s3.js +198 -0
  47. package/dist/sql.d.ts +74 -0
  48. package/dist/sql.js +151 -0
  49. package/dist/terminal.d.ts +161 -0
  50. package/dist/terminal.js +538 -0
  51. package/package.json +24 -9
  52. package/src/browser.ts +0 -1807
  53. package/src/components/email.ts +0 -398
  54. package/src/components/expo.ts +0 -167
  55. package/src/components/index.ts +0 -63
  56. package/src/components/k3s.ts +0 -1312
  57. package/src/components/postgres.ts +0 -105
  58. package/src/components/replayFake.ts +0 -848
  59. package/src/components/s3.ts +0 -132
  60. package/src/components/supabase.ts +0 -1299
  61. package/src/daemon.ts +0 -4969
  62. package/src/index.ts +0 -2350
  63. package/src/ingress.ts +0 -288
  64. package/src/inspect.ts +0 -673
  65. package/src/locator.ts +0 -594
  66. package/src/mobile.ts +0 -133
  67. package/src/recorder.ts +0 -817
  68. package/src/redis.ts +0 -202
  69. package/src/resolver.ts +0 -351
  70. package/src/s3.ts +0 -333
  71. package/src/sql.ts +0 -243
  72. package/src/terminal.ts +0 -740
  73. package/src/vendor/rrweb-plugin-console-record.umd.js +0 -521
  74. package/src/vendor/rrweb-record.min.js +0 -5061
@@ -0,0 +1,99 @@
1
+ import { type S3ClientLike } from "../s3.js";
2
+ export interface S3Options {
3
+ /** HTTP port the S3 API is served on. Default `9090`. */
4
+ port?: number;
5
+ /**
6
+ * Buckets to create on first boot. The backing server's own `initialBuckets`
7
+ * env is unreliable across versions, so the component creates them in `setup`
8
+ * (a plain CreateBucket `PUT`) — captured into the warm snapshot, never
9
+ * re-run on warm starts/forks.
10
+ */
11
+ buckets?: string[];
12
+ /**
13
+ * Default bucket the `helpers.client` targets (so `client.file("k")` resolves
14
+ * without a per-call `{ bucket }`). Defaults to the first entry of `buckets`.
15
+ */
16
+ bucket?: string;
17
+ /**
18
+ * Hostnames to additionally serve the store at over **HTTPS** (CA-trusted),
19
+ * via the daemon's TLS-terminating reverse proxy. Optional — defaults to none;
20
+ * the store is always reachable at `http://<key>:<port>` regardless. Set this
21
+ * only when the app under test hardcodes a specific prod S3 endpoint it can't
22
+ * be told to override (`https://<bucket>.s3.amazonaws.com`, a provider's
23
+ * storage host): the daemon mints a cert for each host and proxies to the
24
+ * store's plain HTTP. The store ignores SigV4, so the Host rewrite is harmless.
25
+ */
26
+ hosts?: string[];
27
+ /** Access key id the helper client signs with. The store ignores SigV4, so
28
+ * any non-empty value works; default `"s3"`. */
29
+ accessKeyId?: string;
30
+ /** Secret key the helper client signs with (ignored by the store). Default
31
+ * `"s3"`. */
32
+ secretAccessKey?: string;
33
+ /** Region for the helper client. Default `"us-east-1"`. */
34
+ region?: string;
35
+ /** Extra environment variables forwarded to the container. */
36
+ env?: Record<string, string>;
37
+ }
38
+ /** Helpers an `s3(...)` service exposes on `ctx.svc.<name>`. */
39
+ export interface S3Helpers {
40
+ /** An instrumented {@link S3Client} pointed at this store (path-style) — each
41
+ * object op lands on the test event log and its result comes back
42
+ * provenance-wrapped, so `expect(...)` on a read links to the op. */
43
+ client: S3ClientLike;
44
+ }
45
+ /**
46
+ * A ready-to-use, S3-compatible object store (backed by
47
+ * [`adobe/s3mock`](https://github.com/adobe/S3Mock)), with an instrumented S3
48
+ * client on `ctx.svc.<key>.client`. Drop into `environment.services`:
49
+ *
50
+ * ```ts
51
+ * import { s3 } from "@specific.dev/spectest/components";
52
+ *
53
+ * services: {
54
+ * storage: s3({ buckets: ["uploads"] }),
55
+ * },
56
+ * ```
57
+ *
58
+ * Tests get an instrumented S3 client at `ctx.svc.<key>.client`:
59
+ *
60
+ * ```ts
61
+ * await ctx.svc.storage.client.write("hello.txt", "hi there");
62
+ * const body = await ctx.svc.storage.client.file("hello.txt").text();
63
+ * expect(body).toBe("hi there"); // nests under the s3 read step
64
+ * ```
65
+ *
66
+ * The store skips SigV4 validation and is path-style only (what S3-compatible
67
+ * clients use), so it stands in for AWS S3, R2, Spaces, etc. without credential
68
+ * plumbing. If — and only if — the app under test hardcodes a specific prod S3
69
+ * endpoint, pass `hosts` to also serve the store there over a CA-trusted cert:
70
+ *
71
+ * ```ts
72
+ * // app hardcodes https://files.example.com — serve it there too:
73
+ * storage: s3({ hosts: ["files.example.com"] }),
74
+ * ```
75
+ */
76
+ export declare function s3(opts?: S3Options): {
77
+ setup?: (({ name }: {
78
+ name: string;
79
+ }) => Promise<void>) | undefined;
80
+ readyCheck: {
81
+ type: "http";
82
+ port: number;
83
+ path: string;
84
+ timeoutSecs: number;
85
+ };
86
+ helpers: ({ name }: {
87
+ name: string;
88
+ }) => S3Helpers;
89
+ tls?: {
90
+ hostname: string;
91
+ port: number;
92
+ }[] | undefined;
93
+ env?: Record<string, string> | undefined;
94
+ image: {
95
+ type: "registry";
96
+ reference: string;
97
+ };
98
+ ports: number[];
99
+ };
@@ -0,0 +1,81 @@
1
+ import { S3Client } from "../s3.js";
2
+ // Backing image, pinned internally (not user-configurable). adobe/s3mock 5.1.0
3
+ // is the current release; avoid exactly 5.0.0, which crashed at startup on
4
+ // Java 25 (`NoClassDefFoundError: KotlinBuiltIns$2`) — fixed in 5.1.0.
5
+ const S3_IMAGE = "adobe/s3mock:5.1.0";
6
+ /**
7
+ * A ready-to-use, S3-compatible object store (backed by
8
+ * [`adobe/s3mock`](https://github.com/adobe/S3Mock)), with an instrumented S3
9
+ * client on `ctx.svc.<key>.client`. Drop into `environment.services`:
10
+ *
11
+ * ```ts
12
+ * import { s3 } from "@specific.dev/spectest/components";
13
+ *
14
+ * services: {
15
+ * storage: s3({ buckets: ["uploads"] }),
16
+ * },
17
+ * ```
18
+ *
19
+ * Tests get an instrumented S3 client at `ctx.svc.<key>.client`:
20
+ *
21
+ * ```ts
22
+ * await ctx.svc.storage.client.write("hello.txt", "hi there");
23
+ * const body = await ctx.svc.storage.client.file("hello.txt").text();
24
+ * expect(body).toBe("hi there"); // nests under the s3 read step
25
+ * ```
26
+ *
27
+ * The store skips SigV4 validation and is path-style only (what S3-compatible
28
+ * clients use), so it stands in for AWS S3, R2, Spaces, etc. without credential
29
+ * plumbing. If — and only if — the app under test hardcodes a specific prod S3
30
+ * endpoint, pass `hosts` to also serve the store there over a CA-trusted cert:
31
+ *
32
+ * ```ts
33
+ * // app hardcodes https://files.example.com — serve it there too:
34
+ * storage: s3({ hosts: ["files.example.com"] }),
35
+ * ```
36
+ */
37
+ export function s3(opts = {}) {
38
+ const port = opts.port ?? 9090;
39
+ const buckets = opts.buckets ?? [];
40
+ const defaultBucket = opts.bucket ?? buckets[0];
41
+ const accessKeyId = opts.accessKeyId ?? "s3";
42
+ const secretAccessKey = opts.secretAccessKey ?? "s3";
43
+ const region = opts.region ?? "us-east-1";
44
+ return {
45
+ image: { type: "registry", reference: S3_IMAGE },
46
+ ports: [port],
47
+ ...(opts.env ? { env: opts.env } : {}),
48
+ ...(opts.hosts
49
+ ? { tls: opts.hosts.map((hostname) => ({ hostname, port })) }
50
+ : {}),
51
+ // GET / returns the ListBuckets XML with 200 once the store is up.
52
+ readyCheck: { type: "http", port, path: "/", timeoutSecs: 60 },
53
+ helpers: ({ name }) => ({
54
+ client: new S3Client({
55
+ endpoint: `http://${name}:${port}`,
56
+ region,
57
+ accessKeyId,
58
+ secretAccessKey,
59
+ ...(defaultBucket ? { bucket: defaultBucket } : {}),
60
+ // Path-style (Bun's default) — the store is path-style only.
61
+ }),
62
+ }),
63
+ ...(buckets.length > 0
64
+ ? {
65
+ setup: async ({ name }) => {
66
+ for (const bucket of buckets) {
67
+ // CreateBucket is a plain `PUT` on the bucket root; the store
68
+ // returns 200 (or 409 if it already exists — idempotent enough to
69
+ // ignore).
70
+ const res = await fetch(`http://${name}:${port}/${bucket}`, {
71
+ method: "PUT",
72
+ });
73
+ if (!res.ok && res.status !== 409) {
74
+ throw new Error(`s3: failed to create bucket ${bucket}: ${res.status}`);
75
+ }
76
+ }
77
+ },
78
+ }
79
+ : {}),
80
+ };
81
+ }
@@ -0,0 +1,197 @@
1
+ import { type ServiceGroup, type ServicesMap } from "../index.js";
2
+ import { type SqlClient } from "../sql.js";
3
+ import { type EmailHelpers } from "./email.js";
4
+ export interface SupabaseOptions {
5
+ /**
6
+ * Key of the API-gateway service and the prefix for every other piece.
7
+ * Default `"supabase"`. The gateway is reachable at `http://<name>:8000`
8
+ * and its helpers at `ctx.svc.<name>`; the rest are `<name>-db`,
9
+ * `<name>-auth`, `<name>-rest`, `<name>-storage`, `<name>-realtime`, ….
10
+ */
11
+ name?: string;
12
+ /**
13
+ * Password for the Postgres roles the services authenticate with (and the
14
+ * `postgres` superuser the app / SQL helper connect as). Default
15
+ * `"postgres"`. Purely local to the hermetic VM — not a secret to guard.
16
+ */
17
+ dbPassword?: string;
18
+ /**
19
+ * HS256 secret that signs and verifies all JWTs (user access tokens plus the
20
+ * derived `anonKey` / `serviceRoleKey` API keys). Must be ≥32 chars. Default
21
+ * is Supabase's well-known demo secret. Changing it re-derives the keys
22
+ * deterministically, so the warm-template cache stays stable.
23
+ */
24
+ jwtSecret?: string;
25
+ /** Studio dashboard Basic-Auth username (only relevant with `studio`). Default `"supabase"`. */
26
+ dashboardUsername?: string;
27
+ /** Studio dashboard Basic-Auth password (only relevant with `studio`). */
28
+ dashboardPassword?: string;
29
+ /** Include GoTrue auth (`<name>-auth`). Default `true`. */
30
+ auth?: boolean;
31
+ /** Include storage-api + imgproxy (`<name>-storage`, `<name>-imgproxy`). Default `true`. */
32
+ storage?: boolean;
33
+ /** Include Realtime (`<name>-realtime`). Default `true`. */
34
+ realtime?: boolean;
35
+ /**
36
+ * Include the Studio dashboard (`<name>-studio`) and its postgres-meta
37
+ * backend (`<name>-meta`). Off by default — Studio is a heavy Next.js image
38
+ * and is only useful for interactive `ctx.browser()` exploration, not
39
+ * automated backend assertions. Turning it on implies `meta`.
40
+ */
41
+ studio?: boolean;
42
+ /** Include postgres-meta (`<name>-meta`). Defaults to whatever `studio` is. */
43
+ meta?: boolean;
44
+ /**
45
+ * Also serve the gateway over **HTTPS** at `https://<hostname>` via the
46
+ * daemon's CA-trusted TLS reverse proxy (in addition to the plain
47
+ * `http://<name>:8000`). Set this only when the app under test hardcodes a
48
+ * specific Supabase URL it can't be told to override — otherwise leave it and
49
+ * point the app at `sb.url`.
50
+ */
51
+ hostname?: string;
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.
60
+ *
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
64
+ * an `email()` service the environment already declares — keep a single
65
+ * mailbox per environment rather than one per component: if the app under
66
+ * test also sends mail, share one `email()` between it and Supabase
67
+ * instead of standing up a second capture server. `false` disables mail
68
+ * capture entirely.
69
+ */
70
+ mail?: boolean | SupabaseMailOptions;
71
+ /**
72
+ * Extra API keys the gateway accepts alongside the derived JWT keys — for
73
+ * clients that ship a hardcoded key (e.g. an `sb_publishable_…` /
74
+ * `sb_secret_…` pair baked into an app build). Each `anon` key is admitted
75
+ * with anon privileges and each `serviceRole` key with service-role
76
+ * privileges; Kong swaps the matching *derived JWT* in before proxying, so
77
+ * GoTrue/PostgREST/Realtime still receive a validly-signed token.
78
+ */
79
+ extraApiKeys?: {
80
+ anon?: string[];
81
+ serviceRole?: string[];
82
+ };
83
+ /**
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`).
92
+ * Because `supabase/**` is project content, editing a migration correctly
93
+ * forces a cold rebuild and re-apply (unlike `spectest/tests/**`).
94
+ */
95
+ migrations?: boolean | string;
96
+ /**
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.
100
+ */
101
+ seed?: boolean | string;
102
+ }
103
+ /** Tuning for `SupabaseOptions.mail`. */
104
+ export interface SupabaseMailOptions {
105
+ /**
106
+ * Reuse an `email()` service the environment already declares (its
107
+ * services-map key) instead of adding one to the group — the way to keep a
108
+ * single global mailbox when the app under test sends mail too. Assumes
109
+ * the service's default ports (SMTP 1025, API 8025).
110
+ */
111
+ service?: string;
112
+ /**
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).
117
+ */
118
+ autoconfirm?: boolean;
119
+ /** Sender address GoTrue mails from. Default `admin@example.com`. */
120
+ adminEmail?: string;
121
+ /** Sender display name. Default `Supabase`. */
122
+ senderName?: string;
123
+ }
124
+ /** Helpers the gateway service exposes on `ctx.svc.<name>`. */
125
+ export interface SupabaseHelpers {
126
+ /**
127
+ * An instrumented {@link SqlClient} connected to the database as the
128
+ * `postgres` superuser — every query lands on the test event log and its rows
129
+ * come back provenance-wrapped, so `expect(...)` on a read links to the
130
+ * query. Point of direct DB verification alongside the REST/auth surface.
131
+ */
132
+ sql: SqlClient;
133
+ /** The gateway base URL the app uses as `SUPABASE_URL` (e.g. `http://supabase:8000`). */
134
+ url: string;
135
+ /** Long-lived `anon` API key (HS256 JWT). Pass as the `apikey` header. */
136
+ anonKey: string;
137
+ /** Long-lived `service_role` API key (HS256 JWT). Bypasses RLS — server-side only. */
138
+ serviceRoleKey: string;
139
+ /**
140
+ * The captured-mail mailbox (`lastEmail` / `emails` / `clear`) — present
141
+ * unless the stack was built with `mail: false`. Wait for delivery with
142
+ * the standard `ctx.poll`:
143
+ *
144
+ * ```ts
145
+ * const mail = await ctx.poll("confirmation email", () =>
146
+ * ctx.svc.supabase.mail!.lastEmail());
147
+ * expect(mail.to).toContain("alice@example.com");
148
+ * ```
149
+ */
150
+ mail?: EmailHelpers;
151
+ }
152
+ /** What `supabase()` returns: the service group plus the derived
153
+ * connection surface for wiring the app under test. */
154
+ export interface SupabaseStack {
155
+ /** The Supabase stack as a service group. Mount it at the key matching
156
+ * the `name` option (default `"supabase"`):
157
+ * `services: { supabase: sb.group, ... }`. */
158
+ group: ServiceGroup<ServicesMap, SupabaseHelpers>;
159
+ /** Gateway base URL — `http://<name>:8000`, or `https://<hostname>` when set. */
160
+ url: string;
161
+ /** `anon` API key (HS256 JWT). */
162
+ anonKey: string;
163
+ /** `service_role` API key (HS256 JWT). */
164
+ serviceRoleKey: string;
165
+ /** Direct Postgres URL for the app (`postgres` superuser): `postgresql://postgres:<pw>@<name>-db:5432/postgres`. */
166
+ dbUrl: string;
167
+ /** The gateway service key — use in a dependent service's `dependsOn` to wait
168
+ * for the whole stack (the gateway waits on the services behind it). */
169
+ ready: string;
170
+ /**
171
+ * Where the stack's SMTP capture server listens (absent with
172
+ * `mail: false`) — point the app under test's own mail transport here to
173
+ * share the one mailbox: `env: { SMTP_HOST: sb.smtp.host, SMTP_PORT: String(sb.smtp.port) }`.
174
+ */
175
+ smtp?: {
176
+ host: string;
177
+ port: number;
178
+ };
179
+ /** Ready-to-spread env for the app under test: `SUPABASE_URL`,
180
+ * `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`, `DATABASE_URL`. */
181
+ appEnv: Record<string, string>;
182
+ }
183
+ /**
184
+ * A ready-to-use self-hosted Supabase stack. Mount `.group` at the key
185
+ * matching the `name` option (default `"supabase"`) and wire the app with
186
+ * `.appEnv` / `.ready`:
187
+ *
188
+ * ```ts
189
+ * const sb = supabase({ dbPassword: "postgres" });
190
+ * services: { supabase: sb.group, app: { image, env: sb.appEnv, dependsOn: [sb.ready] } }
191
+ * ```
192
+ *
193
+ * See the file header for the full picture. Tests get an instrumented DB client
194
+ * at `ctx.svc.<name>.sql` plus `sb.url` / `sb.anonKey` / `sb.serviceRoleKey`
195
+ * for hitting the REST/auth/storage APIs through the gateway.
196
+ */
197
+ export declare function supabase(opts?: SupabaseOptions): SupabaseStack;