@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,33 @@
1
+ /**
2
+ * Simulated Clerk: the instance's allowed redirect URLs.
3
+ *
4
+ * Assumptions about the real API that this fake encodes, each marked with how far it is checked. "Verified" means
5
+ * against Clerk's published Backend API OpenAPI document (github.com/clerk/openapi-specs, bapi/2026-05-12.yml,
6
+ * identical for /redirect_urls back to 2021-02-05; fetched 2026-10-10); see docs/api-verification.md:
7
+ * C1. A duplicate redirect URL answers 422 (ClerkErrors body). Unverified: the spec documents 400 and 422 for
8
+ * POST /redirect_urls without saying which a duplicate gets. The adapter checks the list after either.
9
+ * C2. GET /redirect_urls is a bare JSON array (verified); with `paginated=true` it is `{ data, total_count }`
10
+ * paged by `offset`/`limit` (limit 1–500, default 10: verified parameters). The envelope is not in the
11
+ * spec; it is how Clerk's own SDKs (clerk/javascript RedirectUrlApi, clerk-sdk-go redirecturl) call and
12
+ * decode it. Chaos `page_size` caps the page further.
13
+ * C3. A redirect URL is `{ object: "redirect_url", id, url, created_at, updated_at }` and DELETE answers a
14
+ * DeletedObject `{ object, id, deleted }` (verified).
15
+ */
16
+ import { type CreatedBy, type ProviderSim } from "../provider.js";
17
+ /** One allow-listed redirect URL. */
18
+ export interface ClerkRedirect {
19
+ id: string;
20
+ url: string;
21
+ createdAt: number;
22
+ createdBy: CreatedBy;
23
+ }
24
+ /** Simulated Clerk: the instance's redirect allow-list. */
25
+ export interface ClerkState {
26
+ redirect_urls: ClerkRedirect[];
27
+ }
28
+ /** Initial Clerk state. */
29
+ export interface ClerkSeed {
30
+ redirect_urls: string[];
31
+ }
32
+ /** Simulated Clerk (redirect URL allow-list); see the assumptions at the top of this file. */
33
+ export declare const clerkSim: ProviderSim<ClerkState, ClerkSeed>;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Simulated Clerk: the instance's allowed redirect URLs.
3
+ *
4
+ * Assumptions about the real API that this fake encodes, each marked with how far it is checked. "Verified" means
5
+ * against Clerk's published Backend API OpenAPI document (github.com/clerk/openapi-specs, bapi/2026-05-12.yml,
6
+ * identical for /redirect_urls back to 2021-02-05; fetched 2026-10-10); see docs/api-verification.md:
7
+ * C1. A duplicate redirect URL answers 422 (ClerkErrors body). Unverified: the spec documents 400 and 422 for
8
+ * POST /redirect_urls without saying which a duplicate gets. The adapter checks the list after either.
9
+ * C2. GET /redirect_urls is a bare JSON array (verified); with `paginated=true` it is `{ data, total_count }`
10
+ * paged by `offset`/`limit` (limit 1–500, default 10: verified parameters). The envelope is not in the
11
+ * spec; it is how Clerk's own SDKs (clerk/javascript RedirectUrlApi, clerk-sdk-go redirecturl) call and
12
+ * decode it. Chaos `page_size` caps the page further.
13
+ * C3. A redirect URL is `{ object: "redirect_url", id, url, created_at, updated_at }` and DELETE answers a
14
+ * DeletedObject `{ object, id, deleted }` (verified).
15
+ */
16
+ import { page, Reply, route, router } from "../provider.js";
17
+ /** Simulated Clerk (redirect URL allow-list); see the assumptions at the top of this file. */
18
+ export const clerkSim = {
19
+ env: { token: "CLERK_SECRET_KEY", url: "CLERK_API_URL", testToken: "tok_clerk" },
20
+ defaultSeed: { redirect_urls: [] },
21
+ reset(core, seed) {
22
+ const now = Date.now();
23
+ return { redirect_urls: (seed?.redirect_urls ?? []).map((url) => redirect(core, url, now, "sim")) };
24
+ },
25
+ /** `redirect.<url>`: "delete" | "recreate". Clerk has one instance per key, so there is no `clerk:<project>.` form. */
26
+ drift(core, state, { key, rest, value, only }) {
27
+ if (only !== undefined || !rest.startsWith("redirect."))
28
+ return false;
29
+ const url = rest.slice("redirect.".length);
30
+ if (value !== "delete" && value !== "recreate")
31
+ throw new Error(`drift ${key}: only "delete" and "recreate" are supported`);
32
+ const hit = state.redirect_urls.filter((r) => r.url === url);
33
+ state.redirect_urls = state.redirect_urls.filter((r) => !hit.includes(r));
34
+ if (value === "recreate")
35
+ for (const r of hit)
36
+ state.redirect_urls.push(redirect(core, r.url, Date.now(), "sim"));
37
+ return true;
38
+ },
39
+ routes(core, state, req) {
40
+ return routes(core, state, req);
41
+ },
42
+ };
43
+ const routes = router([
44
+ route("GET", "/redirect_urls", ({ core, state, url }) => {
45
+ const all = state.redirect_urls.map(publicRedirect);
46
+ if (url.searchParams.get("paginated") !== "true")
47
+ return new Reply(200, all);
48
+ const limit = url.searchParams.get("limit");
49
+ const offset = url.searchParams.get("offset");
50
+ if ((limit !== null && !/^\d+$/.test(limit)) || (offset !== null && !/^\d+$/.test(offset)) || Number(limit ?? 10) < 1 || Number(limit ?? 10) > 500) {
51
+ return clerkError(422, "form_param_format_invalid", "limit must be 1–500 and offset ≥ 0");
52
+ }
53
+ const pg = page(core, all, offset, Number(limit ?? 10));
54
+ return new Reply(200, { data: pg.items, total_count: all.length });
55
+ }),
56
+ route("POST", "/redirect_urls", ({ core, state, body }) => {
57
+ const b = (body ?? {});
58
+ if (!b.url)
59
+ return clerkError(422, "form_param_missing", "url is required");
60
+ if (state.redirect_urls.some((r) => r.url === b.url))
61
+ return clerkError(422, "duplicate_record", "redirect url already exists");
62
+ const r = redirect(core, b.url, Date.now(), "api");
63
+ state.redirect_urls.push(r);
64
+ return new Reply(200, publicRedirect(r));
65
+ }),
66
+ route("DELETE", "/redirect_urls/:id", ({ state, params }) => {
67
+ const r = state.redirect_urls.find((x) => x.id === params.id);
68
+ if (!r)
69
+ return clerkError(404, "resource_not_found", "not found");
70
+ state.redirect_urls = state.redirect_urls.filter((x) => x !== r);
71
+ return new Reply(200, { id: r.id, object: "redirect_url", deleted: true });
72
+ }),
73
+ ], () => clerkError(404, "resource_not_found", "not found"));
74
+ /** The spec's RedirectURL: `object`, `id`, `url`, `created_at`, `updated_at` (all required). */
75
+ function publicRedirect(r) {
76
+ return { object: "redirect_url", id: r.id, url: r.url, created_at: r.createdAt, updated_at: r.createdAt };
77
+ }
78
+ /** The spec's ClerkErrors body. */
79
+ function clerkError(status, code, message) {
80
+ return new Reply(status, { errors: [{ code, message, long_message: message }] });
81
+ }
82
+ function redirect(core, url, createdAt, createdBy) {
83
+ return { id: core.nextId("ru_"), url, createdAt, createdBy };
84
+ }
85
+ //# sourceMappingURL=clerk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clerk.js","sourceRoot":"","sources":["../../src/routes/clerk.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>;
@@ -0,0 +1,176 @@
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 { Reply, route, router } from "../provider.js";
43
+ const ENVS = ["preview", "production"];
44
+ /** The simulated Cloudflare API, registered in `PROVIDERS` (packages/sim/src/state.ts) and served under `/cloudflare`. */
45
+ export const cloudflareSim = {
46
+ env: { token: "CLOUDFLARE_API_TOKEN", url: "CLOUDFLARE_API_URL", testToken: "tok_cloudflare" },
47
+ defaultSeed: { accounts: { acc_demo: { demo: {} } } },
48
+ reset(core, seed) {
49
+ const accounts = {};
50
+ for (const [account, projects] of Object.entries(seed?.accounts ?? {})) {
51
+ accounts[account] = { projects: {} };
52
+ for (const [name, vars] of Object.entries(projects))
53
+ accounts[account].projects[name] = project(core, name, vars);
54
+ }
55
+ return { accounts };
56
+ },
57
+ /**
58
+ * `pages_env.<env>.<NAME>` or `cloudflare:<project>.pages_env.<env>.<NAME>`, what a human in the dashboard would do:
59
+ * `delete`; `plain:<value>` or `secret:<value>` (set, choosing the type); any other value replaces the value and
60
+ * keeps the type. Setting a variable that does not exist creates it as plain text.
61
+ */
62
+ drift(_core, state, { key, rest, value, only }) {
63
+ const m = /^pages_env\.(preview|production)\.(.+)$/.exec(rest);
64
+ if (!m)
65
+ return false;
66
+ const env = m[1];
67
+ const name = m[2];
68
+ const projects = Object.values(state.accounts).flatMap((a) => Object.values(a.projects)).filter((p) => only === undefined || p.name === only);
69
+ if (projects.length === 0)
70
+ throw new Error(`drift ${key}: no Pages project${only ? ` named ${only}` : ""}`);
71
+ for (const p of projects) {
72
+ const vars = p.deployment_configs[env].env_vars;
73
+ if (value === "delete")
74
+ delete vars[name];
75
+ else if (value.startsWith("plain:"))
76
+ vars[name] = { type: "plain_text", value: value.slice("plain:".length) };
77
+ else if (value.startsWith("secret:"))
78
+ vars[name] = { type: "secret_text", value: value.slice("secret:".length) };
79
+ else
80
+ vars[name] = { type: vars[name]?.type ?? "plain_text", value };
81
+ }
82
+ return true;
83
+ },
84
+ routes(core, state, req) {
85
+ return routes(core, state, req);
86
+ },
87
+ };
88
+ const PROJECT = "/accounts/:account/pages/projects/:project";
89
+ const routes = router([
90
+ route("GET", PROJECT, ({ state, params }) => {
91
+ const p = state.accounts[params.account]?.projects[params.project];
92
+ return p ? ok(publicProject(p)) : notFound();
93
+ }),
94
+ route("PATCH", PROJECT, ({ state, params, body }) => {
95
+ const p = state.accounts[params.account]?.projects[params.project];
96
+ if (!p)
97
+ return notFound();
98
+ const problem = patchProblem(body);
99
+ if (problem)
100
+ return failure(400, 8000000, problem);
101
+ const configs = body.deployment_configs ?? {};
102
+ for (const env of ENVS) {
103
+ // CF4: a merge. Keys absent from the body stay; `null` deletes.
104
+ for (const [name, v] of Object.entries(configs[env]?.env_vars ?? {})) {
105
+ if (v === null)
106
+ delete p.deployment_configs[env].env_vars[name];
107
+ else
108
+ p.deployment_configs[env].env_vars[name] = { type: v.type, value: v.value };
109
+ }
110
+ }
111
+ return ok(publicProject(p));
112
+ }),
113
+ ], () => failure(404, 7003, "No route for the URI"));
114
+ /** Why a PATCH body is refused, or undefined. Only what the schema says about `env_vars` entries is checked. */
115
+ function patchProblem(body) {
116
+ if (body === null || typeof body !== "object" || Array.isArray(body))
117
+ return "request body must be a JSON object";
118
+ const configs = body.deployment_configs;
119
+ if (configs === undefined)
120
+ return undefined;
121
+ if (configs === null || typeof configs !== "object")
122
+ return "deployment_configs must be an object";
123
+ for (const env of Object.keys(configs)) {
124
+ if (!ENVS.includes(env))
125
+ return `unknown deployment config ${env}`;
126
+ const problem = envVarsProblem(env, configs[env]?.env_vars);
127
+ if (problem)
128
+ return problem;
129
+ }
130
+ return undefined;
131
+ }
132
+ /** Why one `env_vars` map of a PATCH body is refused, or undefined. */
133
+ function envVarsProblem(env, vars) {
134
+ if (vars === undefined || vars === null)
135
+ return undefined;
136
+ if (typeof vars !== "object" || Array.isArray(vars))
137
+ return `deployment_configs.${env}.env_vars must be an object`;
138
+ for (const [name, v] of Object.entries(vars)) {
139
+ if (name === "")
140
+ return "environment variable names must not be empty";
141
+ if (v === null)
142
+ continue;
143
+ const e = v;
144
+ if ((e.type !== "plain_text" && e.type !== "secret_text") || typeof e.value !== "string")
145
+ return `deployment_configs.${env}.env_vars.${name} must be { type: plain_text | secret_text, value: string }`;
146
+ }
147
+ return undefined;
148
+ }
149
+ function ok(result) {
150
+ return new Reply(200, { success: true, errors: [], messages: [], result });
151
+ }
152
+ function failure(status, code, message) {
153
+ return new Reply(status, { success: false, errors: [{ code, message }], messages: [], result: null });
154
+ }
155
+ /** CF6: what Wrangler reports for an unknown project. */
156
+ function notFound() {
157
+ return failure(404, 8000007, "Project not found. The specified project name does not match any of your existing projects.");
158
+ }
159
+ function project(core, name, seed) {
160
+ const vars = (s) => Object.fromEntries(Object.entries(s ?? {}).map(([k, v]) => [k, typeof v === "string" ? { type: "plain_text", value: v } : { type: v.type ?? "plain_text", value: v.value }]));
161
+ return {
162
+ id: core.nextId("cf_"),
163
+ name,
164
+ subdomain: `${name}.pages.dev`,
165
+ createdBy: "sim",
166
+ deployment_configs: { preview: { env_vars: vars(seed.preview) }, production: { env_vars: vars(seed.production) } },
167
+ };
168
+ }
169
+ /** The project as GET answers it: CF3, a secret's value is never returned. */
170
+ function publicProject(p) {
171
+ const config = (env) => ({
172
+ env_vars: Object.fromEntries(Object.entries(p.deployment_configs[env].env_vars).map(([k, v]) => [k, v.type === "secret_text" ? { type: v.type, value: "" } : { ...v }])),
173
+ });
174
+ return { id: p.id, name: p.name, subdomain: p.subdomain, production_branch: "main", deployment_configs: { preview: config("preview"), production: config("production") } };
175
+ }
176
+ //# sourceMappingURL=cloudflare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../src/routes/cloudflare.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Simulated DigitalOcean App Platform: apps built from an app spec, each create and update starting a deployment.
3
+ *
4
+ * Assumptions about the real API this fake encodes, checked against the official OpenAPI document
5
+ * (https://github.com/digitalocean/openapi, `specification/resources/apps`) and the docs unless marked unverified.
6
+ * The critical ones are pinned in scenarios/contract.test.ts (`assumption DO…`).
7
+ * DO1. The API is `https://api.digitalocean.com`, authenticated as `Authorization: Bearer <token>` (scopes
8
+ * app:create/read/update/delete); doctl reads the token from `DIGITALOCEAN_ACCESS_TOKEN`. (verified:
9
+ * https://docs.digitalocean.com/reference/api/digitalocean/, doctl README)
10
+ * DO2. `POST /v2/apps` with `{ spec }` creates an app and answers `{ app: { id, spec, pending_deployment, … } }`;
11
+ * `GET /v2/apps/{id}` answers `{ app }` (404 when gone); `GET /v2/apps?page=&per_page=` lists
12
+ * `{ apps, links: { pages: { next } }, meta }`, with no name filter. (verified: apps_create.yml, apps_get.yml,
13
+ * apps_list.yml)
14
+ * DO3. An app's `spec.name` is unique in the account (`^[a-z][a-z0-9-]{0,30}[a-z0-9]$`); the status of a
15
+ * duplicate create is not documented (unverified: the sim answers 409). (verified: app_spec.yml)
16
+ * DO4. `PUT /v2/apps/{id}` with `{ spec }` replaces the whole spec and starts a deployment. (verified:
17
+ * apps_update.yml; "starts a deployment" inferred from `pending_deployment` being set for UpdateApp)
18
+ * DO5. `pending_deployment` names the deployment a create or update started; `GET
19
+ * /v2/apps/{app_id}/deployments/{deployment_id}` answers `{ deployment: { id, phase } }`, the phase one of
20
+ * PENDING_BUILD, BUILDING, PENDING_DEPLOY, DEPLOYING, ACTIVE, SUPERSEDED, ERROR, CANCELED. (verified:
21
+ * app.yml, apps_get_deployment.yml, apps_deployment_response.yml, apps_deployment_phase.yml)
22
+ * DO6. The platform adds defaults to the spec it stores (a region, instance counts); it keeps every field the
23
+ * client sent, lists in the order sent. (unverified: the sim adds `region` and `instance_count`)
24
+ * DO7. A `type: SECRET` variable's value is encrypted on first submission and read back as `EV[…]`.
25
+ * (verified: app_variable_definition.yml, "encrypted on first submission"; the read-back form unverified)
26
+ * DO8. `DELETE /v2/apps/{id}` deletes the app and its deployments (200 `{ id }`; 404 when gone). (verified:
27
+ * apps_delete.yml)
28
+ */
29
+ import { type CreatedBy, type ProviderSim } from "../provider.js";
30
+ /** An app as the simulated App Platform stores it. */
31
+ export interface DigitaloceanApp {
32
+ id: string;
33
+ spec: Record<string, unknown>;
34
+ liveUrl: string | null;
35
+ /** Deployments by id: phase, and polls left before the next phase. */
36
+ deployments: Record<string, {
37
+ phase: string;
38
+ polls: number;
39
+ }>;
40
+ pending: string | null;
41
+ createdBy: CreatedBy;
42
+ }
43
+ /** The simulated App Platform's state. */
44
+ export interface DigitaloceanState {
45
+ apps: DigitaloceanApp[];
46
+ /** Polls a deployment answers BUILDING before it is ACTIVE (default 1); `fail` ends it ERROR instead. */
47
+ deployPolls: number;
48
+ failDeployments: boolean;
49
+ }
50
+ /** What a scenario or test seeds the simulated App Platform with (`seed: { digitalocean: … }`). */
51
+ export interface DigitaloceanSeed {
52
+ /** Apps that exist already (unmanaged), by spec. */
53
+ apps?: Array<Record<string, unknown>>;
54
+ deployPolls?: number;
55
+ failDeployments?: boolean;
56
+ }
57
+ /** The simulated App Platform API, registered in `PROVIDERS` (packages/sim/src/state.ts) and served under `/digitalocean`. */
58
+ export declare const digitaloceanSim: ProviderSim<DigitaloceanState, DigitaloceanSeed>;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Simulated DigitalOcean App Platform: apps built from an app spec, each create and update starting a deployment.
3
+ *
4
+ * Assumptions about the real API this fake encodes, checked against the official OpenAPI document
5
+ * (https://github.com/digitalocean/openapi, `specification/resources/apps`) and the docs unless marked unverified.
6
+ * The critical ones are pinned in scenarios/contract.test.ts (`assumption DO…`).
7
+ * DO1. The API is `https://api.digitalocean.com`, authenticated as `Authorization: Bearer <token>` (scopes
8
+ * app:create/read/update/delete); doctl reads the token from `DIGITALOCEAN_ACCESS_TOKEN`. (verified:
9
+ * https://docs.digitalocean.com/reference/api/digitalocean/, doctl README)
10
+ * DO2. `POST /v2/apps` with `{ spec }` creates an app and answers `{ app: { id, spec, pending_deployment, … } }`;
11
+ * `GET /v2/apps/{id}` answers `{ app }` (404 when gone); `GET /v2/apps?page=&per_page=` lists
12
+ * `{ apps, links: { pages: { next } }, meta }`, with no name filter. (verified: apps_create.yml, apps_get.yml,
13
+ * apps_list.yml)
14
+ * DO3. An app's `spec.name` is unique in the account (`^[a-z][a-z0-9-]{0,30}[a-z0-9]$`); the status of a
15
+ * duplicate create is not documented (unverified: the sim answers 409). (verified: app_spec.yml)
16
+ * DO4. `PUT /v2/apps/{id}` with `{ spec }` replaces the whole spec and starts a deployment. (verified:
17
+ * apps_update.yml; "starts a deployment" inferred from `pending_deployment` being set for UpdateApp)
18
+ * DO5. `pending_deployment` names the deployment a create or update started; `GET
19
+ * /v2/apps/{app_id}/deployments/{deployment_id}` answers `{ deployment: { id, phase } }`, the phase one of
20
+ * PENDING_BUILD, BUILDING, PENDING_DEPLOY, DEPLOYING, ACTIVE, SUPERSEDED, ERROR, CANCELED. (verified:
21
+ * app.yml, apps_get_deployment.yml, apps_deployment_response.yml, apps_deployment_phase.yml)
22
+ * DO6. The platform adds defaults to the spec it stores (a region, instance counts); it keeps every field the
23
+ * client sent, lists in the order sent. (unverified: the sim adds `region` and `instance_count`)
24
+ * DO7. A `type: SECRET` variable's value is encrypted on first submission and read back as `EV[…]`.
25
+ * (verified: app_variable_definition.yml, "encrypted on first submission"; the read-back form unverified)
26
+ * DO8. `DELETE /v2/apps/{id}` deletes the app and its deployments (200 `{ id }`; 404 when gone). (verified:
27
+ * apps_delete.yml)
28
+ */
29
+ import { Reply, route, router } from "../provider.js";
30
+ /** The simulated App Platform API, registered in `PROVIDERS` (packages/sim/src/state.ts) and served under `/digitalocean`. */
31
+ export const digitaloceanSim = {
32
+ env: { token: "DIGITALOCEAN_ACCESS_TOKEN", url: "DIGITALOCEAN_API_URL", testToken: "tok_digitalocean" },
33
+ defaultSeed: { apps: [] },
34
+ reset(core, seed) {
35
+ const apps = (seed?.apps ?? []).map((spec) => ({ id: core.nextId("app_"), spec: stored(core, spec), liveUrl: `https://${String(spec.name)}.ondigitalocean.app`, deployments: {}, pending: null, createdBy: "sim" }));
36
+ return { apps, deployPolls: seed?.deployPolls ?? 1, failDeployments: seed?.failDeployments ?? false };
37
+ },
38
+ /**
39
+ * `app.<name>`: "delete"; `app.<name>.env.<KEY>`: a new value for that app-level variable — what a human in the
40
+ * control panel would do.
41
+ */
42
+ drift(_core, state, { key, rest, value, only }) {
43
+ if (only !== undefined || !rest.startsWith("app."))
44
+ return false;
45
+ const [name, kind, envKey] = rest.slice("app.".length).split(".");
46
+ const app = state.apps.find((a) => a.spec.name === name);
47
+ if (!app)
48
+ throw new Error(`drift ${key}: no app ${String(name)}`);
49
+ if (kind === undefined && value === "delete")
50
+ state.apps = state.apps.filter((a) => a !== app);
51
+ else if (kind === "env" && envKey) {
52
+ const env = (app.spec.envs ?? []).find((e) => e.key === envKey);
53
+ if (!env)
54
+ throw new Error(`drift ${key}: no variable ${envKey}`);
55
+ env.value = value;
56
+ }
57
+ else
58
+ throw new Error(`drift ${key}: use app.<name> = delete, or app.<name>.env.<KEY> = <value>`);
59
+ return true;
60
+ },
61
+ routes(core, state, req) {
62
+ return routes(core, state, req);
63
+ },
64
+ };
65
+ /** The spec as stored: defaults added (DO6), SECRET values encrypted (DO7). */
66
+ function stored(core, spec) {
67
+ const s = structuredClone(spec);
68
+ s.region ??= "nyc";
69
+ for (const svc of s.services ?? [])
70
+ svc.instance_count ??= 1;
71
+ const encrypt = (envs) => {
72
+ for (const e of envs ?? [])
73
+ if (e.type === "SECRET" && typeof e.value === "string" && !e.value.startsWith("EV["))
74
+ e.value = `EV[1:${core.nextId("")}:sim]`;
75
+ };
76
+ encrypt(s.envs);
77
+ for (const svc of s.services ?? [])
78
+ encrypt(svc.envs);
79
+ return s;
80
+ }
81
+ function deploy(core, state, app) {
82
+ const id = core.nextId("dep_");
83
+ app.deployments[id] = { phase: "PENDING_BUILD", polls: state.deployPolls };
84
+ app.pending = id;
85
+ }
86
+ function publicApp(a) {
87
+ return { id: a.id, spec: a.spec, ...(a.liveUrl ? { live_url: a.liveUrl } : {}), ...(a.pending ? { pending_deployment: { id: a.pending } } : {}) };
88
+ }
89
+ const NAME = /^[a-z][a-z0-9-]{0,30}[a-z0-9]$/;
90
+ const notFound = () => new Reply(404, { id: "not_found", message: "The resource you were accessing could not be found." });
91
+ function specOf(body) {
92
+ const spec = body?.spec;
93
+ if (typeof spec !== "object" || spec === null || Array.isArray(spec) || typeof spec.name !== "string" || !NAME.test(spec.name)) {
94
+ return new Reply(422, { id: "unprocessable_entity", message: "spec.name is invalid" });
95
+ }
96
+ return spec;
97
+ }
98
+ const routes = router([
99
+ route("GET", "/v2/apps", ({ state, url }) => {
100
+ const per = Math.max(1, Number(url.searchParams.get("per_page") ?? 20));
101
+ const page = Math.max(1, Number(url.searchParams.get("page") ?? 1));
102
+ const slice = state.apps.slice((page - 1) * per, page * per);
103
+ const next = page * per < state.apps.length ? { next: `https://api.digitalocean.com/v2/apps?page=${page + 1}&per_page=${per}` } : {};
104
+ return new Reply(200, { apps: slice.map(publicApp), links: { pages: next }, meta: { total: state.apps.length } });
105
+ }),
106
+ route("POST", "/v2/apps", ({ core, state, body }) => {
107
+ const spec = specOf(body);
108
+ if (spec instanceof Reply)
109
+ return spec;
110
+ if (state.apps.some((a) => a.spec.name === spec.name))
111
+ return new Reply(409, { id: "conflict", message: "an app with this name already exists" });
112
+ const app = { id: core.nextId("app_"), spec: stored(core, spec), liveUrl: null, deployments: {}, pending: null, createdBy: "api" };
113
+ deploy(core, state, app);
114
+ state.apps.push(app);
115
+ return new Reply(200, { app: publicApp(app) });
116
+ }),
117
+ route("GET", "/v2/apps/:id", ({ state, params }) => {
118
+ const app = state.apps.find((a) => a.id === params.id);
119
+ return app ? new Reply(200, { app: publicApp(app) }) : notFound();
120
+ }),
121
+ route("PUT", "/v2/apps/:id", ({ core, state, params, body }) => {
122
+ const app = state.apps.find((a) => a.id === params.id);
123
+ if (!app)
124
+ return notFound();
125
+ const spec = specOf(body);
126
+ if (spec instanceof Reply)
127
+ return spec;
128
+ app.spec = stored(core, spec);
129
+ deploy(core, state, app);
130
+ return new Reply(200, { app: publicApp(app) });
131
+ }),
132
+ route("GET", "/v2/apps/:id/deployments/:deployment", ({ state, params }) => {
133
+ const app = state.apps.find((a) => a.id === params.id);
134
+ const d = app?.deployments[params.deployment];
135
+ if (!app || !d)
136
+ return notFound();
137
+ if (d.polls > 0) {
138
+ d.polls--;
139
+ d.phase = "BUILDING";
140
+ }
141
+ else if (d.phase !== "ACTIVE" && d.phase !== "ERROR") {
142
+ d.phase = state.failDeployments ? "ERROR" : "ACTIVE";
143
+ if (d.phase === "ACTIVE") {
144
+ app.liveUrl = `https://${String(app.spec.name)}-${app.id.slice(4)}.ondigitalocean.app`;
145
+ app.pending = null;
146
+ }
147
+ }
148
+ return new Reply(200, { deployment: { id: params.deployment, phase: d.phase } });
149
+ }),
150
+ route("DELETE", "/v2/apps/:id", ({ state, params }) => {
151
+ const app = state.apps.find((a) => a.id === params.id);
152
+ if (!app)
153
+ return notFound();
154
+ state.apps = state.apps.filter((a) => a !== app);
155
+ return new Reply(200, { id: app.id });
156
+ }),
157
+ ], notFound);
158
+ //# sourceMappingURL=digitalocean.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digitalocean.js","sourceRoot":"","sources":["../../src/routes/digitalocean.ts"],"names":[],"mappings":"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"}