@ryusenkei/maezae 0.4.0 → 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/dist/dev.d.ts +1 -0
- package/dist/dev.js +100 -0
- package/dist/types/cms-contract/src/errors.d.ts +1 -0
- package/dist/types/sdk/src/dev.d.ts +22 -0
- package/package.json +6 -2
package/dist/dev.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './types/sdk/src/dev.js';
|
package/dist/dev.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { i as SIGNED_HEADERS, s as signedHeaders } from "./src-Baj-EC2G.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { createServer } from "node:http";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
// src/dev.ts
|
|
7
|
+
/** 記録する応答のヘッダー(配信のキャッシュの照合と本文の形式) */
|
|
8
|
+
const KEPT_HEADERS = [
|
|
9
|
+
"content-type",
|
|
10
|
+
"etag",
|
|
11
|
+
"x-stale"
|
|
12
|
+
];
|
|
13
|
+
/** 記録のディレクトリの URL(末尾はスラッシュ) */
|
|
14
|
+
const dirUrl = (dir) => {
|
|
15
|
+
const url = dir instanceof URL ? dir : pathToFileURL(dir);
|
|
16
|
+
return url.href.endsWith("/") ? url : new URL(`${url.href}/`);
|
|
17
|
+
};
|
|
18
|
+
/** 呼び出しの記録のファイル */
|
|
19
|
+
const fileOf = (dir, method, path, ifNoneMatch, body) => new URL(`${createHash("sha256").update(`${method} ${path}\n${ifNoneMatch}\n${body}`).digest("hex").slice(0, 16)}.json`, dir);
|
|
20
|
+
/** upstream へ中継(API の鍵を付与、下書きの署名は upstream の URL で改めて付与) */
|
|
21
|
+
async function relay({ origin, apiKey, previewSecret }, path, init) {
|
|
22
|
+
const url = `${origin.replace(/\/$/, "")}${path}`;
|
|
23
|
+
const body = typeof init.body === "string" ? init.body : null;
|
|
24
|
+
const headers = new Headers(init.headers);
|
|
25
|
+
if (previewSecret && headers.has(SIGNED_HEADERS.preview)) headers.set(SIGNED_HEADERS.preview, (await signedHeaders(previewSecret, {
|
|
26
|
+
method: init.method ?? "GET",
|
|
27
|
+
url,
|
|
28
|
+
body
|
|
29
|
+
}, { header: SIGNED_HEADERS.preview })).get(SIGNED_HEADERS.preview));
|
|
30
|
+
headers.set("authorization", `Bearer ${apiKey}`);
|
|
31
|
+
return fetch(url, {
|
|
32
|
+
...init,
|
|
33
|
+
headers
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** 記録の配信 API(port Delivery、upstream があれば中継して保存、並行の書き込みは一時のファイルから置き換え) */
|
|
37
|
+
function fixtureDelivery({ dir, upstream }) {
|
|
38
|
+
const base = dirUrl(dir);
|
|
39
|
+
return async (path, init = {}) => {
|
|
40
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
41
|
+
const file = fileOf(base, method, path, new Headers(init.headers).get("if-none-match") ?? "", typeof init.body === "string" ? init.body : "");
|
|
42
|
+
let recorded;
|
|
43
|
+
if (upstream) {
|
|
44
|
+
const res = await relay(upstream, path, init);
|
|
45
|
+
recorded = {
|
|
46
|
+
method,
|
|
47
|
+
path,
|
|
48
|
+
status: res.status,
|
|
49
|
+
headers: Object.fromEntries(KEPT_HEADERS.flatMap((h) => res.headers.has(h) ? [[h, res.headers.get(h)]] : [])),
|
|
50
|
+
body: await res.text()
|
|
51
|
+
};
|
|
52
|
+
mkdirSync(base, { recursive: true });
|
|
53
|
+
const tmp = new URL(`${file.href}.${process.pid}.tmp`);
|
|
54
|
+
writeFileSync(tmp, `${JSON.stringify(recorded, null, 2)}\n`);
|
|
55
|
+
renameSync(tmp, file);
|
|
56
|
+
} else {
|
|
57
|
+
if (!existsSync(file)) throw new Error(`配信 API の記録なし: ${method} ${path}(upstream を指定して記録)`);
|
|
58
|
+
recorded = JSON.parse(readFileSync(file, "utf8"));
|
|
59
|
+
}
|
|
60
|
+
return new Response(recorded.status === 304 ? null : recorded.body, {
|
|
61
|
+
status: recorded.status,
|
|
62
|
+
headers: recorded.headers
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** 手元の配信 API のサーバー(/content/v1/… だけ、記録にない呼び出しは502、戻り値は origin と停止の関数) */
|
|
67
|
+
async function serveDelivery(options) {
|
|
68
|
+
const delivery = fixtureDelivery(options);
|
|
69
|
+
const server = createServer(async (req, res) => {
|
|
70
|
+
const chunks = [];
|
|
71
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
72
|
+
const path = req.url ?? "/";
|
|
73
|
+
if (!path.startsWith("/content/v1")) {
|
|
74
|
+
res.writeHead(404).end();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const headers = Object.entries(req.headers).flatMap(([k, v]) => typeof v === "string" && k !== "host" ? [[k, v]] : []);
|
|
78
|
+
try {
|
|
79
|
+
const response = await delivery(path, {
|
|
80
|
+
method: req.method ?? "GET",
|
|
81
|
+
headers,
|
|
82
|
+
...chunks.length ? { body: Buffer.concat(chunks).toString("utf8") } : {}
|
|
83
|
+
});
|
|
84
|
+
const out = {};
|
|
85
|
+
response.headers.forEach((v, k) => out[k] = v);
|
|
86
|
+
res.writeHead(response.status, out);
|
|
87
|
+
res.end(Buffer.from(await response.arrayBuffer()));
|
|
88
|
+
} catch (e) {
|
|
89
|
+
res.writeHead(502, { "content-type": "text/plain; charset=utf-8" }).end(e instanceof Error ? e.message : String(e));
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
const hostname = options.hostname ?? "127.0.0.1";
|
|
93
|
+
await new Promise((resolve) => server.listen(options.port ?? 0, hostname, resolve));
|
|
94
|
+
const address = server.address();
|
|
95
|
+
return {
|
|
96
|
+
origin: `http://${hostname}:${typeof address === "object" && address ? address.port : options.port}`,
|
|
97
|
+
close: () => new Promise((resolve) => server.close(() => resolve()))
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export { fixtureDelivery, serveDelivery };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Delivery } from '../../delivery-client/src/index.js';
|
|
2
|
+
/** 公開の配信 API の取得先(origin は媒体の識別子までの URL、例: <公開の配信 API のオリジン>/demo) */
|
|
3
|
+
export type Upstream = Readonly<{
|
|
4
|
+
origin: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
previewSecret?: string;
|
|
7
|
+
}>;
|
|
8
|
+
/** 記録の配信 API の指定(dir は記録のディレクトリ、upstream があれば中継して記録) */
|
|
9
|
+
export type FixtureOptions = Readonly<{
|
|
10
|
+
dir: string | URL;
|
|
11
|
+
upstream?: Upstream;
|
|
12
|
+
}>;
|
|
13
|
+
/** 記録の配信 API(port Delivery、upstream があれば中継して保存、並行の書き込みは一時のファイルから置き換え) */
|
|
14
|
+
export declare function fixtureDelivery({ dir, upstream }: FixtureOptions): Delivery;
|
|
15
|
+
/** 手元の配信 API のサーバー(/content/v1/… だけ、記録にない呼び出しは502、戻り値は origin と停止の関数) */
|
|
16
|
+
export declare function serveDelivery(options: FixtureOptions & Readonly<{
|
|
17
|
+
port?: number;
|
|
18
|
+
hostname?: string;
|
|
19
|
+
}>): Promise<Readonly<{
|
|
20
|
+
origin: string;
|
|
21
|
+
close: () => Promise<void>;
|
|
22
|
+
}>>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryusenkei/maezae",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "maezae の導入先のサイト向けの SDK(配信 API
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "maezae の導入先のサイト向けの SDK(配信 API の取得・本文の型・プレビューの署名・手元の開発の配信 API)",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "RYUSENKEI",
|
|
7
7
|
"repository": {
|
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
"./trace": {
|
|
40
40
|
"types": "./dist/trace.d.ts",
|
|
41
41
|
"default": "./dist/trace.js"
|
|
42
|
+
},
|
|
43
|
+
"./dev": {
|
|
44
|
+
"types": "./dist/dev.d.ts",
|
|
45
|
+
"default": "./dist/dev.js"
|
|
42
46
|
}
|
|
43
47
|
},
|
|
44
48
|
"files": [
|