@arp-protocol/cli 0.0.0-stage → 0.0.3
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/CHANGELOG.md +74 -0
- package/LICENSE +26 -0
- package/README.md +79 -2
- package/dist/chunks/backfill-awengrsd.js +1048 -0
- package/dist/chunks/ccip-zxtpc4fx.js +198 -0
- package/dist/chunks/cli-00qp1sj2.js +116 -0
- package/dist/chunks/cli-0gsbnq5w.js +4043 -0
- package/dist/chunks/cli-10wacn76.js +7741 -0
- package/dist/chunks/cli-3kbbwfbp.js +359 -0
- package/dist/chunks/cli-64fx73kx.js +43 -0
- package/dist/chunks/cli-d3dy7cxe.js +2110 -0
- package/dist/chunks/cli-fbwpcfvp.js +1033 -0
- package/dist/chunks/cli-g5zvr10v.js +19 -0
- package/dist/chunks/cli-mhzh9z8k.js +2042 -0
- package/dist/chunks/cli-n4qagybd.js +307 -0
- package/dist/chunks/cli-wev0jgf7.js +15274 -0
- package/dist/chunks/commands-157bms70.js +866 -0
- package/dist/chunks/env-n5ac8d1z.js +57 -0
- package/dist/chunks/index-jrs2shcc.js +53 -0
- package/dist/chunks/line-691gbaha.js +166 -0
- package/dist/chunks/node-init-rcepfyeh.js +255 -0
- package/dist/chunks/node-m0mx4y5k.js +593 -0
- package/dist/chunks/secp256k1-j2w0pmjr.js +17 -0
- package/dist/chunks/server-59zry2jp.js +6824 -0
- package/dist/chunks/share-vtbkzs5f.js +104 -0
- package/dist/cli.js +258 -0
- package/dist/hook.js +4337 -0
- package/package.json +54 -4
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
generatePrivateKey
|
|
4
|
+
} from "./cli-fbwpcfvp.js";
|
|
5
|
+
|
|
6
|
+
// ../../packages/protocol/dist/node/serve.js
|
|
7
|
+
import { createServer } from "node:http";
|
|
8
|
+
import { PassThrough } from "node:stream";
|
|
9
|
+
var DEFAULT_SERVE_MAX_BODY_BYTES = 1024 * 1024;
|
|
10
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
11
|
+
var TOO_LARGE_GRACE_MS = 1000;
|
|
12
|
+
|
|
13
|
+
class BodyTooLarge extends Error {
|
|
14
|
+
}
|
|
15
|
+
function bodyOf(message, limit) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const chunks = [];
|
|
18
|
+
let size = 0;
|
|
19
|
+
const done = () => {
|
|
20
|
+
message.off("data", onData);
|
|
21
|
+
message.off("end", onEnd);
|
|
22
|
+
message.off("error", onError);
|
|
23
|
+
};
|
|
24
|
+
const onData = (chunk) => {
|
|
25
|
+
const bytes = typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk instanceof Uint8Array ? chunk : new Uint8Array(0);
|
|
26
|
+
size += bytes.byteLength;
|
|
27
|
+
if (size > limit) {
|
|
28
|
+
done();
|
|
29
|
+
message.pause();
|
|
30
|
+
message.on("error", () => {
|
|
31
|
+
return;
|
|
32
|
+
});
|
|
33
|
+
reject(new BodyTooLarge);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
chunks.push(bytes);
|
|
37
|
+
};
|
|
38
|
+
const onEnd = () => {
|
|
39
|
+
done();
|
|
40
|
+
const out = new Uint8Array(size);
|
|
41
|
+
let at = 0;
|
|
42
|
+
for (const chunk of chunks) {
|
|
43
|
+
out.set(chunk, at);
|
|
44
|
+
at += chunk.byteLength;
|
|
45
|
+
}
|
|
46
|
+
resolve(out);
|
|
47
|
+
};
|
|
48
|
+
const onError = (error) => {
|
|
49
|
+
done();
|
|
50
|
+
reject(error);
|
|
51
|
+
};
|
|
52
|
+
message.on("data", onData);
|
|
53
|
+
message.on("end", onEnd);
|
|
54
|
+
message.on("error", onError);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function requestOf(message, body, base) {
|
|
58
|
+
const headers = new Headers;
|
|
59
|
+
for (const [name, value] of Object.entries(message.headers)) {
|
|
60
|
+
if (value === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
for (const item of Array.isArray(value) ? value : [value])
|
|
63
|
+
headers.append(name, item);
|
|
64
|
+
}
|
|
65
|
+
return new Request(new URL(message.url ?? "/", base), {
|
|
66
|
+
method: message.method ?? "GET",
|
|
67
|
+
headers,
|
|
68
|
+
body: body === null || body.byteLength === 0 ? null : body
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async function send(response, out) {
|
|
72
|
+
const headers = {};
|
|
73
|
+
response.headers.forEach((value, name) => {
|
|
74
|
+
headers[name] = value;
|
|
75
|
+
});
|
|
76
|
+
out.writeHead(response.status, headers);
|
|
77
|
+
out.end(response.body === null ? undefined : new Uint8Array(await response.arrayBuffer()));
|
|
78
|
+
}
|
|
79
|
+
function serveFetch(options, handle) {
|
|
80
|
+
const hostname = options.hostname ?? "127.0.0.1";
|
|
81
|
+
const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_SERVE_MAX_BODY_BYTES;
|
|
82
|
+
const base = `http://${hostname}:${options.port}`;
|
|
83
|
+
const server = createServer((message, out) => {
|
|
84
|
+
(async () => {
|
|
85
|
+
try {
|
|
86
|
+
const hasBody = message.method !== "GET" && message.method !== "HEAD";
|
|
87
|
+
const body = hasBody ? await bodyOf(message, maxBodyBytes) : null;
|
|
88
|
+
const ip = message.socket.remoteAddress ?? "unknown";
|
|
89
|
+
await send(await handle(requestOf(message, body, base), ip), out);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (out.headersSent) {
|
|
92
|
+
out.destroy();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const tooLarge = error instanceof BodyTooLarge;
|
|
96
|
+
out.writeHead(tooLarge ? 413 : 500, {
|
|
97
|
+
"content-type": "application/json",
|
|
98
|
+
...tooLarge ? { connection: "close" } : {}
|
|
99
|
+
});
|
|
100
|
+
if (tooLarge) {
|
|
101
|
+
const drop = () => message.socket.destroy();
|
|
102
|
+
out.once("finish", drop);
|
|
103
|
+
setTimeout(drop, TOO_LARGE_GRACE_MS).unref();
|
|
104
|
+
}
|
|
105
|
+
out.end(JSON.stringify({ error: tooLarge ? "body too large" : "internal error" }));
|
|
106
|
+
}
|
|
107
|
+
})();
|
|
108
|
+
});
|
|
109
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
110
|
+
server.requestTimeout = requestTimeoutMs;
|
|
111
|
+
server.headersTimeout = Math.min(server.headersTimeout, requestTimeoutMs);
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
server.once("error", reject);
|
|
114
|
+
server.listen(options.port, hostname, () => resolve(server));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function bufferedCopy(stream) {
|
|
118
|
+
return stream.pipe(new PassThrough);
|
|
119
|
+
}
|
|
120
|
+
async function* linesOf(stream) {
|
|
121
|
+
const decoder = new TextDecoder;
|
|
122
|
+
let buffered = "";
|
|
123
|
+
for await (const chunk of stream) {
|
|
124
|
+
buffered += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
|
|
125
|
+
let newline = buffered.indexOf(`
|
|
126
|
+
`);
|
|
127
|
+
while (newline !== -1) {
|
|
128
|
+
const line = buffered.slice(0, newline).trim();
|
|
129
|
+
buffered = buffered.slice(newline + 1);
|
|
130
|
+
newline = buffered.indexOf(`
|
|
131
|
+
`);
|
|
132
|
+
if (line.length > 0)
|
|
133
|
+
yield line;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const rest = buffered.trim();
|
|
137
|
+
if (rest.length > 0)
|
|
138
|
+
yield rest;
|
|
139
|
+
}
|
|
140
|
+
// ../../packages/protocol/dist/node/local-state.js
|
|
141
|
+
import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
142
|
+
import { homedir } from "node:os";
|
|
143
|
+
import { dirname, join, resolve } from "node:path";
|
|
144
|
+
function arpHome(env = process.env, home = homedir()) {
|
|
145
|
+
const configured = env["ARP_HOME"]?.trim();
|
|
146
|
+
return configured !== undefined && configured !== "" ? configured : join(home, ".arp");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
class MovedStateError extends Error {
|
|
150
|
+
legacy;
|
|
151
|
+
current;
|
|
152
|
+
constructor(legacy, current) {
|
|
153
|
+
super(`${legacy} exists but ${current} does not. State now lives under ~/.arp (or ARP_HOME), ` + `not the working directory. Move it before starting: mv ${legacy} ${current}`);
|
|
154
|
+
this.legacy = legacy;
|
|
155
|
+
this.current = current;
|
|
156
|
+
this.name = "MovedStateError";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function refuseMovedState(legacy, current) {
|
|
160
|
+
if (resolve(legacy) === resolve(current))
|
|
161
|
+
return;
|
|
162
|
+
if (existsSync(legacy) && !existsSync(current))
|
|
163
|
+
throw new MovedStateError(legacy, current);
|
|
164
|
+
}
|
|
165
|
+
function stateFile(override, name, options = {}) {
|
|
166
|
+
if (override !== undefined && override.trim() !== "")
|
|
167
|
+
return override.trim();
|
|
168
|
+
const current = join(arpHome(options.env ?? process.env, options.home ?? homedir()), name);
|
|
169
|
+
refuseMovedState(join(options.cwd ?? process.cwd(), ".arp", name), current);
|
|
170
|
+
return current;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
class KeyFileError extends Error {
|
|
174
|
+
constructor(message) {
|
|
175
|
+
super(message);
|
|
176
|
+
this.name = "KeyFileError";
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
var PRIVATE_KEY = /^0x[0-9a-fA-F]{64}$/;
|
|
180
|
+
function readKeyFile(path, options = {}) {
|
|
181
|
+
const stat = statSync(path);
|
|
182
|
+
if (!stat.isFile())
|
|
183
|
+
throw new KeyFileError(`${path} is not a file`);
|
|
184
|
+
if ((options.platform ?? process.platform) !== "win32" && (stat.mode & 63) !== 0) {
|
|
185
|
+
throw new KeyFileError(`${path} has mode ${(stat.mode & 511).toString(8)}; it must be 600 (chmod 600 ${path})`);
|
|
186
|
+
}
|
|
187
|
+
const raw = readFileSync(path, "utf8").trim();
|
|
188
|
+
const key = raw.startsWith("0x") ? raw : `0x${raw}`;
|
|
189
|
+
if (!PRIVATE_KEY.test(key))
|
|
190
|
+
throw new KeyFileError(`${path} does not hold a 32-byte hex private key`);
|
|
191
|
+
return key;
|
|
192
|
+
}
|
|
193
|
+
function loadOrCreateKeyFile(path, options = {}) {
|
|
194
|
+
if (existsSync(path))
|
|
195
|
+
return { key: readKeyFile(path, options), created: false };
|
|
196
|
+
const key = (options.generate ?? generatePrivateKey)();
|
|
197
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
198
|
+
writeFileSync(path, `${key}
|
|
199
|
+
`, { mode: 384, flag: "wx" });
|
|
200
|
+
if ((options.platform ?? process.platform) !== "win32")
|
|
201
|
+
chmodSync(path, 384);
|
|
202
|
+
return { key, created: true };
|
|
203
|
+
}
|
|
204
|
+
function appendJsonLine(path, value) {
|
|
205
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
206
|
+
appendFileSync(path, `${JSON.stringify(value)}
|
|
207
|
+
`);
|
|
208
|
+
}
|
|
209
|
+
function readJsonLines(path) {
|
|
210
|
+
if (!existsSync(path))
|
|
211
|
+
return [];
|
|
212
|
+
const out = [];
|
|
213
|
+
for (const line of readFileSync(path, "utf8").split(`
|
|
214
|
+
`)) {
|
|
215
|
+
if (line.trim().length === 0)
|
|
216
|
+
continue;
|
|
217
|
+
try {
|
|
218
|
+
out.push(JSON.parse(line));
|
|
219
|
+
} catch {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
function alive(pid) {
|
|
226
|
+
try {
|
|
227
|
+
process.kill(pid, 0);
|
|
228
|
+
return true;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
return error.code === "EPERM";
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
var HELD_HERE = new Set;
|
|
234
|
+
function lockFile(path) {
|
|
235
|
+
const lock = `${path}.lock`;
|
|
236
|
+
mkdirSync(dirname(lock), { recursive: true });
|
|
237
|
+
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
238
|
+
let fd;
|
|
239
|
+
try {
|
|
240
|
+
fd = openSync(lock, "wx", 384);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (error.code !== "EEXIST")
|
|
243
|
+
throw error;
|
|
244
|
+
const holder = Number(readFileSync(lock, "utf8").trim());
|
|
245
|
+
const ownLeftover = holder === process.pid && !HELD_HERE.has(lock);
|
|
246
|
+
if (Number.isInteger(holder) && holder > 0 && !ownLeftover && alive(holder)) {
|
|
247
|
+
throw new Error(`${path} is held by process ${holder} (${lock}); one process per ledger`);
|
|
248
|
+
}
|
|
249
|
+
unlinkSync(lock);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
writeSync(fd, `${process.pid}
|
|
253
|
+
`);
|
|
254
|
+
closeSync(fd);
|
|
255
|
+
HELD_HERE.add(lock);
|
|
256
|
+
let held = true;
|
|
257
|
+
const release = () => {
|
|
258
|
+
if (!held)
|
|
259
|
+
return;
|
|
260
|
+
held = false;
|
|
261
|
+
HELD_HERE.delete(lock);
|
|
262
|
+
try {
|
|
263
|
+
if (readFileSync(lock, "utf8").trim() === String(process.pid))
|
|
264
|
+
unlinkSync(lock);
|
|
265
|
+
} catch {}
|
|
266
|
+
};
|
|
267
|
+
process.once("exit", release);
|
|
268
|
+
return { release };
|
|
269
|
+
}
|
|
270
|
+
throw new Error(`${lock} could not be taken`);
|
|
271
|
+
}
|
|
272
|
+
function bigintSafe(_key, inner) {
|
|
273
|
+
return typeof inner === "bigint" ? inner.toString() : inner;
|
|
274
|
+
}
|
|
275
|
+
function jsonResponse(value, status = 200) {
|
|
276
|
+
return new Response(JSON.stringify(value, bigintSafe, 2), {
|
|
277
|
+
status,
|
|
278
|
+
headers: { "content-type": "application/json" }
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function redactUrls(text) {
|
|
282
|
+
return text.replace(/\b(https?|wss?):\/\/([^/\s?#"']+)[^\s"']*/gi, (_match, scheme, host) => {
|
|
283
|
+
const bare = host.includes("@") ? host.slice(host.lastIndexOf("@") + 1) : host;
|
|
284
|
+
return `${scheme}://${bare}/…`;
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
function loopbackHosts(port) {
|
|
288
|
+
return [`127.0.0.1:${port}`, `localhost:${port}`, `[::1]:${port}`];
|
|
289
|
+
}
|
|
290
|
+
function refuseForeignRequest(request, policy) {
|
|
291
|
+
const host = (request.headers.get("host") ?? "").toLowerCase();
|
|
292
|
+
if (!policy.hosts.some((allowed) => allowed.toLowerCase() === host)) {
|
|
293
|
+
return jsonResponse({ error: "this service answers to its own host only" }, 403);
|
|
294
|
+
}
|
|
295
|
+
const origin = request.headers.get("origin");
|
|
296
|
+
if (origin !== null && !(policy.origins ?? []).includes(origin)) {
|
|
297
|
+
return jsonResponse({ error: `origin ${origin} is not allowed` }, 403);
|
|
298
|
+
}
|
|
299
|
+
if (request.method === "POST") {
|
|
300
|
+
const type = (request.headers.get("content-type") ?? "").split(";")[0]?.trim().toLowerCase();
|
|
301
|
+
if (type !== "application/json") {
|
|
302
|
+
return jsonResponse({ error: "content-type must be application/json" }, 415);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
export { DEFAULT_SERVE_MAX_BODY_BYTES, DEFAULT_REQUEST_TIMEOUT_MS, serveFetch, bufferedCopy, linesOf, arpHome, MovedStateError, refuseMovedState, stateFile, KeyFileError, readKeyFile, loadOrCreateKeyFile, appendJsonLine, readJsonLines, lockFile, bigintSafe, jsonResponse, redactUrls, loopbackHosts, refuseForeignRequest };
|