@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,378 @@
1
+ /**
2
+ * Simulated Google Cloud Run Admin API v2: services whose every template change makes an immutable revision, their
3
+ * traffic targets and tags, and the long-running operations a patch answers with.
4
+ *
5
+ * Assumptions about the real API this fake encodes, checked against the official REST reference and discovery
6
+ * document (https://run.googleapis.com/$discovery/rest?version=v2, called DISC below) unless marked unverified. The
7
+ * critical ones are pinned in scenarios/contract.test.ts (`assumption CR…`).
8
+ * CR1. The API is `https://run.googleapis.com/v2`; every method takes an OAuth access token with the
9
+ * `cloud-platform` scope as `Authorization: Bearer`, which `gcloud auth print-access-token` prints for the
10
+ * active account. (verified: DISC; https://docs.cloud.google.com/sdk/gcloud/reference/auth/print-access-token)
11
+ * CR2. `GET /v2/projects/{project}/locations/{region}/services/{service}` answers the Service: `etag` ("may be used
12
+ * to detect modification conflict during updates"), `template`, `traffic`, `trafficStatuses`,
13
+ * `latestReadyRevision`, `latestCreatedRevision`, `reconciling`, and `generation` as a JSON string (int64).
14
+ * (verified: DISC, https://docs.cloud.google.com/run/docs/reference/rest/v2/projects.locations.services)
15
+ * CR3. `PATCH /v2/{service.name}?updateMask=<fields>` takes a Service and answers a long-running Operation; only the
16
+ * fields the mask names are written. (verified: https://docs.cloud.google.com/run/docs/reference/rest/v2/projects.locations.services/patch;
17
+ * the sim refuses a patch without a mask, which the adapter never sends)
18
+ * CR4. Each change of `template` makes a new, immutable revision; `template.revision` names it ("If this field is
19
+ * omitted, it will be automatically generated based on the Service name"). A name starts with the service's
20
+ * name and `-`, has only lowercase letters, digits and `-`, does not end with `-` and is at most 63 characters.
21
+ * (verified: DISC; https://docs.cloud.google.com/run/docs/configuring/services/containers) A name already
22
+ * taken is refused (unverified: the sim answers 409 ALREADY_EXISTS).
23
+ * CR5. A TrafficTarget is `{ type, revision, percent, tag }`, `type` `TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST` or
24
+ * `…_REVISION`, `percent` zero when left out; empty `traffic` means 100% to the latest ready revision. LATEST
25
+ * follows the latest ready revision, so a template change alone moves its share to the new revision; gcloud's
26
+ * `--no-traffic` "assigns any traffic assigned to the LATEST revision to the specific revision bound to LATEST
27
+ * before the deployment", after which "the LATEST revision will not receive traffic on future deployments".
28
+ * (verified: DISC; https://docs.cloud.google.com/sdk/gcloud/reference/run/deploy)
29
+ * CR6. A `tag` gives its revision a URL of its own with no traffic ("test the new revision at a specific URL,
30
+ * without serving traffic"), shown in `trafficStatuses[].uri` ("Displays the target URI"); its form is not to be
31
+ * parsed ("Don't parse the SERVICE_IDENTIFIER"), so it is read, never built. (verified:
32
+ * https://docs.cloud.google.com/run/docs/rollouts-rollbacks-traffic-migration,
33
+ * https://docs.cloud.google.com/run/docs/triggering/https-request)
34
+ * CR7. Whether `latestReadyRevision`, `latestCreatedRevision` and `TrafficTarget.revision` hold a revision's short
35
+ * name or its full resource name is not documented (unverified): clients take the last path segment; the sim
36
+ * answers full names in `latest*Revision` and the names it was sent in `traffic`.
37
+ * CR8. The `etag` sent in a patch body is a precondition: a patch whose etag is no longer the service's is refused
38
+ * and changes nothing. (unverified: the docs say only that it "may be used to detect modification conflict";
39
+ * the sim answers 409 ABORTED)
40
+ * CR9. `GET /v2/{name=projects/*\/locations/*\/operations/*}` answers `{ name, done, error, response }`; when `done`,
41
+ * "either `error` or `response` is available". (verified: DISC) The operation of a template change ends when
42
+ * the new revision is ready, or with an `error` when it cannot become ready (unverified: the sim ends it after
43
+ * `operationPolls` polls, failing it for an image in `failImages`).
44
+ * CR10. `GET …/services/{service}/revisions/{revision}` answers the Revision: `containers` (`name`, `image`, `env`
45
+ * of `{ name, value }` or `{ name, valueSource: { secretKeyRef } }`) and `conditions` of `{ type, state }`,
46
+ * where type "Ready" is "True when the Resource is ready" and state is CONDITION_SUCCEEDED, CONDITION_FAILED,
47
+ * CONDITION_PENDING or CONDITION_RECONCILING. (verified: DISC)
48
+ * CR11. Once an operation has succeeded, the Service answers the new `traffic` and its `trafficStatuses`
49
+ * (`reconciling` false: then they "will match"). (verified: DISC, `reconciling`)
50
+ * CR12. Cloud Run deletes revisions by itself: past 1000 per service, "non-addressable revisions are automatically
51
+ * deleted in historical order"; past 2000 tags per project and region, "tags that don't have a specified
52
+ * traffic percentage are automatically deleted". (verified: https://docs.cloud.google.com/run/quotas) Whether
53
+ * a tagged revision counts as addressable is not documented. The sim does not delete anything by itself.
54
+ * CR13. A tag is lowercase (the only documented rule, "your lowercase tag name"); the sim also refuses one that is
55
+ * not a DNS label (`^[a-z]([a-z0-9-]*[a-z0-9])?$`) or that two targets share. (unverified)
56
+ */
57
+ import { Reply, route, router } from "../provider.js";
58
+ const LATEST = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST";
59
+ const BY_REVISION = "TRAFFIC_TARGET_ALLOCATION_TYPE_REVISION";
60
+ const DEFAULT_IMAGE = "us-docker.pkg.dev/demo-project/web/app:main";
61
+ /** The simulated Cloud Run Admin API, registered in `PROVIDERS` (packages/sim/src/state.ts) and served under `/cloud_run`. */
62
+ export const cloudRunSim = {
63
+ // The adapter takes its token from `gcloud auth print-access-token` (CR1); this variable is only the placeholder.
64
+ env: { token: "CLOUD_RUN_SIM_ACCESS_TOKEN", url: "CLOUD_RUN_API_URL", testToken: "tok_cloud_run" },
65
+ defaultSeed: { services: [{ name: "web", env: { NODE_ENV: "production", DATABASE_URL: "postgres://prod.example.com/app" } }] },
66
+ reset(core, seed) {
67
+ const state = { services: [], revisions: [], operations: [], operationPolls: seed?.operationPolls ?? 1, failImages: [...(seed?.failImages ?? [])] };
68
+ for (const s of seed?.services ?? [])
69
+ seedService(core, state, s);
70
+ return state;
71
+ },
72
+ /**
73
+ * `service.<name>.tag.<tag>.env.<NAME>`: someone deploys the tagged revision again with that variable changed and
74
+ * the tag moved to the new revision (`gcloud run deploy --no-traffic --tag <tag> --update-env-vars`);
75
+ * `service.<name>.tag.<tag>` = "delete": the tag removed (`update-traffic --remove-tags`); `service.<name>.deploy`
76
+ * = an image: a deploy of the service's template with that image (traffic as it stands, CR5).
77
+ */
78
+ drift(core, state, { key, rest, value, only }) {
79
+ if (only !== undefined || !rest.startsWith("service."))
80
+ return false;
81
+ const [name, kind, tag, sub, envName] = rest.slice("service.".length).split(".");
82
+ const svc = state.services.find((s) => s.name === name);
83
+ if (!svc)
84
+ throw new Error(`drift ${key}: no service ${String(name)}`);
85
+ if (kind === "deploy" && tag === undefined)
86
+ deployImage(core, state, svc, value);
87
+ else if (kind === "tag" && tag && sub === undefined && value === "delete")
88
+ removeTag(core, svc, tag);
89
+ else if (kind === "tag" && tag && sub === "env" && envName)
90
+ redeployTagged(core, state, svc, tag, envName, value);
91
+ else
92
+ throw new Error(`drift ${key}: use service.<name>.deploy = <image>, service.<name>.tag.<tag> = delete, or service.<name>.tag.<tag>.env.<NAME> = <value>`);
93
+ return true;
94
+ },
95
+ routes(core, state, req) {
96
+ return routes(core, state, req);
97
+ },
98
+ };
99
+ // ---------------------------------------------------------------------------
100
+ // State
101
+ // ---------------------------------------------------------------------------
102
+ function keyOf(s) {
103
+ return `${s.project}/${s.region}/${s.name}`;
104
+ }
105
+ function fullName(s) {
106
+ return `projects/${s.project}/locations/${s.region}/services/${s.name}`;
107
+ }
108
+ function revisionFullName(s, revision) {
109
+ return `${fullName(s)}/revisions/${revision}`;
110
+ }
111
+ function bump(core, svc) {
112
+ svc.generation += 1;
113
+ svc.etag = `"${core.nextId("etag-")}"`;
114
+ }
115
+ function seedService(core, state, s) {
116
+ const project = s.project ?? "demo-project";
117
+ const region = s.region ?? "us-central1";
118
+ const first = `${s.name}-00001-abc`;
119
+ const template = { containers: [{ name: "app", image: s.image ?? DEFAULT_IMAGE, ports: [{ containerPort: 8080 }], env: Object.entries(s.env ?? {}).map(([name, value]) => ({ name, value })) }] };
120
+ const svc = {
121
+ project,
122
+ region,
123
+ name: s.name,
124
+ generation: 1,
125
+ etag: `"${core.nextId("etag-")}"`,
126
+ template,
127
+ traffic: s.traffic === "pinned" ? [{ type: BY_REVISION, revision: first, percent: 100 }] : [{ type: LATEST, percent: 100 }],
128
+ latestCreated: first,
129
+ latestReady: first,
130
+ identifier: `${core.nextId("h")}x3bz5a7c2q-uc`,
131
+ createdBy: "sim",
132
+ };
133
+ state.services.push(svc);
134
+ state.revisions.push({ name: first, service: keyOf(svc), template: structuredClone(template), state: "ready", createdBy: "sim" });
135
+ }
136
+ /** A revision of `svc` made outside the API (a human's deploy), ready at once. */
137
+ function addReadyRevision(core, state, svc, template) {
138
+ const name = `${svc.name}-${String(svc.generation + 1).padStart(5, "0")}-${core.nextId("r")}`;
139
+ const t = structuredClone(template);
140
+ delete t.revision;
141
+ state.revisions.push({ name, service: keyOf(svc), template: structuredClone(t), state: "ready", createdBy: "sim" });
142
+ svc.template = t;
143
+ svc.latestCreated = name;
144
+ svc.latestReady = name;
145
+ bump(core, svc);
146
+ return name;
147
+ }
148
+ function deployImage(core, state, svc, image) {
149
+ const t = structuredClone(svc.template);
150
+ const c = t.containers[0];
151
+ c.image = image;
152
+ addReadyRevision(core, state, svc, t);
153
+ }
154
+ function removeTag(core, svc, tag) {
155
+ const t = svc.traffic.find((x) => x.tag === tag);
156
+ if (!t)
157
+ throw new Error(`drift: service ${svc.name} has no tag ${tag}`);
158
+ if (!t.percent)
159
+ svc.traffic = svc.traffic.filter((x) => x !== t);
160
+ else
161
+ delete t.tag;
162
+ bump(core, svc);
163
+ }
164
+ function redeployTagged(core, state, svc, tag, envName, value) {
165
+ const target = svc.traffic.find((x) => x.tag === tag);
166
+ const base = target ? state.revisions.find((r) => r.service === keyOf(svc) && r.name === lastSegment(target.revision ?? svc.latestReady)) : undefined;
167
+ if (!target || !base)
168
+ throw new Error(`drift: service ${svc.name} has no tagged revision ${tag}`);
169
+ // `--no-traffic`: LATEST's share stays with the revision it pointed at (CR5).
170
+ svc.traffic = svc.traffic.map((x) => (x.type === LATEST ? { ...x, type: BY_REVISION, revision: svc.latestReady } : x));
171
+ const t = structuredClone(base.template);
172
+ const c = t.containers[0];
173
+ const env = (c.env ?? []).filter((e) => e.name !== envName);
174
+ c.env = [...env, { name: envName, value }];
175
+ const name = addReadyRevision(core, state, svc, t);
176
+ const moved = svc.traffic.find((x) => x.tag === tag);
177
+ moved.type = BY_REVISION;
178
+ moved.revision = name;
179
+ }
180
+ function lastSegment(name) {
181
+ return name.slice(name.lastIndexOf("/") + 1);
182
+ }
183
+ // ---------------------------------------------------------------------------
184
+ // Operations
185
+ // ---------------------------------------------------------------------------
186
+ function imageOf(template) {
187
+ const c = template.containers?.[0];
188
+ return typeof c?.image === "string" ? c.image : undefined;
189
+ }
190
+ /** End an operation: its revision comes up (or fails to, CR9). */
191
+ function settle(state, op) {
192
+ op.done = true;
193
+ const svc = state.services.find((s) => keyOf(s) === op.service);
194
+ const rev = op.revision ? state.revisions.find((r) => r.service === op.service && r.name === op.revision) : undefined;
195
+ if (!svc || !rev)
196
+ return;
197
+ if (state.failImages.includes(imageOf(rev.template) ?? "")) {
198
+ rev.state = "failed";
199
+ op.error = { code: 9, message: `Revision '${rev.name}' is not ready and cannot serve traffic. The user-provided container failed to start and listen on the port defined provided by the PORT=8080 environment variable.` };
200
+ return;
201
+ }
202
+ rev.state = "ready";
203
+ svc.latestReady = rev.name;
204
+ }
205
+ /** One poll of an operation: it ends after `operationPolls` of them. */
206
+ function tick(state, op) {
207
+ if (op.done)
208
+ return;
209
+ if (op.polls > 0)
210
+ op.polls -= 1;
211
+ else
212
+ settle(state, op);
213
+ }
214
+ // ---------------------------------------------------------------------------
215
+ // Answers
216
+ // ---------------------------------------------------------------------------
217
+ function googleError(status, code, message) {
218
+ return new Reply(status, { error: { code: status, message, status: code } });
219
+ }
220
+ const notFound = () => googleError(404, "NOT_FOUND", "Requested entity was not found.");
221
+ function trafficStatuses(svc) {
222
+ return svc.traffic.map((t) => ({
223
+ type: t.type,
224
+ revision: t.type === LATEST ? svc.latestReady : lastSegment(t.revision ?? ""),
225
+ ...(t.percent ? { percent: t.percent } : {}),
226
+ ...(t.tag ? { tag: t.tag, uri: `https://${t.tag}---${svc.name}-${svc.identifier}.a.run.app` } : {}),
227
+ }));
228
+ }
229
+ function publicService(state, svc) {
230
+ const reconciling = state.operations.some((o) => o.service === keyOf(svc) && !o.done);
231
+ return {
232
+ name: fullName(svc),
233
+ generation: String(svc.generation),
234
+ observedGeneration: String(reconciling ? svc.generation - 1 : svc.generation),
235
+ etag: svc.etag,
236
+ template: svc.template,
237
+ traffic: svc.traffic,
238
+ trafficStatuses: trafficStatuses(svc),
239
+ uri: `https://${svc.name}-${svc.identifier}.a.run.app`,
240
+ latestCreatedRevision: revisionFullName(svc, svc.latestCreated),
241
+ latestReadyRevision: revisionFullName(svc, svc.latestReady),
242
+ reconciling,
243
+ terminalCondition: { type: "Ready", state: reconciling ? "CONDITION_RECONCILING" : "CONDITION_SUCCEEDED" },
244
+ };
245
+ }
246
+ const CONDITION = { pending: "CONDITION_RECONCILING", ready: "CONDITION_SUCCEEDED", failed: "CONDITION_FAILED" };
247
+ function publicRevision(svc, rev) {
248
+ return {
249
+ name: revisionFullName(svc, rev.name),
250
+ service: fullName(svc),
251
+ containers: rev.template.containers,
252
+ reconciling: rev.state === "pending",
253
+ conditions: [{ type: "Ready", state: CONDITION[rev.state] }],
254
+ };
255
+ }
256
+ function publicOperation(state, op) {
257
+ const svc = state.services.find((s) => keyOf(s) === op.service);
258
+ const outcome = !op.done ? {} : op.error ? { error: op.error } : { response: svc ? { "@type": "type.googleapis.com/google.cloud.run.v2.Service", ...publicService(state, svc) } : {} };
259
+ return { name: op.name, metadata: { "@type": "type.googleapis.com/google.cloud.run.v2.Service", name: svc ? fullName(svc) : "" }, done: op.done, ...outcome };
260
+ }
261
+ // ---------------------------------------------------------------------------
262
+ // The patch (CR3, CR4, CR5, CR8)
263
+ // ---------------------------------------------------------------------------
264
+ const MASK_FIELDS = new Set(["template", "traffic"]);
265
+ const TAG = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
266
+ function maskOf(url) {
267
+ const raw = url.searchParams.get("updateMask");
268
+ if (!raw)
269
+ return googleError(400, "INVALID_ARGUMENT", "updateMask is required (the sim's own strictness)");
270
+ const fields = raw.split(",").map((f) => f.trim());
271
+ const unknown = fields.find((f) => !MASK_FIELDS.has(f));
272
+ if (unknown)
273
+ return googleError(400, "INVALID_ARGUMENT", `updateMask: field \`${unknown}\` is not one the sim can update`);
274
+ return new Set(fields);
275
+ }
276
+ /** The revision a template change makes: its name (CR4), or what is wrong with the template. */
277
+ function newRevisionName(core, state, svc, template) {
278
+ const containers = template?.containers;
279
+ if (!Array.isArray(containers) || containers.length === 0 || !containers.every((c) => typeof c?.image === "string")) {
280
+ return googleError(400, "INVALID_ARGUMENT", "template.containers: at least one container with an image is required");
281
+ }
282
+ const named = template.revision;
283
+ if (named === undefined || named === "")
284
+ return `${svc.name}-${String(svc.generation + 1).padStart(5, "0")}-${core.nextId("r")}`;
285
+ if (typeof named !== "string" || !named.startsWith(`${svc.name}-`) || named.length > 63 || !/^[a-z0-9-]*[a-z0-9]$/.test(named)) {
286
+ return googleError(400, "INVALID_ARGUMENT", `template.revision: ${String(named)} must start with ${svc.name}-, use lowercase letters, digits and -, not end with - and be at most 63 characters`);
287
+ }
288
+ if (state.revisions.some((r) => r.service === keyOf(svc) && r.name === named))
289
+ return googleError(409, "ALREADY_EXISTS", `Revision ${named} already exists.`);
290
+ return named;
291
+ }
292
+ /** What is wrong with one traffic target (CR5), or undefined. */
293
+ function targetProblem(t, known) {
294
+ if (t.type !== LATEST && t.type !== BY_REVISION)
295
+ return `traffic: unknown type ${String(t.type)}`;
296
+ if (t.type === BY_REVISION && (typeof t.revision !== "string" || !known.has(lastSegment(t.revision))))
297
+ return `traffic: revision ${String(t.revision)} does not exist`;
298
+ const percent = t.percent ?? 0;
299
+ if (typeof percent !== "number" || !Number.isInteger(percent) || percent < 0 || percent > 100)
300
+ return "traffic: percent must be 0 to 100";
301
+ if (t.tag !== undefined && (typeof t.tag !== "string" || !TAG.test(t.tag)))
302
+ return `traffic: tag ${String(t.tag)} is not a lowercase DNS label`;
303
+ return undefined;
304
+ }
305
+ /** What is wrong with a traffic list (CR5, CR13), or undefined. */
306
+ function trafficProblem(traffic, known) {
307
+ if (!Array.isArray(traffic))
308
+ return "traffic must be a list";
309
+ const targets = traffic;
310
+ const problem = targets.map((t) => targetProblem(t, known)).find((p) => p !== undefined);
311
+ if (problem)
312
+ return problem;
313
+ const tags = targets.flatMap((t) => (t.tag === undefined ? [] : [t.tag]));
314
+ if (new Set(tags).size !== tags.length)
315
+ return "traffic: a tag is used twice";
316
+ const sum = targets.reduce((n, t) => n + (t.percent ?? 0), 0);
317
+ return targets.length > 0 && sum !== 100 ? `traffic: percentages add up to ${sum}, not 100` : undefined;
318
+ }
319
+ function patchService(core, state, svc, url, body) {
320
+ const mask = maskOf(url);
321
+ if (mask instanceof Reply)
322
+ return mask;
323
+ const b = (body ?? {});
324
+ if (b.etag !== undefined && b.etag !== svc.etag)
325
+ return googleError(409, "ABORTED", "The etag does not match the service's: it was changed since it was read.");
326
+ const changesTemplate = mask.has("template") && JSON.stringify(b.template) !== JSON.stringify(svc.template);
327
+ const revision = changesTemplate ? newRevisionName(core, state, svc, b.template) : null;
328
+ if (revision instanceof Reply)
329
+ return revision;
330
+ const known = new Set(state.revisions.filter((r) => r.service === keyOf(svc)).map((r) => r.name).concat(revision ? [revision] : []));
331
+ const problem = mask.has("traffic") ? trafficProblem(b.traffic, known) : undefined;
332
+ if (problem)
333
+ return googleError(400, "INVALID_ARGUMENT", problem);
334
+ if (revision) {
335
+ const template = structuredClone(b.template);
336
+ state.revisions.push({ name: revision, service: keyOf(svc), template: structuredClone(template), state: "pending", createdBy: "api" });
337
+ svc.template = template;
338
+ svc.latestCreated = revision;
339
+ }
340
+ if (mask.has("traffic"))
341
+ svc.traffic = structuredClone(b.traffic);
342
+ bump(core, svc);
343
+ const op = { name: `projects/${svc.project}/locations/${svc.region}/operations/${core.nextId("op-")}`, service: keyOf(svc), revision, polls: state.operationPolls, done: false };
344
+ state.operations.push(op);
345
+ return new Reply(200, publicOperation(state, op));
346
+ }
347
+ function serviceAt(state, p) {
348
+ return state.services.find((s) => s.project === p.project && s.region === p.location && s.name === p.service);
349
+ }
350
+ const SERVICE = "/v2/projects/:project/locations/:location/services/:service";
351
+ const routes = router([
352
+ route("GET", SERVICE, ({ state, params }) => {
353
+ const svc = serviceAt(state, params);
354
+ if (!svc)
355
+ return notFound();
356
+ for (const op of state.operations)
357
+ if (op.service === keyOf(svc))
358
+ tick(state, op);
359
+ return new Reply(200, publicService(state, svc));
360
+ }),
361
+ route("PATCH", SERVICE, ({ core, state, params, url, body }) => {
362
+ const svc = serviceAt(state, params);
363
+ return svc ? patchService(core, state, svc, url, body) : notFound();
364
+ }),
365
+ route("GET", `${SERVICE}/revisions/:revision`, ({ state, params }) => {
366
+ const svc = serviceAt(state, params);
367
+ const rev = svc ? state.revisions.find((r) => r.service === keyOf(svc) && r.name === params.revision) : undefined;
368
+ return svc && rev ? new Reply(200, publicRevision(svc, rev)) : notFound();
369
+ }),
370
+ route("GET", "/v2/projects/:project/locations/:location/operations/:operation", ({ state, params }) => {
371
+ const op = state.operations.find((o) => o.name === `projects/${params.project}/locations/${params.location}/operations/${params.operation}`);
372
+ if (!op)
373
+ return notFound();
374
+ tick(state, op);
375
+ return new Reply(200, publicOperation(state, op));
376
+ }),
377
+ ], notFound);
378
+ //# sourceMappingURL=cloud_run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud_run.js","sourceRoot":"","sources":["../../src/routes/cloud_run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Simulated Cloudflare: Pages projects and their per-environment variables (`deployment_configs.<env>.env_vars`).
3
+ *
4
+ * Assumptions about the real API that this fake encodes. Sources (fetched 2026-10-11): the official OpenAPI document
5
+ * https://github.com/cloudflare/api-schemas (`openapi.json`), the API reference pages
6
+ * https://developers.cloudflare.com/api/resources/pages/subresources/projects/methods/get/ and …/methods/edit/,
7
+ * and the Pages docs cited per item. The critical ones are pinned in scenarios/contract.test.ts
8
+ * (`assumption CF<n>: …`) so `pnpm test:live` checks them against a real account.
9
+ *
10
+ * CF1. Base URL `https://api.cloudflare.com/client/v4`, `Authorization: Bearer <API token>` — verified
11
+ * (https://developers.cloudflare.com/fundamentals/api/how-to/make-api-calls/). The token needs the
12
+ * account permission "Cloudflare Pages Edit" (fundamentals/api/reference/permissions/; schema token groups
13
+ * "Pages Read"/"Pages Write").
14
+ * CF2. `GET /accounts/{account_id}/pages/projects/{project_name}` answers `200 { success, errors, messages, result }`,
15
+ * `result` a project with `id`, `name`, `subdomain` and `deployment_configs.{preview,production}.env_vars`, a
16
+ * nullable map NAME → `{ type: "plain_text" | "secret_text", value }` — verified (schema `pages_project`,
17
+ * `pages_plain_text_env_var`, `pages_secret_text_env_var`).
18
+ * CF3. A `secret_text` value is never returned: the entry is listed with its type and `value: ""` — partly
19
+ * verified: the docs say secrets cannot be seen after they are set (pages/functions/bindings/), and the
20
+ * schema's example is `{"type":"secret_text","value":""}`; whether the key comes back with `""`, null or
21
+ * no `value` is not stated (the adapter accepts all three). Pinned.
22
+ * CF4. `PATCH` of the same path merges: `deployment_configs.<env>.env_vars` keys not in the body are left alone, a
23
+ * key set to `null` is deleted, and the answer is `200` with the updated project — `null` deletes and `200`
24
+ * verified ("To delete an environment variable, set its key to `null`"); merging is implied by that rule and
25
+ * by a body with no required fields, but not stated. Pinned: the adapter also re-reads and refuses to go on
26
+ * if a variable it did not send disappeared.
27
+ * CF5. No precondition (ETag / If-Match) exists for that PATCH — verified (only path parameters, no 412). Two
28
+ * writers of the same key race; last write wins. Sponson's own writers take turns through the parent lock
29
+ * (`lockOn`, ADR 0019).
30
+ * CF6. Failures answer `4XX { success: false, errors: [{ code, message }], result: null }` — verified (schema
31
+ * `pages_api-response-common-failure`). An unknown project answers `404` with code `8000007` — unverified:
32
+ * the schema declares only `4XX`; the status and code come from Wrangler's error reports.
33
+ * CF7. A write is visible to the next GET at once — unverified. Pinned.
34
+ * CF8. Preview variables are one set shared by every preview deployment; there are no per-branch values —
35
+ * verified ("This will set the configuration for all preview deployments, not just the deployments from a
36
+ * specific branch", pages/functions/wrangler-configuration/).
37
+ * CF9. Variable names: the schema gives no pattern (free-form map keys); the sim accepts any non-empty name.
38
+ * CF10. Changing a variable does not change existing deployments; it applies to the next one — indirectly
39
+ * documented ("Redeploy your project for the binding to take effect", pages/functions/bindings/). The sim
40
+ * has no deployments, so nothing here depends on it.
41
+ */
42
+ import { type CreatedBy, type ProviderSim } from "../provider.js";
43
+ /** The two variable sets of a Pages project. */
44
+ export type CloudflarePagesEnv = "preview" | "production";
45
+ /** One variable as the simulated Cloudflare stores it (the secret's value included: the sim must compare it). */
46
+ export interface CloudflareEnvVar {
47
+ type: "plain_text" | "secret_text";
48
+ value: string;
49
+ }
50
+ /** A Pages project, with the bookkeeping the sim adds (`createdBy`). */
51
+ export interface CloudflarePagesProject {
52
+ id: string;
53
+ name: string;
54
+ subdomain: string;
55
+ createdBy: CreatedBy;
56
+ deployment_configs: Record<CloudflarePagesEnv, {
57
+ env_vars: Record<string, CloudflareEnvVar>;
58
+ }>;
59
+ }
60
+ /** The simulated Cloudflare's state: Pages projects per account id. */
61
+ export interface CloudflareState {
62
+ accounts: Record<string, {
63
+ projects: Record<string, CloudflarePagesProject>;
64
+ }>;
65
+ }
66
+ /** Variables of one environment in a seed: NAME → a plain value, or `{ type, value }`. */
67
+ export type CloudflareSeedVars = Record<string, string | {
68
+ type?: CloudflareEnvVar["type"];
69
+ value: string;
70
+ }>;
71
+ /** What a scenario or test seeds the simulated Cloudflare with (`seed: { cloudflare: … }`). */
72
+ export interface CloudflareSeed {
73
+ /** Account id → project name → the variables that already exist (set "by a human", so unmanaged). */
74
+ accounts: Record<string, Record<string, {
75
+ preview?: CloudflareSeedVars;
76
+ production?: CloudflareSeedVars;
77
+ }>>;
78
+ }
79
+ /** The simulated Cloudflare API, registered in `PROVIDERS` (packages/sim/src/state.ts) and served under `/cloudflare`. */
80
+ export declare const cloudflareSim: ProviderSim<CloudflareState, CloudflareSeed>;