@sponson/sim 0.0.0-stage → 0.5.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 (78) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +9 -2
  3. package/dist/auth.d.ts +60 -0
  4. package/dist/auth.js +195 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/bin.d.ts +2 -0
  7. package/dist/bin.js +16 -0
  8. package/dist/bin.js.map +1 -0
  9. package/dist/chaos.d.ts +74 -0
  10. package/dist/chaos.js +80 -0
  11. package/dist/chaos.js.map +1 -0
  12. package/dist/cli.d.ts +29 -0
  13. package/dist/cli.js +99 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/demo.d.ts +39 -0
  16. package/dist/demo.js +168 -0
  17. package/dist/demo.js.map +1 -0
  18. package/dist/graphql.d.ts +22 -0
  19. package/dist/graphql.js +36 -0
  20. package/dist/graphql.js.map +1 -0
  21. package/dist/index.d.ts +46 -0
  22. package/dist/index.js +54 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/provider.d.ts +138 -0
  25. package/dist/provider.js +68 -0
  26. package/dist/provider.js.map +1 -0
  27. package/dist/routes/algolia.d.ts +37 -0
  28. package/dist/routes/algolia.js +83 -0
  29. package/dist/routes/algolia.js.map +1 -0
  30. package/dist/routes/aws.d.ts +43 -0
  31. package/dist/routes/aws.js +211 -0
  32. package/dist/routes/aws.js.map +1 -0
  33. package/dist/routes/clerk.d.ts +33 -0
  34. package/dist/routes/clerk.js +85 -0
  35. package/dist/routes/clerk.js.map +1 -0
  36. package/dist/routes/cloudflare.d.ts +80 -0
  37. package/dist/routes/cloudflare.js +176 -0
  38. package/dist/routes/cloudflare.js.map +1 -0
  39. package/dist/routes/digitalocean.d.ts +58 -0
  40. package/dist/routes/digitalocean.js +158 -0
  41. package/dist/routes/digitalocean.js.map +1 -0
  42. package/dist/routes/fly.d.ts +22 -0
  43. package/dist/routes/fly.js +113 -0
  44. package/dist/routes/fly.js.map +1 -0
  45. package/dist/routes/launchdarkly.d.ts +98 -0
  46. package/dist/routes/launchdarkly.js +213 -0
  47. package/dist/routes/launchdarkly.js.map +1 -0
  48. package/dist/routes/neon.d.ts +73 -0
  49. package/dist/routes/neon.js +184 -0
  50. package/dist/routes/neon.js.map +1 -0
  51. package/dist/routes/netlify.d.ts +126 -0
  52. package/dist/routes/netlify.js +347 -0
  53. package/dist/routes/netlify.js.map +1 -0
  54. package/dist/routes/planetscale.d.ts +95 -0
  55. package/dist/routes/planetscale.js +234 -0
  56. package/dist/routes/planetscale.js.map +1 -0
  57. package/dist/routes/railway.d.ts +24 -0
  58. package/dist/routes/railway.js +112 -0
  59. package/dist/routes/railway.js.map +1 -0
  60. package/dist/routes/rest.d.ts +138 -0
  61. package/dist/routes/rest.js +462 -0
  62. package/dist/routes/rest.js.map +1 -0
  63. package/dist/routes/supabase.d.ts +103 -0
  64. package/dist/routes/supabase.js +234 -0
  65. package/dist/routes/supabase.js.map +1 -0
  66. package/dist/routes/vercel.d.ts +114 -0
  67. package/dist/routes/vercel.js +370 -0
  68. package/dist/routes/vercel.js.map +1 -0
  69. package/dist/routes/xata.d.ts +57 -0
  70. package/dist/routes/xata.js +115 -0
  71. package/dist/routes/xata.js.map +1 -0
  72. package/dist/server.d.ts +4 -0
  73. package/dist/server.js +189 -0
  74. package/dist/server.js.map +1 -0
  75. package/dist/state.d.ts +102 -0
  76. package/dist/state.js +121 -0
  77. package/dist/state.js.map +1 -0
  78. package/package.json +51 -3
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Simulated Supabase Management API (https://api.supabase.com/v1): preview branches of a project, and the project's
3
+ * Auth redirect allow-list.
4
+ *
5
+ * Assumptions about the real API that this fake encodes, each marked with how far it is checked. "Verified" means
6
+ * against Supabase's published OpenAPI document (https://api.supabase.com/api/v1-json, fetched 2026-10-11; rendered
7
+ * at https://supabase.com/docs/reference/api/introduction) unless another source is named; see
8
+ * docs/api-verification.md:
9
+ * S1. POST /v1/projects/{ref}/branches takes `{ branch_name }` (required, the only required field) and answers
10
+ * 201 with a BranchResponse: `id` (uuid), `name` (= branch_name), `project_ref` (the branch's own project),
11
+ * `parent_project_ref`, `is_default`, `persistent`, `status`, `created_at` (verified: CreateBranchBody,
12
+ * BranchResponse). That `project_ref` is known in the 201 answer, before provisioning finishes, is implied by
13
+ * the schema (required) but unverified.
14
+ * S2. GET /v1/projects/{ref}/branches answers a bare array of BranchResponse, unpaged (verified: no page
15
+ * parameters, `type: array`). That the list includes the project's own default branch (`is_default: true`,
16
+ * `project_ref` = the parent) once branching is enabled is unverified; the adapter skips `is_default` either
17
+ * way and never manages it.
18
+ * S3. A branch is provisioned asynchronously: GET /v1/branches/{ref} answers BranchDetailResponse whose `status`
19
+ * (the project status enum) is `COMING_UP` until it is ready and `ACTIVE_HEALTHY` after (verified: schema and
20
+ * enum). Here this takes chaos `supabase_ready_ms` (default 0). That the detail answers 200 (not 404) while
21
+ * the branch is coming up, and that `ACTIVE_HEALTHY` is the right "ready" signal, are unverified: needs a
22
+ * live account. The BranchResponse `status` (CREATING_PROJECT, …) is deprecated in the spec and not used.
23
+ * S4. The detail carries `db_host`, `db_port` (required), `db_user` and `db_pass` (optional in the spec,
24
+ * verified). That `db_pass` is returned to a personal access token is unverified (pinned by the contract
25
+ * suite). The database is `postgres` (Supabase's default database name, verified in the connection docs,
26
+ * https://supabase.com/docs/guides/database/connecting-to-postgres) and the API URL is
27
+ * `https://<ref>.supabase.co` (verified: https://supabase.com/docs/guides/api).
28
+ * S5. Creating a branch whose name exists in the project answers 409. Unverified: the spec documents only 201,
29
+ * 401, 403, 429 and 500. The adapter re-reads the list after a 409, or a 400/422, before reporting it.
30
+ * S6. DELETE /v1/branches/{ref} answers 200 `{ message: "ok" }` and the branch is gone from the list at once
31
+ * (verified: BranchDeleteResponse; "by default, deletes immediately"). An unknown ref answers 404:
32
+ * unverified (the spec documents no 404).
33
+ * S7. GET /v1/projects/{ref}/config/auth answers the auth config with `uri_allow_list`, a nullable string
34
+ * (verified: AuthConfigResponse, required); PATCH with `{ uri_allow_list }` alone sets that field and
35
+ * leaves the others, answering the whole config (verified: UpdateAuthConfigBody has no required field).
36
+ * PATCH offers no precondition (no ETag or version), verified by its absence from the spec.
37
+ * S8. The allow-list is comma-separated URLs (verified: Supabase Auth's `URI_ALLOW_LIST`, "a comma separated
38
+ * list of URIs", https://github.com/supabase/auth#general-config). Whitespace around commas is ignored by
39
+ * Auth; whether the API stores the string verbatim (here: yes) is unverified.
40
+ * S9. A preview branch is a project with its own Auth config, read and written through the same
41
+ * /v1/projects/{ref}/config/auth with the branch's ref (`project_ref` is a "Project ref" in the spec,
42
+ * verified; branches have "their own Supabase instance and API credentials", verified in
43
+ * https://supabase.com/docs/guides/deployment/branching). That the endpoint accepts a branch's ref, and
44
+ * what the branch's allow-list starts as (here: empty), are unverified.
45
+ */
46
+ import { Reply, route, router } from "../provider.js";
47
+ /** The parent project the default seed creates, a ref of 20 lowercase letters like Supabase's. */
48
+ export const SUPABASE_DEMO_PROJECT = "demoprojectrefabcdef";
49
+ /** The simulated Supabase Management API, registered in `PROVIDERS` (packages/sim/src/state.ts), served under `/supabase`. */
50
+ export const supabaseSim = {
51
+ env: { token: "SUPABASE_ACCESS_TOKEN", url: "SUPABASE_API_URL", testToken: "tok_supabase" },
52
+ defaultSeed: { projects: { [SUPABASE_DEMO_PROJECT]: { uri_allow_list: "http://localhost:3000/**" } } },
53
+ reset(core, seed) {
54
+ const projects = {};
55
+ for (const [ref, p] of Object.entries(seed?.projects ?? {})) {
56
+ const project = { branches: [], auth: { site_url: p.site_url ?? "http://localhost:3000", uri_allow_list: p.uri_allow_list ?? null } };
57
+ projects[ref] = project;
58
+ // With branching enabled the project itself is listed as its default branch (assumption S2).
59
+ project.branches.push({ ...newSupabaseBranch(core, ref, "main", "sim"), project_ref: ref, is_default: true });
60
+ for (const b of p.branches ?? [])
61
+ project.branches.push(newSupabaseBranch(core, ref, b.name, "sim"));
62
+ }
63
+ return { projects };
64
+ },
65
+ /**
66
+ * `branch.<name>`: "delete" | "recreate" (same name, new ref). `redirect.<url>`: "delete" | "add" — what a human
67
+ * in the Supabase dashboard would do. `supabase:<ref>.` limits either to one project.
68
+ */
69
+ drift(core, state, { key, rest, value, only }) {
70
+ const projects = Object.entries(state.projects).filter(([ref]) => only === undefined || ref === only);
71
+ if (rest.startsWith("branch."))
72
+ return driftBranch(core, projects, key, rest.slice("branch.".length), value);
73
+ if (rest.startsWith("redirect."))
74
+ return driftRedirect(projects, key, rest.slice("redirect.".length), value);
75
+ return false;
76
+ },
77
+ routes(core, state, req) {
78
+ return routes(core, state, req);
79
+ },
80
+ };
81
+ function driftBranch(core, projects, key, name, value) {
82
+ if (value !== "delete" && value !== "recreate")
83
+ throw new Error(`drift ${key}: only "delete" and "recreate" are supported`);
84
+ for (const [ref, p] of projects) {
85
+ const hit = p.branches.filter((b) => b.name === name && !b.is_default);
86
+ p.branches = p.branches.filter((b) => !hit.includes(b));
87
+ if (value === "recreate")
88
+ for (const b of hit)
89
+ p.branches.push(newSupabaseBranch(core, ref, b.name, "sim"));
90
+ }
91
+ return true;
92
+ }
93
+ function driftRedirect(projects, key, url, value) {
94
+ if (value !== "delete" && value !== "add")
95
+ throw new Error(`drift ${key}: only "delete" and "add" are supported`);
96
+ for (const [, p] of projects) {
97
+ const rest = supabaseAllowList(p).filter((u) => u !== url);
98
+ p.auth.uri_allow_list = (value === "add" ? [...rest, url] : rest).join(",");
99
+ }
100
+ return true;
101
+ }
102
+ /** The allow-list's entries: comma-separated, whitespace around them ignored (assumption S8). */
103
+ export function supabaseAllowList(p) {
104
+ return (p.auth.uri_allow_list ?? "")
105
+ .split(",")
106
+ .map((s) => s.trim())
107
+ .filter((s) => s !== "");
108
+ }
109
+ /** The Management API, one route per method and path. Errors are `{ message }`, as Supabase's are. */
110
+ const routes = router([
111
+ route("GET", "/projects/:ref/branches", ({ state, params }) => {
112
+ const p = state.projects[params.ref];
113
+ return p ? new Reply(200, p.branches.map(publicBranch)) : notFound("project");
114
+ }),
115
+ route("POST", "/projects/:ref/branches", ({ core, state, params, body }) => {
116
+ const p = state.projects[params.ref];
117
+ if (!p)
118
+ return notFound("project");
119
+ const name = (body ?? {}).branch_name;
120
+ if (typeof name !== "string" || name === "")
121
+ return new Reply(400, { message: "branch_name must be a non-empty string" });
122
+ if (p.branches.some((b) => b.name === name))
123
+ return new Reply(409, { message: `Branch ${name} already exists` });
124
+ const b = newSupabaseBranch(core, params.ref, name, "api");
125
+ b.readyAt = Date.now() + core.chaos.supabase_ready_ms;
126
+ p.branches.push(b);
127
+ return new Reply(201, publicBranch(b));
128
+ }),
129
+ route("GET", "/branches/:ref", ({ state, params }) => {
130
+ const b = branchByRef(state, params.ref);
131
+ return b ? new Reply(200, branchDetail(b)) : notFound("branch");
132
+ }),
133
+ route("DELETE", "/branches/:ref", ({ state, params }) => {
134
+ const b = branchByRef(state, params.ref);
135
+ if (!b)
136
+ return notFound("branch");
137
+ if (b.is_default)
138
+ return new Reply(400, { message: "the default branch cannot be deleted" });
139
+ const p = state.projects[b.parent_project_ref];
140
+ p.branches = p.branches.filter((x) => x !== b);
141
+ return new Reply(200, { message: "ok" });
142
+ }),
143
+ route("GET", "/projects/:ref/config/auth", ({ state, params }) => {
144
+ const a = authOf(state, params.ref);
145
+ return a ? new Reply(200, authConfig(a)) : notFound("project");
146
+ }),
147
+ route("PATCH", "/projects/:ref/config/auth", ({ state, params, body }) => {
148
+ const a = authOf(state, params.ref);
149
+ if (!a)
150
+ return notFound("project");
151
+ const b = (body ?? {});
152
+ if (b.uri_allow_list !== undefined && b.uri_allow_list !== null && typeof b.uri_allow_list !== "string")
153
+ return new Reply(400, { message: "uri_allow_list must be a string" });
154
+ if (b.uri_allow_list !== undefined)
155
+ a.uri_allow_list = b.uri_allow_list;
156
+ if (typeof b.site_url === "string")
157
+ a.site_url = b.site_url;
158
+ return new Reply(200, authConfig(a));
159
+ }),
160
+ ], () => notFound("route"));
161
+ /** A branch by its own project ref, in any project. */
162
+ function branchByRef(state, ref) {
163
+ for (const p of Object.values(state.projects)) {
164
+ const b = p.branches.find((x) => x.project_ref === ref);
165
+ if (b)
166
+ return b;
167
+ }
168
+ return undefined;
169
+ }
170
+ /**
171
+ * A new (not yet added) branch of project `parent`, ready at once. Tests use it to seed "someone else's" branch.
172
+ * Refs are 20 lowercase letters, deterministic after a reset.
173
+ */
174
+ export function newSupabaseBranch(core, parent, name, createdBy) {
175
+ const n = core.nextId("");
176
+ const ref = `br${[...n].map((d) => "abcdefghij"[Number(d)]).join("")}`.padEnd(20, "x");
177
+ return {
178
+ id: `00000000-0000-4000-8000-${n.padStart(12, "0")}`,
179
+ name,
180
+ project_ref: ref,
181
+ parent_project_ref: parent,
182
+ is_default: false,
183
+ readyAt: 0,
184
+ db_pass: `pw_${ref}`,
185
+ auth: { site_url: "http://localhost:3000", uri_allow_list: null },
186
+ created_at: new Date().toISOString(),
187
+ createdAt: Date.now(),
188
+ createdBy,
189
+ };
190
+ }
191
+ /** The connection string the adapter builds from a branch's detail (assumption S4). */
192
+ export function supabaseConnectionString(b) {
193
+ return `postgresql://postgres:${b.db_pass}@db.${b.project_ref}.supabase.co:5432/postgres`;
194
+ }
195
+ function publicBranch(b) {
196
+ const ready = Date.now() >= b.readyAt;
197
+ return {
198
+ id: b.id,
199
+ name: b.name,
200
+ project_ref: b.project_ref,
201
+ parent_project_ref: b.parent_project_ref,
202
+ is_default: b.is_default,
203
+ persistent: b.is_default,
204
+ status: ready ? "FUNCTIONS_DEPLOYED" : "CREATING_PROJECT",
205
+ preview_project_status: ready ? "ACTIVE_HEALTHY" : "COMING_UP",
206
+ with_data: false,
207
+ created_at: b.created_at,
208
+ updated_at: b.created_at,
209
+ };
210
+ }
211
+ function branchDetail(b) {
212
+ return {
213
+ ref: b.project_ref,
214
+ postgres_version: "17.4.1",
215
+ postgres_engine: "17",
216
+ release_channel: "ga",
217
+ status: Date.now() >= b.readyAt ? "ACTIVE_HEALTHY" : "COMING_UP",
218
+ db_host: `db.${b.project_ref}.supabase.co`,
219
+ db_port: 5432,
220
+ db_user: "postgres",
221
+ db_pass: b.db_pass,
222
+ };
223
+ }
224
+ /** The Auth config of a project ref: a parent project's, or a preview branch's own (assumption S9). */
225
+ function authOf(state, ref) {
226
+ return state.projects[ref]?.auth ?? branchByRef(state, ref)?.auth;
227
+ }
228
+ function authConfig(a) {
229
+ return { site_url: a.site_url, uri_allow_list: a.uri_allow_list, disable_signup: false, jwt_exp: 3600 };
230
+ }
231
+ function notFound(what) {
232
+ return new Reply(404, { message: `${what} not found` });
233
+ }
234
+ //# sourceMappingURL=supabase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supabase.js","sourceRoot":"","sources":["../../src/routes/supabase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Simulated Vercel: project env vars and deployments.
3
+ *
4
+ * Assumptions about the real API that this fake encodes, each marked with how far it is checked. "Verified" means
5
+ * against Vercel's published OpenAPI document (https://openapi.vercel.sh/, fetched 2026-10-10); see
6
+ * docs/api-verification.md for the per-call table. A local fake cannot prove the rest; scenarios/contract.test.ts
7
+ * pins the critical ones against live accounts (`pnpm test:live`):
8
+ * V1. Env writes are visible to the next deployment immediately (no propagation delay). Lists are
9
+ * read-your-writes here; the real list may lag a write, so adapters trust write responses instead.
10
+ * Unverified: needs a live account.
11
+ * V2. GET /v7/deployments?projectId=&sha= filters by commit and returns newest first. The `sha` filter is
12
+ * verified (a v7 query parameter; v6 is not in the spec); items carry `readyState` (required) and `state`
13
+ * (optional), and `url` may be null until the upload completes (verified). The order is unverified: the
14
+ * adapter sorts by createdAt anyway.
15
+ * V3. GET /v10/projects/:id/env?decrypt=true returns plaintext with `decrypted: true`; without `decrypt`,
16
+ * `encrypted` values are opaque. `decrypt` (deprecated), `decrypted` and GET /v1/projects/:id/env/:id
17
+ * ("Retrieve the decrypted value") are verified; whether the deprecated parameter still decrypts is
18
+ * unverified, so the adapter falls back to the per-variable GET. `sensitive` values are never returned
19
+ * (the GET /v1 variant without `value`), so those always diff as `update`.
20
+ * V4. POST /v10/projects/:id/env?upsert=true answers 201 with `{ created, failed }` (verified: `upsert`, the
21
+ * status, `created` as one object or a list, `failed[].error` with `code`, `message`, `key`/`envVarKey`).
22
+ * Unverified: that `created` lists every entry written (new or updated, `createdAt === updatedAt` only for
23
+ * new ones), and that an entry whose key+gitBranch exists with an overlapping but different target set
24
+ * fails the whole request with 400 `ENV_CONFLICT`. Entries named in chaos `env_upsert_fail` come back under
25
+ * `failed` and are not written. Without `upsert` a duplicate answers 403 (verified: documented 403 reason).
26
+ * V5. POST /v13/deployments: `project` overrides `name` and a GitHub `gitSource` needs `ref` plus `repoId` (or
27
+ * `org` + `repo`) — both verified, as is `forceNew`. Deployments go QUEUED → BUILDING → READY over chaos
28
+ * `deploy_ms` (default 0: READY at once; the real API also passes through INITIALIZING, which the adapter
29
+ * handles as in progress). Every deployment has its own URL (verified: "the unique URL of the deployment").
30
+ * `deploy: cancel` cancels a branch's in-progress builds when a new one starts (unverified).
31
+ * V6. Pagination (chaos `page_size`): `{ envs, pagination: { count, next, prev } }` (verified: the spec's
32
+ * Pagination schema). The `?until=` page parameter is unverified for the env list, which documents no page
33
+ * parameter; the adapter drops repeated records, so an API that ignores it costs one extra request.
34
+ * V7. GET /v9/projects/:id carries `link`, the connected repository (`type`, `repoId`, …); verified.
35
+ * V8. GET /v7/deployments accepts `target` (`production` | `preview`) and each item's `target` is `production`,
36
+ * a custom environment name such as `staging`, or null for a preview; verified against the spec's
37
+ * `/v7/deployments` parameters and item schema. The git integration builds previews; production builds
38
+ * of a commit exist separately.
39
+ */
40
+ import { type CreatedBy, type ProviderSim, type SimCore } from "../provider.js";
41
+ /** One simulated Vercel environment variable. */
42
+ export interface VercelEnv {
43
+ id: string;
44
+ key: string;
45
+ value: string;
46
+ target: string[];
47
+ type: "encrypted" | "plain";
48
+ gitBranch?: string;
49
+ createdAt: number;
50
+ updatedAt: number;
51
+ createdBy: CreatedBy;
52
+ }
53
+ /** Vercel's deployment states, as its API reports them. */
54
+ export type DeploymentState = "QUEUED" | "BUILDING" | "READY" | "ERROR" | "CANCELED";
55
+ /** One simulated deployment. */
56
+ export interface VercelDeployment {
57
+ uid: string;
58
+ url: string;
59
+ state: DeploymentState;
60
+ createdAt: number;
61
+ /** While in progress: QUEUED until `buildingAt`, BUILDING until `readyAt`, then `finalState`. */
62
+ buildingAt?: number;
63
+ readyAt?: number;
64
+ finalState?: DeploymentState;
65
+ meta: {
66
+ githubCommitSha: string;
67
+ githubCommitRef?: string;
68
+ };
69
+ /** `production` for production deployments, null for previews. */
70
+ target: "production" | null;
71
+ createdBy: CreatedBy;
72
+ }
73
+ /** The Git repository a project is connected to (`link` in GET /v9/projects/:id); null when it has none. */
74
+ export interface VercelLink {
75
+ type: "github";
76
+ repoId: number;
77
+ org: string;
78
+ repo: string;
79
+ productionBranch: string;
80
+ }
81
+ /** One simulated Vercel project: its variables, deployments and Git connection. */
82
+ export interface VercelProject {
83
+ envs: VercelEnv[];
84
+ deployments: VercelDeployment[];
85
+ link: VercelLink | null;
86
+ }
87
+ /** Simulated Vercel: projects by id. */
88
+ export interface VercelState {
89
+ projects: Record<string, VercelProject>;
90
+ }
91
+ /** Initial Vercel state. */
92
+ export interface VercelSeed {
93
+ projects: Record<string, {
94
+ envs: Array<{
95
+ key: string;
96
+ value: string;
97
+ target: string;
98
+ gitBranch?: string;
99
+ }>;
100
+ }>;
101
+ }
102
+ /** Simulated Vercel (env vars, deployments); see the assumptions at the top of this file. */
103
+ export declare const vercelSim: ProviderSim<VercelState, VercelSeed>;
104
+ /**
105
+ * Every deployment gets its own URL: the first one of a sha is `<project>-<sha8>.vercel.app`, later ones
106
+ * (redeploys) `-2`, `-3`, … `buildMs` > 0 starts it QUEUED and makes it reach `state` over that time.
107
+ */
108
+ export declare function createDeployment(core: SimCore, projectId: string, project: VercelProject, sha: string, state: DeploymentState, createdAt: number, opts?: {
109
+ ref?: string;
110
+ buildMs?: number;
111
+ target?: "production";
112
+ }): VercelDeployment;
113
+ /** Advance in-progress deployments to the state their timings say they are in now. */
114
+ export declare function refreshDeployments(list: VercelDeployment[], now?: number): void;