@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.
- package/README.md +5 -0
- package/dist/auth.d.ts +60 -0
- package/dist/auth.js +195 -0
- package/dist/auth.js.map +1 -0
- package/dist/bin.js +9 -14
- package/dist/bin.js.map +1 -1
- package/dist/chaos.d.ts +4 -0
- package/dist/chaos.js +2 -0
- package/dist/chaos.js.map +1 -1
- package/dist/cli.d.ts +29 -0
- package/dist/cli.js +99 -0
- package/dist/cli.js.map +1 -0
- package/dist/demo.d.ts +39 -0
- package/dist/demo.js +168 -0
- package/dist/demo.js.map +1 -0
- package/dist/graphql.d.ts +22 -0
- package/dist/graphql.js +36 -0
- package/dist/graphql.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/provider.d.ts +43 -4
- package/dist/provider.js +12 -6
- package/dist/provider.js.map +1 -1
- package/dist/routes/algolia.d.ts +37 -0
- package/dist/routes/algolia.js +83 -0
- package/dist/routes/algolia.js.map +1 -0
- package/dist/routes/aws.d.ts +43 -0
- package/dist/routes/aws.js +211 -0
- package/dist/routes/aws.js.map +1 -0
- package/dist/routes/cloud_run.d.ts +131 -0
- package/dist/routes/cloud_run.js +378 -0
- package/dist/routes/cloud_run.js.map +1 -0
- package/dist/routes/cloudflare.d.ts +80 -0
- package/dist/routes/cloudflare.js +176 -0
- package/dist/routes/cloudflare.js.map +1 -0
- package/dist/routes/digitalocean.d.ts +58 -0
- package/dist/routes/digitalocean.js +158 -0
- package/dist/routes/digitalocean.js.map +1 -0
- package/dist/routes/fly.d.ts +22 -0
- package/dist/routes/fly.js +113 -0
- package/dist/routes/fly.js.map +1 -0
- package/dist/routes/launchdarkly.d.ts +98 -0
- package/dist/routes/launchdarkly.js +213 -0
- package/dist/routes/launchdarkly.js.map +1 -0
- package/dist/routes/netlify.d.ts +126 -0
- package/dist/routes/netlify.js +347 -0
- package/dist/routes/netlify.js.map +1 -0
- package/dist/routes/planetscale.d.ts +95 -0
- package/dist/routes/planetscale.js +234 -0
- package/dist/routes/planetscale.js.map +1 -0
- package/dist/routes/railway.d.ts +24 -0
- package/dist/routes/railway.js +112 -0
- package/dist/routes/railway.js.map +1 -0
- package/dist/routes/rest.d.ts +138 -0
- package/dist/routes/rest.js +462 -0
- package/dist/routes/rest.js.map +1 -0
- package/dist/routes/supabase.d.ts +103 -0
- package/dist/routes/supabase.js +234 -0
- package/dist/routes/supabase.js.map +1 -0
- package/dist/routes/xata.d.ts +57 -0
- package/dist/routes/xata.js +115 -0
- package/dist/routes/xata.js.map +1 -0
- package/dist/server.js +71 -12
- package/dist/server.js.map +1 -1
- package/dist/state.d.ts +13 -0
- package/dist/state.js +14 -1
- package/dist/state.js.map +1 -1
- package/package.json +5 -1
|
@@ -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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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ProviderSim } from "../provider.js";
|
|
2
|
+
/** One secret as stored: the value (never answered), its digest and when it was set. */
|
|
3
|
+
export interface FlySecret {
|
|
4
|
+
value: string;
|
|
5
|
+
digest: string;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
}
|
|
8
|
+
/** Simulated Fly.io state: apps by name, with their secrets and release counter. */
|
|
9
|
+
export interface FlyState {
|
|
10
|
+
apps: Record<string, {
|
|
11
|
+
secrets: Record<string, FlySecret>;
|
|
12
|
+
releases: number;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
/** Initial Fly.io state: apps by name, with secret values. */
|
|
16
|
+
export interface FlySeed {
|
|
17
|
+
apps?: Record<string, {
|
|
18
|
+
secrets?: Record<string, string>;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
/** Simulated Fly.io GraphQL API; see the assumptions at the top of this file. */
|
|
22
|
+
export declare const flySim: ProviderSim<FlyState, FlySeed>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simulated Fly.io GraphQL API (`POST https://api.fly.io/graphql`), the subset that sets an app's secrets: enough
|
|
3
|
+
* to test the generic `http` adapter's GraphQL requests against Fly's real operation shapes.
|
|
4
|
+
*
|
|
5
|
+
* Assumptions (docs/api-verification.md, "Railway and Fly.io through the generic adapter"):
|
|
6
|
+
* F1. `Authorization: Bearer <token>`, or `FlyV1 <macaroon>` for the tokens `fly tokens create` prints (sent as
|
|
7
|
+
* they are). Verified: superfly/fly-go tokens/tokens.go.
|
|
8
|
+
* F2. `app(name: String!) { secrets { name digest createdAt } }` lists an app's secrets; values are never
|
|
9
|
+
* returned. Verified: fly-go resource_secrets.go and schema.graphql.
|
|
10
|
+
* F3. `setSecrets(input: SetSecretsInput!)` (appId: the app name, secrets: [{ key, value }], replaceAll?) and
|
|
11
|
+
* `unsetSecrets(input: UnsetSecretsInput!)` (appId, keys) answer `{ app, release }`; each makes a new
|
|
12
|
+
* release. Verified: fly-go schema.graphql. Unsetting a key that is not set is not an error here: unverified.
|
|
13
|
+
* F4. Failures are HTTP 200 with `errors[]`; an unknown app is "Could not find App". Unverified wording.
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { graphqlData, graphqlError, graphqlOperation, isMutation } from "../graphql.js";
|
|
17
|
+
import { Reply, route, router } from "../provider.js";
|
|
18
|
+
/** Simulated Fly.io GraphQL API; see the assumptions at the top of this file. */
|
|
19
|
+
export const flySim = {
|
|
20
|
+
env: { token: "FLY_API_TOKEN", url: "FLY_API_URL", testToken: "tok_fly" },
|
|
21
|
+
defaultSeed: { apps: { "demo-pr-42": {} } },
|
|
22
|
+
reset(_core, seed) {
|
|
23
|
+
const apps = {};
|
|
24
|
+
for (const [name, a] of Object.entries(seed?.apps ?? {})) {
|
|
25
|
+
apps[name] = { secrets: Object.fromEntries(Object.entries(a.secrets ?? {}).map(([k, v]) => [k, secret(v)])), releases: 0 };
|
|
26
|
+
}
|
|
27
|
+
return { apps };
|
|
28
|
+
},
|
|
29
|
+
/** `secrets.<app>.<NAME>`: a new value, or "delete". */
|
|
30
|
+
drift(_core, state, { key, rest, value, only }) {
|
|
31
|
+
if (only !== undefined)
|
|
32
|
+
return false;
|
|
33
|
+
const [kind, app, name] = rest.split(".");
|
|
34
|
+
if (kind !== "secrets" || !app || !name)
|
|
35
|
+
return false;
|
|
36
|
+
const a = state.apps[app];
|
|
37
|
+
if (!a)
|
|
38
|
+
throw new Error(`drift ${key}: no app ${app}`);
|
|
39
|
+
if (value === "delete")
|
|
40
|
+
delete a.secrets[name];
|
|
41
|
+
else
|
|
42
|
+
a.secrets[name] = secret(value);
|
|
43
|
+
return true;
|
|
44
|
+
},
|
|
45
|
+
authorized(headers) {
|
|
46
|
+
return /^(Bearer|FlyV1)\s+\S+/.test(headers.authorization ?? "");
|
|
47
|
+
},
|
|
48
|
+
isWrite(req) {
|
|
49
|
+
return isMutation(req.body);
|
|
50
|
+
},
|
|
51
|
+
routes(core, state, req) {
|
|
52
|
+
return routes(core, state, req);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
function secret(value) {
|
|
56
|
+
return { value, digest: createHash("sha256").update(value).digest("hex").slice(0, 16), createdAt: "2026-10-11T00:00:00Z" };
|
|
57
|
+
}
|
|
58
|
+
function input(v) {
|
|
59
|
+
return typeof v.input === "object" && v.input !== null ? v.input : {};
|
|
60
|
+
}
|
|
61
|
+
function appOf(state, name) {
|
|
62
|
+
const app = typeof name === "string" ? state.apps[name] : undefined;
|
|
63
|
+
return app ?? graphqlError("Could not find App", "NOT_FOUND");
|
|
64
|
+
}
|
|
65
|
+
function release(app, name, field) {
|
|
66
|
+
app.releases += 1;
|
|
67
|
+
return graphqlData({ [field]: { app: { name }, release: { id: `rel_${app.releases}`, version: app.releases } } });
|
|
68
|
+
}
|
|
69
|
+
const RESOLVERS = {
|
|
70
|
+
app(state, v) {
|
|
71
|
+
const name = v.appName ?? v.name;
|
|
72
|
+
const app = appOf(state, name);
|
|
73
|
+
if (app instanceof Reply)
|
|
74
|
+
return app;
|
|
75
|
+
return graphqlData({ app: { name, secrets: Object.entries(app.secrets).map(([n, s]) => ({ name: n, digest: s.digest, createdAt: s.createdAt })) } });
|
|
76
|
+
},
|
|
77
|
+
setSecrets(state, v) {
|
|
78
|
+
const i = input(v);
|
|
79
|
+
const app = appOf(state, i.appId);
|
|
80
|
+
if (app instanceof Reply)
|
|
81
|
+
return app;
|
|
82
|
+
const list = Array.isArray(i.secrets) ? i.secrets : [];
|
|
83
|
+
if (list.length === 0 || !list.every((s) => typeof s.key === "string" && typeof s.value === "string"))
|
|
84
|
+
return graphqlError("secrets must be a non-empty list of { key, value }", "BAD_USER_INPUT");
|
|
85
|
+
if (i.replaceAll === true)
|
|
86
|
+
app.secrets = {};
|
|
87
|
+
for (const s of list)
|
|
88
|
+
app.secrets[s.key] = secret(s.value);
|
|
89
|
+
return release(app, i.appId, "setSecrets");
|
|
90
|
+
},
|
|
91
|
+
unsetSecrets(state, v) {
|
|
92
|
+
const i = input(v);
|
|
93
|
+
const app = appOf(state, i.appId);
|
|
94
|
+
if (app instanceof Reply)
|
|
95
|
+
return app;
|
|
96
|
+
const keys = Array.isArray(i.keys) ? i.keys.filter((k) => typeof k === "string") : [];
|
|
97
|
+
if (keys.length === 0)
|
|
98
|
+
return graphqlError("keys must be a non-empty list", "BAD_USER_INPUT");
|
|
99
|
+
for (const k of keys)
|
|
100
|
+
delete app.secrets[k];
|
|
101
|
+
return release(app, i.appId, "unsetSecrets");
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
const routes = router([
|
|
105
|
+
route("POST", "/", ({ state, body }) => {
|
|
106
|
+
const op = graphqlOperation(body);
|
|
107
|
+
if (op instanceof Reply)
|
|
108
|
+
return op;
|
|
109
|
+
const resolve = RESOLVERS[op.root];
|
|
110
|
+
return resolve ? resolve(state, op.variables) : graphqlError(`Cannot query field "${op.root}" on type "${op.kind === "mutation" ? "Mutations" : "Queries"}"`, "GRAPHQL_VALIDATION_FAILED");
|
|
111
|
+
}),
|
|
112
|
+
], () => new Reply(404, { error: "not found" }));
|
|
113
|
+
//# sourceMappingURL=fly.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fly.js","sourceRoot":"","sources":["../../src/routes/fly.ts"],"names":[],"mappings":"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"}
|