@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.
- package/LICENSE +202 -0
- package/README.md +9 -2
- package/dist/auth.d.ts +60 -0
- package/dist/auth.js +195 -0
- package/dist/auth.js.map +1 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +16 -0
- package/dist/bin.js.map +1 -0
- package/dist/chaos.d.ts +74 -0
- package/dist/chaos.js +80 -0
- package/dist/chaos.js.map +1 -0
- 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 +46 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/provider.d.ts +138 -0
- package/dist/provider.js +68 -0
- package/dist/provider.js.map +1 -0
- 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/clerk.d.ts +33 -0
- package/dist/routes/clerk.js +85 -0
- package/dist/routes/clerk.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/neon.d.ts +73 -0
- package/dist/routes/neon.js +184 -0
- package/dist/routes/neon.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/vercel.d.ts +114 -0
- package/dist/routes/vercel.js +370 -0
- package/dist/routes/vercel.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.d.ts +4 -0
- package/dist/server.js +189 -0
- package/dist/server.js.map +1 -0
- package/dist/state.d.ts +102 -0
- package/dist/state.js +121 -0
- package/dist/state.js.map +1 -0
- package/package.json +51 -3
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract between the sim core (chaos, write log, ids, control endpoints) and one simulated provider.
|
|
3
|
+
* A provider lives in one `routes/<name>.ts` file: its state, its seed, its drift keys and its HTTP routes.
|
|
4
|
+
* Adding a provider = writing that file and registering it in `PROVIDERS` (state.ts).
|
|
5
|
+
*/
|
|
6
|
+
import type { IncomingHttpHeaders } from "node:http";
|
|
7
|
+
import type { ChaosConfig } from "./chaos.js";
|
|
8
|
+
/** Who made a record: the sim itself (seed, drift, a "human in the console") or a client through the API. */
|
|
9
|
+
export type CreatedBy = "sim" | "api";
|
|
10
|
+
/** What a provider needs from the core. */
|
|
11
|
+
export interface SimCore {
|
|
12
|
+
chaos: ChaosConfig;
|
|
13
|
+
/** Ids are unique across providers and deterministic after a reset. */
|
|
14
|
+
nextId(prefix: string): string;
|
|
15
|
+
}
|
|
16
|
+
/** A route's answer: status, JSON body and extra headers. Routes return one instead of writing to the socket. */
|
|
17
|
+
export declare class Reply {
|
|
18
|
+
status: number;
|
|
19
|
+
body: unknown;
|
|
20
|
+
headers: Record<string, string>;
|
|
21
|
+
constructor(status: number, body: unknown, headers?: Record<string, string>);
|
|
22
|
+
}
|
|
23
|
+
/** One request as a provider's routes see it, after auth and chaos. */
|
|
24
|
+
export interface RouteRequest {
|
|
25
|
+
method: string;
|
|
26
|
+
url: URL;
|
|
27
|
+
/** The path with the provider prefix stripped, e.g. `/v10/projects/prj_demo/env`. */
|
|
28
|
+
path: string;
|
|
29
|
+
body: unknown;
|
|
30
|
+
/** Request headers, lower-cased names (Node's `IncomingMessage.headers`, multi-valued ones joined). */
|
|
31
|
+
headers?: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A request as a provider's credential check sees it: what a signature (SigV4) or a Digest answer covers. `raw` is
|
|
35
|
+
* the body exactly as received.
|
|
36
|
+
*/
|
|
37
|
+
export interface AuthRequest {
|
|
38
|
+
method: string;
|
|
39
|
+
url: URL;
|
|
40
|
+
/** The request target as received (path and query, still percent-encoded). */
|
|
41
|
+
target: string;
|
|
42
|
+
raw: string;
|
|
43
|
+
}
|
|
44
|
+
/** One `<provider>.<rest>` drift key, as `ProviderSim.drift` receives it: a change "a human made in the console". */
|
|
45
|
+
export interface DriftRequest {
|
|
46
|
+
/** The whole drift key, for error messages. */
|
|
47
|
+
key: string;
|
|
48
|
+
/** The key after `<provider>.` or `<provider>:<project>.`, e.g. `env.preview.API_URL`. */
|
|
49
|
+
rest: string;
|
|
50
|
+
value: string;
|
|
51
|
+
/** `<project>` from a `<provider>:<project>.` key: only that project is affected. */
|
|
52
|
+
only?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* One simulated provider: its state, seed, drift keys and HTTP routes. Implement it in `routes/<name>.ts` and
|
|
56
|
+
* register it in `PROVIDERS` to simulate a new provider.
|
|
57
|
+
*
|
|
58
|
+
* Members are methods (not function-valued properties) so a `ProviderSim<VercelState, …>` is usable where the
|
|
59
|
+
* registry only knows `ProviderSim<unknown, unknown>`.
|
|
60
|
+
*/
|
|
61
|
+
export interface ProviderSim<S, Seed> {
|
|
62
|
+
/**
|
|
63
|
+
* Env var names the adapter reads its credential and base URL from, and the token the sim hands out; `more`:
|
|
64
|
+
* further credential variables and their placeholder values, for a credential made of two parts.
|
|
65
|
+
*/
|
|
66
|
+
env: {
|
|
67
|
+
token: string;
|
|
68
|
+
url: string;
|
|
69
|
+
testToken: string;
|
|
70
|
+
more?: Record<string, string>;
|
|
71
|
+
};
|
|
72
|
+
/** Seed used when a reset does not name this provider. */
|
|
73
|
+
defaultSeed: Seed;
|
|
74
|
+
/** Fresh state from a seed (`undefined`: empty). Called on every reset, in registry order. */
|
|
75
|
+
reset(core: SimCore, seed: Seed | undefined): S;
|
|
76
|
+
/** Apply one drift key to the state; false when the key is not one this provider knows. */
|
|
77
|
+
drift(core: SimCore, state: S, d: DriftRequest): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Whether a request carries a credential this provider accepts. Absent: any non-empty bearer token. Like the
|
|
80
|
+
* default, accept any non-empty value unless the provider's seed configures real credentials: the point is that a
|
|
81
|
+
* missing credential surfaces as 401. `state` is the provider's current state, for a provider whose seed declares
|
|
82
|
+
* how credentials arrive; `req` what a signature (SigV4) or a Digest answer covers. A `Reply` answers the request
|
|
83
|
+
* instead (a 401 with a Digest challenge).
|
|
84
|
+
*/
|
|
85
|
+
authorized?(headers: IncomingHttpHeaders, state: S, req: AuthRequest, core: SimCore): boolean | Reply;
|
|
86
|
+
/**
|
|
87
|
+
* Whether a POST, PUT, PATCH or DELETE changes state, for the write log (I4: `plan` writes nothing). Absent: every
|
|
88
|
+
* one does. A token endpoint, a GraphQL query or a read sent as a POST (an ARM list action) is a POST that does
|
|
89
|
+
* not. `state` is the provider's current state, for a provider whose seed declares such reads.
|
|
90
|
+
*/
|
|
91
|
+
isWrite?(req: {
|
|
92
|
+
method: string;
|
|
93
|
+
path: string;
|
|
94
|
+
body: unknown;
|
|
95
|
+
}, state: S): boolean;
|
|
96
|
+
/** Answer one authenticated, chaos-checked request under `/<name>/`. */
|
|
97
|
+
routes(core: SimCore, state: S, req: RouteRequest): Reply;
|
|
98
|
+
}
|
|
99
|
+
/** One page of `list` under chaos `page_size`, starting at the opaque numeric cursor. */
|
|
100
|
+
export declare function page<T>(core: SimCore, list: T[], cursor: string | null, limit?: number): {
|
|
101
|
+
items: T[];
|
|
102
|
+
next: number | null;
|
|
103
|
+
};
|
|
104
|
+
/** The `:name` segments of a route path, e.g. `"project" | "id"` for `/v9/projects/:project/env/:id`. */
|
|
105
|
+
type ParamNames<P extends string> = P extends `${string}:${infer N}/${infer Rest}` ? N | ParamNames<`/${Rest}`> : P extends `${string}:${infer N}` ? N : P extends `${string}*${infer N}` ? N : never;
|
|
106
|
+
/** The decoded `:name` segments of a matched path, typed from the route's path literal. */
|
|
107
|
+
export type PathParams<P extends string> = {
|
|
108
|
+
[K in ParamNames<P>]: string;
|
|
109
|
+
};
|
|
110
|
+
/** What a route's handler receives. */
|
|
111
|
+
export interface RouteContext<S, Params = Record<string, string>> {
|
|
112
|
+
core: SimCore;
|
|
113
|
+
state: S;
|
|
114
|
+
params: Params;
|
|
115
|
+
url: URL;
|
|
116
|
+
body: unknown;
|
|
117
|
+
/** Request headers, lower-cased names; empty when the caller passed none. */
|
|
118
|
+
headers: Record<string, string>;
|
|
119
|
+
}
|
|
120
|
+
/** One route of a simulated provider. Build it with `route`; serve a list of them with `router`. */
|
|
121
|
+
export interface Route<S> {
|
|
122
|
+
method: string;
|
|
123
|
+
path: string;
|
|
124
|
+
handle(ctx: RouteContext<S>): Reply;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* A route: an HTTP method, a path whose `:name` segments match one non-empty path segment each (and whose last
|
|
128
|
+
* segment may be `*name`, matching the rest of the path, slashes included, undecoded), and the handler that
|
|
129
|
+
* answers it. `params` is typed from the path: `route("GET", "/projects/:project", ({ params }) => …)` gets
|
|
130
|
+
* `params.project: string`.
|
|
131
|
+
*/
|
|
132
|
+
export declare function route<S, P extends string>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", path: P, handle: (ctx: RouteContext<S, PathParams<P>>) => Reply): Route<S>;
|
|
133
|
+
/**
|
|
134
|
+
* A provider's `routes`: the first route whose method and path match answers; when none does, `fallback` (the
|
|
135
|
+
* provider's own "not found" body). Path patterns are compiled once, here.
|
|
136
|
+
*/
|
|
137
|
+
export declare function router<S>(routes: Array<Route<S>>, fallback: () => Reply): (core: SimCore, state: S, req: RouteRequest) => Reply;
|
|
138
|
+
export {};
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** A route's answer: status, JSON body and extra headers. Routes return one instead of writing to the socket. */
|
|
2
|
+
export class Reply {
|
|
3
|
+
status;
|
|
4
|
+
body;
|
|
5
|
+
headers;
|
|
6
|
+
constructor(status, body, headers = {}) {
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.body = body;
|
|
9
|
+
this.headers = headers;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** One page of `list` under chaos `page_size`, starting at the opaque numeric cursor. */
|
|
13
|
+
export function page(core, list, cursor, limit) {
|
|
14
|
+
// Unpaginated (the default): everything, whatever cursor the client sends.
|
|
15
|
+
if (core.chaos.page_size <= 0 && limit === undefined)
|
|
16
|
+
return { items: list, next: null };
|
|
17
|
+
const size = core.chaos.page_size > 0 ? Math.min(core.chaos.page_size, limit ?? Infinity) : (limit ?? list.length);
|
|
18
|
+
const start = cursor !== null && /^\d+$/.test(cursor) ? Number(cursor) : 0;
|
|
19
|
+
const items = list.slice(start, start + size);
|
|
20
|
+
return { items, next: start + size < list.length ? start + size : null };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A route: an HTTP method, a path whose `:name` segments match one non-empty path segment each (and whose last
|
|
24
|
+
* segment may be `*name`, matching the rest of the path, slashes included, undecoded), and the handler that
|
|
25
|
+
* answers it. `params` is typed from the path: `route("GET", "/projects/:project", ({ params }) => …)` gets
|
|
26
|
+
* `params.project: string`.
|
|
27
|
+
*/
|
|
28
|
+
export function route(method, path, handle) {
|
|
29
|
+
// `router` only calls a handler with the params its own path declares.
|
|
30
|
+
return { method, path, handle: handle };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A provider's `routes`: the first route whose method and path match answers; when none does, `fallback` (the
|
|
34
|
+
* provider's own "not found" body). Path patterns are compiled once, here.
|
|
35
|
+
*/
|
|
36
|
+
export function router(routes, fallback) {
|
|
37
|
+
const compiled = routes.map((r) => {
|
|
38
|
+
const names = [];
|
|
39
|
+
const source = r.path
|
|
40
|
+
.split("/")
|
|
41
|
+
.map((seg) => {
|
|
42
|
+
if (seg.startsWith("*")) {
|
|
43
|
+
names.push(seg.slice(1));
|
|
44
|
+
return "(.+)";
|
|
45
|
+
}
|
|
46
|
+
if (!seg.startsWith(":"))
|
|
47
|
+
return seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
48
|
+
names.push(seg.slice(1));
|
|
49
|
+
return "([^/]+)";
|
|
50
|
+
})
|
|
51
|
+
.join("/");
|
|
52
|
+
return { route: r, pattern: new RegExp(`^${source}$`), names, rest: r.path.split("/").some((s) => s.startsWith("*")) };
|
|
53
|
+
});
|
|
54
|
+
return (core, state, req) => {
|
|
55
|
+
for (const { route: r, pattern, names, rest } of compiled) {
|
|
56
|
+
if (r.method !== req.method)
|
|
57
|
+
continue;
|
|
58
|
+
const m = pattern.exec(req.path);
|
|
59
|
+
if (!m)
|
|
60
|
+
continue;
|
|
61
|
+
// A `*name` segment (always the last) is passed as matched: its own segments decode separately.
|
|
62
|
+
const params = Object.fromEntries(names.map((n, i) => [n, rest && i === names.length - 1 ? m[i + 1] : decodeURIComponent(m[i + 1])]));
|
|
63
|
+
return r.handle({ core, state, params, url: req.url, body: req.body, headers: req.headers ?? {} });
|
|
64
|
+
}
|
|
65
|
+
return fallback();
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type CreatedBy, type ProviderSim } from "../provider.js";
|
|
2
|
+
/** An index as the simulated Algolia stores it. */
|
|
3
|
+
export interface AlgoliaIndex {
|
|
4
|
+
/** Number of records. */
|
|
5
|
+
entries: number;
|
|
6
|
+
settings: Record<string, unknown>;
|
|
7
|
+
createdBy: CreatedBy;
|
|
8
|
+
}
|
|
9
|
+
/** A queued task: the copy it performs when published, and how many polls answer `notPublished` first. */
|
|
10
|
+
interface AlgoliaTask {
|
|
11
|
+
index: string;
|
|
12
|
+
copy?: {
|
|
13
|
+
from: string;
|
|
14
|
+
scope?: string[];
|
|
15
|
+
};
|
|
16
|
+
pending: number;
|
|
17
|
+
}
|
|
18
|
+
/** The simulated Algolia's state: one application's indices and its tasks. */
|
|
19
|
+
export interface AlgoliaState {
|
|
20
|
+
indices: Record<string, AlgoliaIndex>;
|
|
21
|
+
tasks: Record<string, AlgoliaTask>;
|
|
22
|
+
/** How many polls of a new task answer `notPublished` before it is published (default 1). */
|
|
23
|
+
taskPolls: number;
|
|
24
|
+
}
|
|
25
|
+
/** What a scenario or test seeds the simulated Algolia with (`seed: { algolia: … }`). */
|
|
26
|
+
export interface AlgoliaSeed {
|
|
27
|
+
/** Indices that exist already (created "by a human", so unmanaged). */
|
|
28
|
+
indices?: Record<string, {
|
|
29
|
+
entries?: number;
|
|
30
|
+
settings?: Record<string, unknown>;
|
|
31
|
+
}>;
|
|
32
|
+
/** Polls of a new task that answer `notPublished` before it is published. */
|
|
33
|
+
taskPolls?: number;
|
|
34
|
+
}
|
|
35
|
+
/** The simulated Algolia API, registered in `PROVIDERS` (packages/sim/src/state.ts) and served under `/algolia`. */
|
|
36
|
+
export declare const algoliaSim: ProviderSim<AlgoliaState, AlgoliaSeed>;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Reply, route, router } from "../provider.js";
|
|
2
|
+
/** The simulated Algolia API, registered in `PROVIDERS` (packages/sim/src/state.ts) and served under `/algolia`. */
|
|
3
|
+
export const algoliaSim = {
|
|
4
|
+
env: { token: "ALGOLIA_API_KEY", url: "ALGOLIA_API_URL", testToken: "tok_algolia" },
|
|
5
|
+
defaultSeed: { indices: { products: { entries: 3, settings: { searchableAttributes: ["name"] } } } },
|
|
6
|
+
reset(_core, seed) {
|
|
7
|
+
const indices = {};
|
|
8
|
+
for (const [name, i] of Object.entries(seed?.indices ?? {}))
|
|
9
|
+
indices[name] = { entries: i.entries ?? 0, settings: structuredClone(i.settings ?? {}), createdBy: "sim" };
|
|
10
|
+
return { indices, tasks: {}, taskPolls: seed?.taskPolls ?? 1 };
|
|
11
|
+
},
|
|
12
|
+
/** `index.<name>`: "delete" — what a human in the Algolia dashboard would do. */
|
|
13
|
+
drift(_core, state, { key, rest, value, only }) {
|
|
14
|
+
if (only !== undefined || !rest.startsWith("index."))
|
|
15
|
+
return false;
|
|
16
|
+
const name = rest.slice("index.".length);
|
|
17
|
+
if (!state.indices[name])
|
|
18
|
+
throw new Error(`drift ${key}: no index ${name}`);
|
|
19
|
+
if (value !== "delete")
|
|
20
|
+
throw new Error(`drift ${key}: only "delete" is supported`);
|
|
21
|
+
delete state.indices[name];
|
|
22
|
+
return true;
|
|
23
|
+
},
|
|
24
|
+
/** AL2: both headers, the key non-empty. */
|
|
25
|
+
authorized(headers) {
|
|
26
|
+
const key = headers["x-algolia-api-key"];
|
|
27
|
+
const app = headers["x-algolia-application-id"];
|
|
28
|
+
return typeof key === "string" && key !== "" && typeof app === "string" && app !== "";
|
|
29
|
+
},
|
|
30
|
+
routes(core, state, req) {
|
|
31
|
+
return routes(core, state, req);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
function task(core, state, t) {
|
|
35
|
+
const id = Number(core.nextId("").replace(/\D/g, "")) + 1000;
|
|
36
|
+
state.tasks[String(id)] = { ...t, pending: state.taskPolls };
|
|
37
|
+
return id;
|
|
38
|
+
}
|
|
39
|
+
/** The copy a task performs when it is published (AL3, AL5). */
|
|
40
|
+
function publish(state, t) {
|
|
41
|
+
if (!t.copy)
|
|
42
|
+
return;
|
|
43
|
+
const from = state.indices[t.copy.from];
|
|
44
|
+
const before = state.indices[t.index];
|
|
45
|
+
const all = t.copy.scope === undefined;
|
|
46
|
+
state.indices[t.index] = {
|
|
47
|
+
entries: all ? (from?.entries ?? 0) : (before?.entries ?? 0),
|
|
48
|
+
settings: all || t.copy.scope.includes("settings") ? structuredClone(from?.settings ?? {}) : (before?.settings ?? {}),
|
|
49
|
+
createdBy: "api",
|
|
50
|
+
};
|
|
51
|
+
delete t.copy;
|
|
52
|
+
}
|
|
53
|
+
const routes = router([
|
|
54
|
+
route("GET", "/1/indexes", ({ state }) => new Reply(200, { items: Object.entries(state.indices).map(([name, i]) => ({ name, entries: i.entries })), nbPages: 1 })),
|
|
55
|
+
route("POST", "/1/indexes/:name/operation", ({ core, state, params, body }) => {
|
|
56
|
+
const b = (body ?? {});
|
|
57
|
+
if (b.operation !== "copy" || typeof b.destination !== "string" || b.destination === "")
|
|
58
|
+
return new Reply(400, { message: "operation must be copy with a destination" });
|
|
59
|
+
const scope = Array.isArray(b.scope) ? b.scope : undefined;
|
|
60
|
+
const id = task(core, state, { index: b.destination, copy: { from: params.name, ...(scope ? { scope } : {}) } });
|
|
61
|
+
return new Reply(200, { taskID: id, updatedAt: new Date().toISOString() });
|
|
62
|
+
}),
|
|
63
|
+
route("GET", "/1/indexes/:name/task/:task", ({ state, params }) => {
|
|
64
|
+
const t = state.tasks[params.task];
|
|
65
|
+
if (!t || t.index !== params.name)
|
|
66
|
+
return new Reply(404, { message: "Task does not exist" });
|
|
67
|
+
if (t.pending > 0) {
|
|
68
|
+
t.pending--;
|
|
69
|
+
return new Reply(200, { status: "notPublished" });
|
|
70
|
+
}
|
|
71
|
+
publish(state, t);
|
|
72
|
+
return new Reply(200, { status: "published" });
|
|
73
|
+
}),
|
|
74
|
+
route("GET", "/1/indexes/:name/settings", ({ state, params }) => {
|
|
75
|
+
const i = state.indices[params.name];
|
|
76
|
+
return i ? new Reply(200, i.settings) : new Reply(404, { message: "Index does not exist", status: 404 });
|
|
77
|
+
}),
|
|
78
|
+
route("DELETE", "/1/indexes/:name", ({ core, state, params }) => {
|
|
79
|
+
delete state.indices[params.name];
|
|
80
|
+
return new Reply(200, { taskID: task(core, state, { index: params.name }), deletedAt: new Date().toISOString() });
|
|
81
|
+
}),
|
|
82
|
+
], () => new Reply(404, { message: "not found" }));
|
|
83
|
+
//# sourceMappingURL=algolia.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"algolia.js","sourceRoot":"","sources":["../../src/routes/algolia.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAkD,MAAM,gBAAgB,CAAC;AAiCtG,oHAAoH;AACpH,MAAM,CAAC,MAAM,UAAU,GAA2C;IAChE,GAAG,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,iBAAiB,EAAE,SAAS,EAAE,aAAa,EAAE;IACnF,WAAW,EAAE,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,oBAAoB,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE;IAEpG,KAAK,CAAC,KAAK,EAAE,IAAI;QACf,MAAM,OAAO,GAA4B,EAAE,CAAC;QAC5C,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,CAAC,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;QACxK,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,IAAI,CAAC,EAAE,CAAC;IACjE,CAAC;IAED,iFAAiF;IACjF,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;QAC5C,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QACnE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,SAAS,GAAG,cAAc,IAAI,EAAE,CAAC,CAAC;QAC5E,IAAI,KAAK,KAAK,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,SAAS,GAAG,8BAA8B,CAAC,CAAC;QACpF,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,4CAA4C;IAC5C,UAAU,CAAC,OAA4B;QACrC,MAAM,GAAG,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;QACzC,MAAM,GAAG,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC;QAChD,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,EAAE,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,EAAE,CAAC;IACxF,CAAC;IAED,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG;QACrB,OAAO,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IAClC,CAAC;CACF,CAAC;AAEF,SAAS,IAAI,CAAC,IAAa,EAAE,KAAmB,EAAE,CAA+B;IAC/E,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;IAC7D,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC;IAC7D,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,gEAAgE;AAChE,SAAS,OAAO,CAAC,KAAmB,EAAE,CAAc;IAClD,IAAI,CAAC,CAAC,CAAC,IAAI;QAAE,OAAO;IACpB,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC;IACvC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG;QACvB,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC,CAAC;QAC5D,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,KAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,IAAI,EAAE,CAAC;QACtH,SAAS,EAAE,KAAK;KACjB,CAAC;IACF,OAAO,CAAC,CAAC,IAAI,CAAC;AAChB,CAAC;AAED,MAAM,MAAM,GAAG,MAAM,CACnB;IACE,KAAK,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAElK,KAAK,CAAC,MAAM,EAAE,4BAA4B,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE;QAC5E,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAoE,CAAC;QAC1F,IAAI,CAAC,CAAC,SAAS,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ,IAAI,CAAC,CAAC,WAAW,KAAK,EAAE;YAAE,OAAO,IAAI,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,2CAA2C,EAAE,CAAC,CAAC;QACzK,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,KAAkB,CAAC,CAAC,CAAC,SAAS,CAAC;QACzE,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACjH,OAAO,IAAI,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAC7E,CAAC,CAAC;IAEF,KAAK,CAAC,KAAK,EAAE,6BAA6B,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE;QAChE,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,IAAI;YAAE,OAAO,IAAI,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC,CAAC;QAC7F,IAAI,CAAC,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;YAClB,CAAC,CAAC,OAAO,EAAE,CAAC;YACZ,OAAO,IAAI,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAClB,OAAO,IAAI,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IACjD,CAAC,CAAC;IAEF,KAAK,CAAC,KAAK,EAAE,2BAA2B,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE;QAC9D,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,sBAAsB,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3G,CAAC,CAAC;IAEF,KAAK,CAAC,QAAQ,EAAE,kBAAkB,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE;QAC9D,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAClC,OAAO,IAAI,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACpH,CAAC,CAAC;CACH,EACD,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAC/C,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type ProviderSim } from "../provider.js";
|
|
2
|
+
/** One S3 CORS rule as stored. */
|
|
3
|
+
export interface AwsCorsRule {
|
|
4
|
+
ID?: string;
|
|
5
|
+
AllowedOrigin: string[];
|
|
6
|
+
AllowedMethod: string[];
|
|
7
|
+
AllowedHeader: string[];
|
|
8
|
+
ExposeHeader: string[];
|
|
9
|
+
MaxAgeSeconds?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Simulated AWS state: buckets with their CORS configuration, Amplify apps with their branches. */
|
|
12
|
+
export interface AwsState {
|
|
13
|
+
/** Access key id → secret access key. */
|
|
14
|
+
keys: Record<string, string>;
|
|
15
|
+
region: string;
|
|
16
|
+
buckets: Record<string, {
|
|
17
|
+
cors: AwsCorsRule[] | null;
|
|
18
|
+
}>;
|
|
19
|
+
amplify: Record<string, {
|
|
20
|
+
branches: Record<string, {
|
|
21
|
+
environmentVariables: Record<string, string>;
|
|
22
|
+
}>;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
/** Initial AWS state. Buckets are listed by name; Amplify apps by id, with their branches' variables. */
|
|
26
|
+
export interface AwsSeed {
|
|
27
|
+
keys?: Record<string, string>;
|
|
28
|
+
region?: string;
|
|
29
|
+
buckets?: Record<string, {
|
|
30
|
+
cors?: AwsCorsRule[] | null;
|
|
31
|
+
}>;
|
|
32
|
+
amplify?: Record<string, {
|
|
33
|
+
branches: Record<string, {
|
|
34
|
+
environmentVariables?: Record<string, string>;
|
|
35
|
+
}>;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
/** The access key id the sim's default seed accepts (`AWS_SIM_ACCESS_KEY_ID` in `simEnv`). */
|
|
39
|
+
export declare const AWS_SIM_ACCESS_KEY_ID = "AKIASIMEXAMPLE0042";
|
|
40
|
+
/** Its secret (`AWS_SIM_SECRET_ACCESS_KEY` in `simEnv`). */
|
|
41
|
+
export declare const AWS_SIM_SECRET_ACCESS_KEY = "sim/aws-secret-access-key-0042";
|
|
42
|
+
/** Simulated AWS (S3 bucket CORS, Amplify branch variables); see the assumptions at the top of this file. */
|
|
43
|
+
export declare const awsSim: ProviderSim<AwsState, AwsSeed>;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simulated AWS APIs signed with SigV4: S3 bucket CORS configuration (XML) and Amplify branch environment
|
|
3
|
+
* variables (JSON). They exist to test the generic `http` adapter's `auth: { aws_sigv4 }` and `encoding: xml`
|
|
4
|
+
* against something that checks both: every request's signature is recomputed (../auth.ts) and refused when wrong.
|
|
5
|
+
*
|
|
6
|
+
* Path-style, under `/aws`: `/aws/s3/<bucket>/?cors` stands for `https://<bucket>.s3.<region>.amazonaws.com/?cors`,
|
|
7
|
+
* `/aws/amplify/apps/<app>/branches/<branch>` for `https://amplify.<region>.amazonaws.com/apps/<app>/branches/<branch>`.
|
|
8
|
+
*
|
|
9
|
+
* Assumptions (docs/api-verification.md, "AWS through the generic adapter"):
|
|
10
|
+
* A1. Requests are signed with SigV4 for the path's service (`s3`, `amplify`) and the seeded region; S3 requests
|
|
11
|
+
* carry `x-amz-content-sha256`. Verified: IAM "Create a signed AWS API request"; S3 "Common request headers".
|
|
12
|
+
* A2. `PUT /?cors` takes a `CORSConfiguration` of 1 to 100 `CORSRule`s (each with at least one `AllowedOrigin` and
|
|
13
|
+
* one `AllowedMethod`) and requires `Content-MD5` (or an x-amz-checksum); it replaces the whole configuration
|
|
14
|
+
* and answers 200 with no body. An empty configuration is `MalformedXML`. Verified: S3 API Reference
|
|
15
|
+
* PutBucketCors; botocore `requestChecksumRequired`.
|
|
16
|
+
* A3. `GET /?cors` answers the configuration as XML; a bucket without one answers 404 `NoSuchCORSConfiguration`.
|
|
17
|
+
* Unverified (the 404 code from memory); `DELETE /?cors` answers 204. Verified: DeleteBucketCors.
|
|
18
|
+
* A4. Amplify `GetBranch` (`GET`) and `UpdateBranch` (`POST`) on `/apps/{appId}/branches/{branchName}` answer
|
|
19
|
+
* `{ branch: { …, environmentVariables } }`; `environmentVariables` in an update replaces the whole map.
|
|
20
|
+
* Verified: Amplify API Reference UpdateBranch, GetBranch. Errors are JSON `{ message }` with
|
|
21
|
+
* `x-amzn-ErrorType`; unknown app or branch is 404 `NotFoundException`.
|
|
22
|
+
*/
|
|
23
|
+
import { createHash } from "node:crypto";
|
|
24
|
+
import { sigV4Problem } from "../auth.js";
|
|
25
|
+
import { Reply, route, router } from "../provider.js";
|
|
26
|
+
/** The access key id the sim's default seed accepts (`AWS_SIM_ACCESS_KEY_ID` in `simEnv`). */
|
|
27
|
+
export const AWS_SIM_ACCESS_KEY_ID = "AKIASIMEXAMPLE0042";
|
|
28
|
+
/** Its secret (`AWS_SIM_SECRET_ACCESS_KEY` in `simEnv`). */
|
|
29
|
+
export const AWS_SIM_SECRET_ACCESS_KEY = "sim/aws-secret-access-key-0042";
|
|
30
|
+
/** Simulated AWS (S3 bucket CORS, Amplify branch variables); see the assumptions at the top of this file. */
|
|
31
|
+
export const awsSim = {
|
|
32
|
+
env: { token: "AWS_SIM_SECRET_ACCESS_KEY", url: "AWS_SIM_URL", testToken: AWS_SIM_SECRET_ACCESS_KEY, more: { AWS_SIM_ACCESS_KEY_ID: AWS_SIM_ACCESS_KEY_ID } },
|
|
33
|
+
defaultSeed: { buckets: { assets: { cors: null } }, amplify: { d1demo: { branches: { "pr-42": {} } } } },
|
|
34
|
+
reset(_core, seed) {
|
|
35
|
+
const buckets = {};
|
|
36
|
+
for (const [name, b] of Object.entries(seed?.buckets ?? {}))
|
|
37
|
+
buckets[name] = { cors: b.cors ? structuredClone(b.cors) : null };
|
|
38
|
+
const amplify = {};
|
|
39
|
+
for (const [app, a] of Object.entries(seed?.amplify ?? {})) {
|
|
40
|
+
amplify[app] = { branches: Object.fromEntries(Object.entries(a.branches).map(([n, b]) => [n, { environmentVariables: { ...(b.environmentVariables ?? {}) } }])) };
|
|
41
|
+
}
|
|
42
|
+
return { keys: seed?.keys ?? { [AWS_SIM_ACCESS_KEY_ID]: AWS_SIM_SECRET_ACCESS_KEY }, region: seed?.region ?? "us-east-1", buckets, amplify };
|
|
43
|
+
},
|
|
44
|
+
/**
|
|
45
|
+
* `s3.<bucket>.cors`: "delete" removes the configuration; "remove:<ID>" one rule.
|
|
46
|
+
* `amplify.<app>.<branch>.<NAME>`: a new value, or "delete".
|
|
47
|
+
*/
|
|
48
|
+
drift(_core, state, { key, rest, value, only }) {
|
|
49
|
+
if (only !== undefined)
|
|
50
|
+
return false;
|
|
51
|
+
const [kind, a, b, c] = rest.split(".");
|
|
52
|
+
if (kind === "s3" && a && b === "cors")
|
|
53
|
+
return driftCors(state, a, value, key);
|
|
54
|
+
if (kind === "amplify" && a && b && c) {
|
|
55
|
+
const branch = state.amplify[a]?.branches[b];
|
|
56
|
+
if (!branch)
|
|
57
|
+
throw new Error(`drift ${key}: no branch ${a}/${b}`);
|
|
58
|
+
if (value === "delete")
|
|
59
|
+
delete branch.environmentVariables[c];
|
|
60
|
+
else
|
|
61
|
+
branch.environmentVariables[c] = value;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
},
|
|
66
|
+
authorized(headers, state, req) {
|
|
67
|
+
return authorized(headers, req, state);
|
|
68
|
+
},
|
|
69
|
+
routes(core, state, req) {
|
|
70
|
+
return routes(core, state, req);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
function driftCors(state, bucket, value, key) {
|
|
74
|
+
const b = state.buckets[bucket];
|
|
75
|
+
if (!b)
|
|
76
|
+
throw new Error(`drift ${key}: no bucket ${bucket}`);
|
|
77
|
+
if (value === "delete")
|
|
78
|
+
b.cors = null;
|
|
79
|
+
else if (value.startsWith("remove:"))
|
|
80
|
+
b.cors = (b.cors ?? []).filter((r) => r.ID !== value.slice(7));
|
|
81
|
+
else
|
|
82
|
+
throw new Error(`drift ${key}: "delete" or "remove:<ID>"`);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
/** A1: the signature must be valid for the service the path belongs to. */
|
|
86
|
+
function authorized(headers, req, state) {
|
|
87
|
+
const service = /^\/aws\/(s3|amplify)\//.exec(req.url.pathname)?.[1];
|
|
88
|
+
if (!service)
|
|
89
|
+
return false;
|
|
90
|
+
if (service === "s3" && headers["x-amz-content-sha256"] === undefined)
|
|
91
|
+
return s3Error(400, "InvalidRequest", "Missing required header for this request: x-amz-content-sha256");
|
|
92
|
+
const problem = sigV4Problem(headers, req, { keys: state.keys, region: state.region, service });
|
|
93
|
+
if (problem === null)
|
|
94
|
+
return true;
|
|
95
|
+
return service === "s3" ? s3Error(403, problem === "InvalidAccessKeyId" ? problem : "SignatureDoesNotMatch", problem) : amplifyError(401, "UnauthorizedException", problem);
|
|
96
|
+
}
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// S3 CORS (XML)
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
const XML = { "content-type": "application/xml" };
|
|
101
|
+
function escape(s) {
|
|
102
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
103
|
+
}
|
|
104
|
+
function unescape(s) {
|
|
105
|
+
return s.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
106
|
+
}
|
|
107
|
+
function s3Error(status, code, message) {
|
|
108
|
+
return new Reply(status, `<?xml version="1.0" encoding="UTF-8"?><Error><Code>${code}</Code><Message>${escape(message)}</Message></Error>`, XML);
|
|
109
|
+
}
|
|
110
|
+
function corsXml(rules) {
|
|
111
|
+
const el = (name, values) => values.map((v) => `<${name}>${escape(v)}</${name}>`).join("");
|
|
112
|
+
const body = rules
|
|
113
|
+
.map((r) => `<CORSRule>${r.ID !== undefined ? el("ID", [r.ID]) : ""}${el("AllowedHeader", r.AllowedHeader)}${el("AllowedMethod", r.AllowedMethod)}${el("AllowedOrigin", r.AllowedOrigin)}${el("ExposeHeader", r.ExposeHeader)}${r.MaxAgeSeconds !== undefined ? el("MaxAgeSeconds", [r.MaxAgeSeconds]) : ""}</CORSRule>`)
|
|
114
|
+
.join("");
|
|
115
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">${body}</CORSConfiguration>`;
|
|
116
|
+
}
|
|
117
|
+
/** The rules of a `CORSConfiguration` document; a reason when it is not one S3 accepts (A2). */
|
|
118
|
+
function parseCors(xml) {
|
|
119
|
+
if (/<!DOCTYPE|<!ENTITY/i.test(xml))
|
|
120
|
+
return "DTDs are not accepted";
|
|
121
|
+
if (!/<CORSConfiguration(\s[^>]*)?>[\s\S]*<\/CORSConfiguration>\s*$/.test(xml.replace(/^<\?xml[^>]*\?>\s*/, "")))
|
|
122
|
+
return "the root element must be CORSConfiguration";
|
|
123
|
+
const rules = [];
|
|
124
|
+
for (const m of xml.matchAll(/<CORSRule>([\s\S]*?)<\/CORSRule>/g)) {
|
|
125
|
+
const values = (name) => [...m[1].matchAll(new RegExp(`<${name}>([^<]*)</${name}>`, "g"))].map((x) => unescape(x[1]));
|
|
126
|
+
const rule = { AllowedOrigin: values("AllowedOrigin"), AllowedMethod: values("AllowedMethod"), AllowedHeader: values("AllowedHeader"), ExposeHeader: values("ExposeHeader") };
|
|
127
|
+
const id = values("ID")[0];
|
|
128
|
+
const age = values("MaxAgeSeconds")[0];
|
|
129
|
+
if (id !== undefined)
|
|
130
|
+
rule.ID = id;
|
|
131
|
+
if (age !== undefined)
|
|
132
|
+
rule.MaxAgeSeconds = age;
|
|
133
|
+
if (rule.AllowedOrigin.length === 0 || rule.AllowedMethod.length === 0)
|
|
134
|
+
return "every CORSRule needs an AllowedOrigin and an AllowedMethod";
|
|
135
|
+
if (rule.AllowedMethod.some((x) => !["GET", "PUT", "POST", "DELETE", "HEAD"].includes(x)))
|
|
136
|
+
return "AllowedMethod must be GET, PUT, POST, DELETE or HEAD";
|
|
137
|
+
rules.push(rule);
|
|
138
|
+
}
|
|
139
|
+
if (rules.length === 0 || rules.length > 100)
|
|
140
|
+
return "a CORSConfiguration holds 1 to 100 CORSRules";
|
|
141
|
+
return rules;
|
|
142
|
+
}
|
|
143
|
+
function putCors(bucket, headers, body) {
|
|
144
|
+
const xml = typeof body === "string" ? body : "";
|
|
145
|
+
const md5 = headers["content-md5"];
|
|
146
|
+
if (md5 === undefined)
|
|
147
|
+
return s3Error(400, "InvalidRequest", "Missing required header for this request: Content-Md5");
|
|
148
|
+
if (md5 !== createHash("md5").update(xml, "utf8").digest("base64"))
|
|
149
|
+
return s3Error(400, "BadDigest", "The Content-MD5 you specified did not match what we received.");
|
|
150
|
+
const rules = parseCors(xml);
|
|
151
|
+
if (typeof rules === "string")
|
|
152
|
+
return s3Error(400, "MalformedXML", rules);
|
|
153
|
+
bucket.cors = rules;
|
|
154
|
+
return new Reply(200, "", XML);
|
|
155
|
+
}
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// Amplify branches (JSON)
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
function amplifyError(status, type, message) {
|
|
160
|
+
return new Reply(status, { message }, { "x-amzn-errortype": type });
|
|
161
|
+
}
|
|
162
|
+
function branchView(app, name, b) {
|
|
163
|
+
return new Reply(200, { branch: { branchArn: `arn:aws:amplify:us-east-1:000000000000:apps/${app}/branches/${name}`, branchName: name, stage: "PULL_REQUEST", environmentVariables: { ...b.environmentVariables } } });
|
|
164
|
+
}
|
|
165
|
+
function updateBranch(app, name, b, body) {
|
|
166
|
+
if (typeof body !== "object" || body === null)
|
|
167
|
+
return amplifyError(400, "BadRequestException", "body must be a JSON object");
|
|
168
|
+
const vars = body.environmentVariables;
|
|
169
|
+
if (vars !== undefined) {
|
|
170
|
+
if (typeof vars !== "object" || vars === null || !Object.values(vars).every((v) => typeof v === "string"))
|
|
171
|
+
return amplifyError(400, "BadRequestException", "environmentVariables must map names to strings");
|
|
172
|
+
b.environmentVariables = { ...vars };
|
|
173
|
+
}
|
|
174
|
+
return branchView(app, name, b);
|
|
175
|
+
}
|
|
176
|
+
const routes = router([
|
|
177
|
+
route("GET", "/s3/:bucket/", ({ state, params, url }) => {
|
|
178
|
+
const b = state.buckets[params.bucket];
|
|
179
|
+
if (!b)
|
|
180
|
+
return s3Error(404, "NoSuchBucket", "The specified bucket does not exist");
|
|
181
|
+
if (!url.searchParams.has("cors"))
|
|
182
|
+
return s3Error(501, "NotImplemented", "only ?cors is simulated");
|
|
183
|
+
return b.cors ? new Reply(200, corsXml(b.cors), XML) : s3Error(404, "NoSuchCORSConfiguration", "The CORS configuration does not exist");
|
|
184
|
+
}),
|
|
185
|
+
route("PUT", "/s3/:bucket/", ({ state, params, url, headers, body }) => {
|
|
186
|
+
const b = state.buckets[params.bucket];
|
|
187
|
+
if (!b)
|
|
188
|
+
return s3Error(404, "NoSuchBucket", "The specified bucket does not exist");
|
|
189
|
+
if (!url.searchParams.has("cors"))
|
|
190
|
+
return s3Error(501, "NotImplemented", "only ?cors is simulated");
|
|
191
|
+
return putCors(b, headers, body);
|
|
192
|
+
}),
|
|
193
|
+
route("DELETE", "/s3/:bucket/", ({ state, params, url }) => {
|
|
194
|
+
const b = state.buckets[params.bucket];
|
|
195
|
+
if (!b)
|
|
196
|
+
return s3Error(404, "NoSuchBucket", "The specified bucket does not exist");
|
|
197
|
+
if (!url.searchParams.has("cors"))
|
|
198
|
+
return s3Error(501, "NotImplemented", "only ?cors is simulated");
|
|
199
|
+
b.cors = null;
|
|
200
|
+
return new Reply(204, null);
|
|
201
|
+
}),
|
|
202
|
+
route("GET", "/amplify/apps/:app/branches/:branch", ({ state, params }) => {
|
|
203
|
+
const b = state.amplify[params.app]?.branches[params.branch];
|
|
204
|
+
return b ? branchView(params.app, params.branch, b) : amplifyError(404, "NotFoundException", "branch not found");
|
|
205
|
+
}),
|
|
206
|
+
route("POST", "/amplify/apps/:app/branches/:branch", ({ state, params, body }) => {
|
|
207
|
+
const b = state.amplify[params.app]?.branches[params.branch];
|
|
208
|
+
return b ? updateBranch(params.app, params.branch, b, body) : amplifyError(404, "NotFoundException", "branch not found");
|
|
209
|
+
}),
|
|
210
|
+
], () => s3Error(404, "NotFound", "not simulated"));
|
|
211
|
+
//# sourceMappingURL=aws.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aws.js","sourceRoot":"","sources":["../../src/routes/aws.ts"],"names":[],"mappings":"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"}
|