@sponson/sim 0.4.0 → 0.6.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 (69) hide show
  1. package/README.md +5 -0
  2. package/dist/auth.d.ts +60 -0
  3. package/dist/auth.js +195 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/bin.js +9 -14
  6. package/dist/bin.js.map +1 -1
  7. package/dist/chaos.d.ts +4 -0
  8. package/dist/chaos.js +2 -0
  9. package/dist/chaos.js.map +1 -1
  10. package/dist/cli.d.ts +29 -0
  11. package/dist/cli.js +99 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/demo.d.ts +39 -0
  14. package/dist/demo.js +168 -0
  15. package/dist/demo.js.map +1 -0
  16. package/dist/graphql.d.ts +22 -0
  17. package/dist/graphql.js +36 -0
  18. package/dist/graphql.js.map +1 -0
  19. package/dist/index.d.ts +13 -0
  20. package/dist/index.js +14 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/provider.d.ts +43 -4
  23. package/dist/provider.js +12 -6
  24. package/dist/provider.js.map +1 -1
  25. package/dist/routes/algolia.d.ts +37 -0
  26. package/dist/routes/algolia.js +83 -0
  27. package/dist/routes/algolia.js.map +1 -0
  28. package/dist/routes/aws.d.ts +43 -0
  29. package/dist/routes/aws.js +211 -0
  30. package/dist/routes/aws.js.map +1 -0
  31. package/dist/routes/cloud_run.d.ts +131 -0
  32. package/dist/routes/cloud_run.js +378 -0
  33. package/dist/routes/cloud_run.js.map +1 -0
  34. package/dist/routes/cloudflare.d.ts +80 -0
  35. package/dist/routes/cloudflare.js +176 -0
  36. package/dist/routes/cloudflare.js.map +1 -0
  37. package/dist/routes/digitalocean.d.ts +58 -0
  38. package/dist/routes/digitalocean.js +158 -0
  39. package/dist/routes/digitalocean.js.map +1 -0
  40. package/dist/routes/fly.d.ts +22 -0
  41. package/dist/routes/fly.js +113 -0
  42. package/dist/routes/fly.js.map +1 -0
  43. package/dist/routes/launchdarkly.d.ts +98 -0
  44. package/dist/routes/launchdarkly.js +213 -0
  45. package/dist/routes/launchdarkly.js.map +1 -0
  46. package/dist/routes/netlify.d.ts +126 -0
  47. package/dist/routes/netlify.js +347 -0
  48. package/dist/routes/netlify.js.map +1 -0
  49. package/dist/routes/planetscale.d.ts +95 -0
  50. package/dist/routes/planetscale.js +234 -0
  51. package/dist/routes/planetscale.js.map +1 -0
  52. package/dist/routes/railway.d.ts +24 -0
  53. package/dist/routes/railway.js +112 -0
  54. package/dist/routes/railway.js.map +1 -0
  55. package/dist/routes/rest.d.ts +138 -0
  56. package/dist/routes/rest.js +462 -0
  57. package/dist/routes/rest.js.map +1 -0
  58. package/dist/routes/supabase.d.ts +103 -0
  59. package/dist/routes/supabase.js +234 -0
  60. package/dist/routes/supabase.js.map +1 -0
  61. package/dist/routes/xata.d.ts +57 -0
  62. package/dist/routes/xata.js +115 -0
  63. package/dist/routes/xata.js.map +1 -0
  64. package/dist/server.js +71 -12
  65. package/dist/server.js.map +1 -1
  66. package/dist/state.d.ts +13 -0
  67. package/dist/state.js +14 -1
  68. package/dist/state.js.map +1 -1
  69. package/package.json +5 -1
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Simulated LaunchDarkly: feature flags per project, with each environment's individual targets (a context key of a
3
+ * context kind served one variation).
4
+ *
5
+ * Assumptions about the real API that this fake encodes, each marked with how far it is checked. "Verified" means
6
+ * against LaunchDarkly's published REST API reference (fetched 2026-10-11): the API overview
7
+ * https://launchdarkly.com/docs/api, "Get feature flag" https://launchdarkly.com/docs/api/feature-flags/get-feature-flag
8
+ * and "Update feature flag" https://launchdarkly.com/docs/api/feature-flags/patch-feature-flag; see
9
+ * docs/api-verification.md:
10
+ * LD1. The access token is the whole `Authorization` value, with no `Bearer` prefix (verified: "The value of the
11
+ * `Authorization` header must be your access token"). The base URL is https://app.launchdarkly.com/api/v2
12
+ * (verified from the OpenAPI document's location; federal and EU instances use other hosts). The sim
13
+ * refuses a `Bearer` header with 401.
14
+ * LD2. `GET /flags/{projectKey}/{featureFlagKey}?env=<key>` answers the flag with `variations[]` (`_id`, `value`,
15
+ * optional `name`) and `environments.<key>` restricted to that environment (verified). An unknown project or
16
+ * flag is 404 (verified: "Invalid resource identifier"). An environment the project lacks is absent from
17
+ * `environments` (unverified: whether LaunchDarkly answers 404 or an empty map instead; the adapter
18
+ * reports either as not found).
19
+ * LD3. An environment's individual targets are `targets[]` (context kind `user`) and `contextTargets[]` (every
20
+ * other kind), items `{ contextKind, values, variation }` where `variation` is an index into `variations`
21
+ * (verified). `contextTargets` also lists, for each variation with user targets, a placeholder
22
+ * `{ contextKind: "user", values: [] }` (unverified, observed behaviour reported by SDK users; the adapter
23
+ * reads both arrays and ignores empty entries, so it does not depend on it).
24
+ * LD4. `PATCH /flags/{projectKey}/{featureFlagKey}` with `Content-Type: application/json;
25
+ * domain-model=launchdarkly.semanticpatch` and `{ environmentKey, instructions, comment? }` applies the
26
+ * instructions; without that media type the body is read as a JSON patch and a semantic patch is 400
27
+ * (verified). It answers 200 with the whole flag (verified). Instructions are all or nothing: one invalid
28
+ * instruction and nothing changes (verified).
29
+ * LD5. `addTargets { contextKind, values, variationId }` (`contextKind` defaults to `user`) is refused (400)
30
+ * when it would make a context key targeted by two variations (verified that it is an error; the status
31
+ * is unverified). Adding a key already targeted by the same variation changes nothing and succeeds
32
+ * (unverified: the docs say only that a semantic patch updates the flag "only where needed"; pinned by the
33
+ * contract suite). `removeTargets` with the same fields does nothing for a key not targeted (verified).
34
+ * LD6. An environment that requires approvals answers 405 to a flag change (verified); a concurrent change
35
+ * answers 409, to be retried (verified). The error body is `{ code, message }` (verified).
36
+ * LD7. The PATCH takes effect at once: the next GET shows it (unverified; pinned by the contract suite).
37
+ */
38
+ import { type CreatedBy, type ProviderSim } from "../provider.js";
39
+ /** The media type of a semantic patch (LD4). */
40
+ export declare const LAUNCHDARKLY_SEMANTIC_PATCH = "application/json; domain-model=launchdarkly.semanticpatch";
41
+ /** One variation of a flag. */
42
+ export interface LaunchdarklyVariation {
43
+ _id: string;
44
+ value: unknown;
45
+ name?: string;
46
+ }
47
+ /** One individual target: a context key of a kind, served the variation at `variation` (an index). */
48
+ export interface LaunchdarklyTarget {
49
+ contextKind: string;
50
+ key: string;
51
+ variation: number;
52
+ createdBy: CreatedBy;
53
+ }
54
+ /** A flag's configuration in one environment, as far as the sim models it. */
55
+ export interface LaunchdarklyFlagEnv {
56
+ on: boolean;
57
+ version: number;
58
+ targets: LaunchdarklyTarget[];
59
+ }
60
+ /** One feature flag. */
61
+ export interface LaunchdarklyFlag {
62
+ key: string;
63
+ variations: LaunchdarklyVariation[];
64
+ version: number;
65
+ environments: Record<string, LaunchdarklyFlagEnv>;
66
+ }
67
+ /** One project: its environments (and which need approvals) and its flags. */
68
+ export interface LaunchdarklyProject {
69
+ environments: string[];
70
+ approvals: string[];
71
+ flags: Record<string, LaunchdarklyFlag>;
72
+ }
73
+ /** The simulated LaunchDarkly's state: projects by key. */
74
+ export interface LaunchdarklyState {
75
+ projects: Record<string, LaunchdarklyProject>;
76
+ }
77
+ /** What a scenario or test seeds the simulated LaunchDarkly with (`seed: { launchdarkly: … }`). */
78
+ export interface LaunchdarklySeed {
79
+ projects: Record<string, {
80
+ environments?: string[];
81
+ /** Environments whose flag changes need an approval request (LD6). */
82
+ approvals?: string[];
83
+ flags?: Record<string, {
84
+ variations?: Array<{
85
+ value: unknown;
86
+ name?: string;
87
+ }>;
88
+ /** Environment key → targets that exist already (made "by a human", so unmanaged). */
89
+ targets?: Record<string, Array<{
90
+ contextKind?: string;
91
+ key: string;
92
+ variation: number;
93
+ }>>;
94
+ }>;
95
+ }>;
96
+ }
97
+ /** The simulated LaunchDarkly API, registered in `PROVIDERS` (packages/sim/src/state.ts) and served under `/launchdarkly`. */
98
+ export declare const launchdarklySim: ProviderSim<LaunchdarklyState, LaunchdarklySeed>;
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Simulated LaunchDarkly: feature flags per project, with each environment's individual targets (a context key of a
3
+ * context kind served one variation).
4
+ *
5
+ * Assumptions about the real API that this fake encodes, each marked with how far it is checked. "Verified" means
6
+ * against LaunchDarkly's published REST API reference (fetched 2026-10-11): the API overview
7
+ * https://launchdarkly.com/docs/api, "Get feature flag" https://launchdarkly.com/docs/api/feature-flags/get-feature-flag
8
+ * and "Update feature flag" https://launchdarkly.com/docs/api/feature-flags/patch-feature-flag; see
9
+ * docs/api-verification.md:
10
+ * LD1. The access token is the whole `Authorization` value, with no `Bearer` prefix (verified: "The value of the
11
+ * `Authorization` header must be your access token"). The base URL is https://app.launchdarkly.com/api/v2
12
+ * (verified from the OpenAPI document's location; federal and EU instances use other hosts). The sim
13
+ * refuses a `Bearer` header with 401.
14
+ * LD2. `GET /flags/{projectKey}/{featureFlagKey}?env=<key>` answers the flag with `variations[]` (`_id`, `value`,
15
+ * optional `name`) and `environments.<key>` restricted to that environment (verified). An unknown project or
16
+ * flag is 404 (verified: "Invalid resource identifier"). An environment the project lacks is absent from
17
+ * `environments` (unverified: whether LaunchDarkly answers 404 or an empty map instead; the adapter
18
+ * reports either as not found).
19
+ * LD3. An environment's individual targets are `targets[]` (context kind `user`) and `contextTargets[]` (every
20
+ * other kind), items `{ contextKind, values, variation }` where `variation` is an index into `variations`
21
+ * (verified). `contextTargets` also lists, for each variation with user targets, a placeholder
22
+ * `{ contextKind: "user", values: [] }` (unverified, observed behaviour reported by SDK users; the adapter
23
+ * reads both arrays and ignores empty entries, so it does not depend on it).
24
+ * LD4. `PATCH /flags/{projectKey}/{featureFlagKey}` with `Content-Type: application/json;
25
+ * domain-model=launchdarkly.semanticpatch` and `{ environmentKey, instructions, comment? }` applies the
26
+ * instructions; without that media type the body is read as a JSON patch and a semantic patch is 400
27
+ * (verified). It answers 200 with the whole flag (verified). Instructions are all or nothing: one invalid
28
+ * instruction and nothing changes (verified).
29
+ * LD5. `addTargets { contextKind, values, variationId }` (`contextKind` defaults to `user`) is refused (400)
30
+ * when it would make a context key targeted by two variations (verified that it is an error; the status
31
+ * is unverified). Adding a key already targeted by the same variation changes nothing and succeeds
32
+ * (unverified: the docs say only that a semantic patch updates the flag "only where needed"; pinned by the
33
+ * contract suite). `removeTargets` with the same fields does nothing for a key not targeted (verified).
34
+ * LD6. An environment that requires approvals answers 405 to a flag change (verified); a concurrent change
35
+ * answers 409, to be retried (verified). The error body is `{ code, message }` (verified).
36
+ * LD7. The PATCH takes effect at once: the next GET shows it (unverified; pinned by the contract suite).
37
+ */
38
+ import { Reply, route, router } from "../provider.js";
39
+ /** The media type of a semantic patch (LD4). */
40
+ export const LAUNCHDARKLY_SEMANTIC_PATCH = "application/json; domain-model=launchdarkly.semanticpatch";
41
+ const BOOLEAN = [
42
+ { value: true, name: "on" },
43
+ { value: false, name: "off" },
44
+ ];
45
+ /** The simulated LaunchDarkly API, registered in `PROVIDERS` (packages/sim/src/state.ts) and served under `/launchdarkly`. */
46
+ export const launchdarklySim = {
47
+ env: { token: "LAUNCHDARKLY_ACCESS_TOKEN", url: "LAUNCHDARKLY_API_URL", testToken: "api-sim-launchdarkly" },
48
+ // LD1: the token alone in Authorization; a `Bearer` header is refused, so an adapter that sends one fails its tests.
49
+ authorized: (headers) => /^\S+$/.test(headers.authorization ?? "") && !/^Bearer$/i.test(headers.authorization ?? ""),
50
+ defaultSeed: { projects: { demo: { environments: ["preview", "production"], flags: { "new-checkout": {} } } } },
51
+ reset(core, seed) {
52
+ const projects = {};
53
+ for (const [key, p] of Object.entries(seed?.projects ?? {})) {
54
+ const environments = p.environments ?? ["preview", "production"];
55
+ const flags = {};
56
+ for (const [flagKey, f] of Object.entries(p.flags ?? {}))
57
+ flags[flagKey] = flag(core, flagKey, environments, f);
58
+ projects[key] = { environments, approvals: p.approvals ?? [], flags };
59
+ }
60
+ return { projects };
61
+ },
62
+ /**
63
+ * `target.<flag>/<environment>/<contextKind>/<key>` (or `launchdarkly:<project>.target.…`): "delete", or a
64
+ * variation name or index to move the target to — what a human in the LaunchDarkly console would do.
65
+ */
66
+ drift(_core, state, { key, rest, value, only }) {
67
+ if (!rest.startsWith("target."))
68
+ return false;
69
+ const [flagKey, envKey, kind, ...target] = rest.slice("target.".length).split("/");
70
+ const contextKey = target.join("/");
71
+ let hit = false;
72
+ for (const [id, p] of Object.entries(state.projects)) {
73
+ if (only !== undefined && id !== only)
74
+ continue;
75
+ const f = p.flags[flagKey ?? ""];
76
+ const env = f?.environments[envKey ?? ""];
77
+ const t = env?.targets.find((x) => x.contextKind === kind && x.key === contextKey);
78
+ if (!f || !env || !t)
79
+ continue;
80
+ hit = true;
81
+ if (value === "delete")
82
+ env.targets = env.targets.filter((x) => x !== t);
83
+ else
84
+ t.variation = variationIndex(f, value, key);
85
+ env.version++;
86
+ }
87
+ if (!hit)
88
+ throw new Error(`drift ${key}: no such target`);
89
+ return true;
90
+ },
91
+ routes(core, state, req) {
92
+ return routes(core, state, req);
93
+ },
94
+ };
95
+ function variationIndex(f, value, key) {
96
+ const byName = f.variations.findIndex((v) => v.name === value);
97
+ if (byName >= 0)
98
+ return byName;
99
+ if (/^\d+$/.test(value) && Number(value) < f.variations.length)
100
+ return Number(value);
101
+ throw new Error(`drift ${key}: flag ${f.key} has no variation ${value}`);
102
+ }
103
+ /** The API, one route per method and path, under `/api/v2` (part of the base URL). */
104
+ const routes = router([
105
+ route("GET", "/flags/:project/:flag", ({ state, params, url }) => {
106
+ const f = state.projects[params.project]?.flags[params.flag];
107
+ if (!f)
108
+ return ldError(404, "not_found", "Unknown resource");
109
+ return new Reply(200, publicFlag(f, url.searchParams.get("env")));
110
+ }),
111
+ route("PATCH", "/flags/:project/:flag", ({ state, params, body, headers }) => {
112
+ if (!(headers["content-type"] ?? "").replace(/\s+/g, "").includes("domain-model=launchdarkly.semanticpatch")) {
113
+ return ldError(400, "invalid_request", "Invalid JSON patch: expected an array of operations");
114
+ }
115
+ const p = state.projects[params.project];
116
+ const f = p?.flags[params.flag];
117
+ if (!p || !f)
118
+ return ldError(404, "not_found", "Unknown resource");
119
+ const b = (body ?? {});
120
+ if (typeof b.environmentKey !== "string" || !f.environments[b.environmentKey])
121
+ return ldError(400, "invalid_request", "environmentKey is required and must name an environment");
122
+ if (!Array.isArray(b.instructions) || b.instructions.length === 0)
123
+ return ldError(400, "invalid_request", "instructions are required");
124
+ if (p.approvals.includes(b.environmentKey))
125
+ return ldError(405, "method_not_allowed", "This environment requires approval for changes to flags");
126
+ const env = f.environments[b.environmentKey];
127
+ // All or nothing (LD4): work on a copy, keep it only if every instruction was valid.
128
+ const next = env.targets.map((t) => ({ ...t }));
129
+ for (const ins of b.instructions) {
130
+ const problem = applyInstruction(f, next, ins);
131
+ if (problem)
132
+ return ldError(400, "invalid_request", problem);
133
+ }
134
+ if (JSON.stringify(next) !== JSON.stringify(env.targets)) {
135
+ env.targets = next;
136
+ env.version++;
137
+ f.version++;
138
+ }
139
+ return new Reply(200, publicFlag(f, null));
140
+ }),
141
+ ], () => ldError(404, "not_found", "Unknown resource"));
142
+ /** One `addTargets` / `removeTargets` instruction against `targets`; a message when it is invalid. */
143
+ function applyInstruction(f, targets, ins) {
144
+ const i = parseInstruction(f, ins);
145
+ if (typeof i === "string")
146
+ return i;
147
+ const { contextKind, variation } = i;
148
+ for (const key of i.values) {
149
+ const at = targets.findIndex((t) => t.contextKind === contextKind && t.key === key);
150
+ if (i.kind === "removeTargets") {
151
+ if (at >= 0 && targets[at].variation === variation)
152
+ targets.splice(at, 1);
153
+ continue;
154
+ }
155
+ if (at >= 0 && targets[at].variation !== variation)
156
+ return `${contextKind} ${key} is already targeted by another variation`;
157
+ if (at < 0)
158
+ targets.push({ contextKind, key, variation, createdBy: "api" });
159
+ }
160
+ return null;
161
+ }
162
+ /** One instruction, validated; a message when it is not one the sim implements or is malformed. */
163
+ function parseInstruction(f, ins) {
164
+ const i = (ins ?? {});
165
+ if (i.kind !== "addTargets" && i.kind !== "removeTargets")
166
+ return `unsupported instruction kind ${String(i.kind)}`;
167
+ const variation = f.variations.findIndex((v) => v._id === i.variationId);
168
+ if (variation < 0)
169
+ return `unknown variationId ${String(i.variationId)}`;
170
+ if (!Array.isArray(i.values) || i.values.some((v) => typeof v !== "string"))
171
+ return "values must be a list of context keys";
172
+ const contextKind = typeof i.contextKind === "string" && i.contextKind !== "" ? i.contextKind : "user";
173
+ return { kind: i.kind, contextKind, values: i.values, variation };
174
+ }
175
+ /** The flag representation (LD2, LD3), restricted to one environment when `env` names one. */
176
+ function publicFlag(f, env) {
177
+ const environments = {};
178
+ for (const [key, e] of Object.entries(f.environments)) {
179
+ if (env !== null && key !== env)
180
+ continue;
181
+ const user = groups(e.targets.filter((t) => t.contextKind === "user"));
182
+ const other = groups(e.targets.filter((t) => t.contextKind !== "user"));
183
+ const placeholders = user.map((g) => ({ contextKind: "user", values: [], variation: g.variation }));
184
+ environments[key] = { on: e.on, version: e.version, _environmentName: key, targets: user, contextTargets: [...other, ...placeholders] };
185
+ }
186
+ return { key: f.key, _version: f.version, variations: f.variations, environments };
187
+ }
188
+ /** Targets grouped the way LaunchDarkly lists them: one entry per (kind, variation). */
189
+ function groups(targets) {
190
+ const out = [];
191
+ for (const t of targets) {
192
+ const g = out.find((x) => x.contextKind === t.contextKind && x.variation === t.variation);
193
+ if (g)
194
+ g.values.push(t.key);
195
+ else
196
+ out.push({ contextKind: t.contextKind, values: [t.key], variation: t.variation });
197
+ }
198
+ return out;
199
+ }
200
+ function flag(core, key, environments, seed) {
201
+ const variations = (seed.variations ?? BOOLEAN).map((v) => ({ _id: core.nextId("var_"), value: v.value, ...(v.name !== undefined ? { name: v.name } : {}) }));
202
+ const envs = {};
203
+ for (const e of environments) {
204
+ const targets = (seed.targets?.[e] ?? []).map((t) => ({ contextKind: t.contextKind ?? "user", key: t.key, variation: t.variation, createdBy: "sim" }));
205
+ envs[e] = { on: true, version: 1, targets };
206
+ }
207
+ return { key, variations, version: 1, environments: envs };
208
+ }
209
+ /** LaunchDarkly's error body (LD6). */
210
+ function ldError(status, code, message) {
211
+ return new Reply(status, { code, message });
212
+ }
213
+ //# sourceMappingURL=launchdarkly.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launchdarkly.js","sourceRoot":"","sources":["../../src/routes/launchdarkly.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Simulated Netlify: site environment variables (one object per key, one value per deploy context) and deploys.
3
+ *
4
+ * Assumptions about the real API that this fake encodes. "Verified" means against Netlify's OpenAPI document
5
+ * (https://github.com/netlify/open-api/blob/master/swagger.yml, rendered at https://open-api.netlify.com/) or its
6
+ * docs (cited), fetched 2026-10-11; docs/api-verification.md has the per-call table. scenarios/contract.test.ts pins
7
+ * the risky ones against a live account (`pnpm test:live`). NL, not N: N… are Neon's.
8
+ * NL1. Base URL https://api.netlify.com/api/v1, bearer token (OpenAPI `host`, `basePath`; the API guide's
9
+ * `Authorization: Bearer`). Verified.
10
+ * NL2. Site variables live under /accounts/{account_id}/env with `?site_id=`: GET lists them (getEnvVars), POST
11
+ * creates a list of them (createEnvVars, 201), GET/PATCH/DELETE /accounts/{account_id}/env/{key} read one,
12
+ * set one context's value (setEnvVarValue, 201: "updates or creates a new value for an existing
13
+ * environment variable") and delete the variable (204); DELETE …/env/{key}/value/{id} deletes one value
14
+ * (deleteEnvVarValue, 204). Verified (OpenAPI paths, statuses; the docs' API guide). `{account_id}` may be
15
+ * the account slug (verified: the API guide). GET /sites/{site_id} carries `account_id`, `account_slug` and
16
+ * `name` (verified field names; that `name` is the `<name>.netlify.app` subdomain is the docs' example, not a
17
+ * statement: unverified, pinned).
18
+ * NL3. A variable is `{ key, scopes, values: [{ id, value, context, context_parameter }], is_secret, updated_at }`;
19
+ * `context` is all | dev | dev-server | branch-deploy | deploy-preview | production | branch, and `branch`
20
+ * needs the branch name in `context_parameter` (verified: envVar / envVarValue definitions). Omitting
21
+ * `scopes` gives all scopes (verified: "By default, environment variables apply to all scopes", env var
22
+ * overview). Unverified: that PATCH leaves the other contexts' values (and their ids) alone and keeps the id
23
+ * of the value it replaces (the sim keeps it; the adapter re-reads ids from the answer anyway).
24
+ * NL4. Secret values (`is_secret`) are write-only except in the `dev` context (verified: Secrets Controller
25
+ * docs). How a masked value is returned is not documented; the sim omits `value` (unverified; the adapter
26
+ * ignores `value` of a secret either way).
27
+ * NL5. POST of a key that already exists is refused with 400 and writes nothing (unverified: status not
28
+ * documented). A variable with an `all` value and a context value together is refused with 400
29
+ * (unverified). The adapter re-lists after a 400/409 and never writes next to an `all` value.
30
+ * NL6. A variable whose last value is deleted stays, with no values (unverified; the adapter deletes it then, and
31
+ * a 404 on the follow-up read is fine).
32
+ * NL7. GET /sites/{site_id}/deploys lists deploys newest first, `page`/`per_page` (verified: the API guide;
33
+ * order unverified, the adapter sorts by `created_at`). A deploy carries `id`, `state`, `context`,
34
+ * `commit_ref`, `review_id`, `branch`, `created_at` (verified field names); `context` is production |
35
+ * deploy-preview | branch-deploy (unverified: no enum in the spec) and `review_id` is the pull request
36
+ * number (unverified).
37
+ * NL8. Deploy states: new, pending_review, accepted, rejected, enqueued, building, uploading, uploaded,
38
+ * preparing, prepared, processing, processed, ready, error, retrying (verified: the `state` filter's enum).
39
+ * The sim uses enqueued → building → ready, or error.
40
+ * NL9. URLs: `<deploy id>--<site name>.netlify.app` is a deploy's permalink and `deploy-preview-<n>--<site
41
+ * name>.netlify.app` the Deploy Preview of pull request n (verified: deploy overview docs).
42
+ * NL10. A build reads the variable values set when it started ("Environment variable changes require a build
43
+ * and deploy to take effect": verified, env var docs); `updated_at` is when a variable's values last
44
+ * changed (verified field; that a PATCH bumps it is unverified, pinned).
45
+ * NL11. POST /sites/{site_id}/builds starts a build of the production branch, or with `?branch=` a branch deploy
46
+ * of that branch's head (verified: createSiteBuild's `branch` description). Unverified: that it works for
47
+ * a branch whose branch deploys are not enabled in the site's settings. Deploy Previews cannot be rebuilt
48
+ * through the documented API.
49
+ * NL12. A `branch` value applies to "deploy permalinks, Deploy Previews, and branch deploys for the specified
50
+ * branch" (verified: env var overview), so a pull request's Deploy Preview reads its head branch's value.
51
+ *
52
+ * The git integration is modelled by `pushes` in the seed: the first deploy list of a site deploys every push it
53
+ * has not deployed yet (a pull request push as a Deploy Preview, `main` as production, other branches as branch
54
+ * deploys), following chaos `deploy` (ok | never | fail | stale | delay:<s>) and `deploy_ms` as Vercel's sim does.
55
+ */
56
+ import { type CreatedBy, type ProviderSim } from "../provider.js";
57
+ /** One value of a simulated variable. */
58
+ export interface NetlifyEnvValue {
59
+ id: string;
60
+ value: string;
61
+ context: string;
62
+ context_parameter?: string;
63
+ }
64
+ /** One simulated site variable. */
65
+ export interface NetlifyEnvVar {
66
+ key: string;
67
+ is_secret: boolean;
68
+ values: NetlifyEnvValue[];
69
+ updatedAt: number;
70
+ createdBy: CreatedBy;
71
+ }
72
+ /** One simulated deploy. */
73
+ export interface NetlifyDeploy {
74
+ id: string;
75
+ state: string;
76
+ context: "production" | "deploy-preview" | "branch-deploy";
77
+ branch: string;
78
+ commit_ref: string;
79
+ review_id: number | null;
80
+ createdAt: number;
81
+ /** While building: `building` until `readyAt`, then `finalState`. */
82
+ readyAt?: number;
83
+ finalState?: string;
84
+ createdBy: CreatedBy;
85
+ }
86
+ /** A commit the git integration has seen: what Netlify would build on its own. */
87
+ export interface NetlifyPush {
88
+ sha: string;
89
+ branch: string;
90
+ pr?: number;
91
+ }
92
+ /** One simulated site. */
93
+ export interface NetlifySite {
94
+ name: string;
95
+ accountId: string;
96
+ accountSlug: string;
97
+ envs: NetlifyEnvVar[];
98
+ deploys: NetlifyDeploy[];
99
+ pushes: NetlifyPush[];
100
+ /** Pushes already deployed by the git integration (by `<sha> <branch>`). */
101
+ built: string[];
102
+ }
103
+ /** The simulated Netlify's state: sites by id. */
104
+ export interface NetlifyState {
105
+ sites: Record<string, NetlifySite>;
106
+ }
107
+ /** What a scenario or test seeds the simulated Netlify with (`seed: { netlify: … }`). */
108
+ export interface NetlifySeed {
109
+ sites: Record<string, {
110
+ name?: string;
111
+ account?: string;
112
+ /** Variables that exist already (set "by a human", so unmanaged). */
113
+ envs?: Array<{
114
+ key: string;
115
+ is_secret?: boolean;
116
+ values: Array<{
117
+ context: string;
118
+ value: string;
119
+ context_parameter?: string;
120
+ }>;
121
+ }>;
122
+ pushes?: NetlifyPush[];
123
+ }>;
124
+ }
125
+ /** The simulated Netlify API (env vars, deploys), registered in `PROVIDERS` and served under `/netlify`. */
126
+ export declare const netlifySim: ProviderSim<NetlifyState, NetlifySeed>;