@eventpipe/cli 0.2.1
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 +79 -0
- package/dist/auth-fetch.d.ts +5 -0
- package/dist/auth-fetch.js +29 -0
- package/dist/base-url.d.ts +2 -0
- package/dist/base-url.js +8 -0
- package/dist/build-bundle.d.ts +12 -0
- package/dist/build-bundle.js +48 -0
- package/dist/cli-version.d.ts +3 -0
- package/dist/cli-version.js +48 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +221 -0
- package/dist/cmd-create.d.ts +1 -0
- package/dist/cmd-create.js +34 -0
- package/dist/cmd-listen.d.ts +2 -0
- package/dist/cmd-listen.js +90 -0
- package/dist/cmd-login.d.ts +1 -0
- package/dist/cmd-login.js +91 -0
- package/dist/cmd-update.d.ts +1 -0
- package/dist/cmd-update.js +20 -0
- package/dist/code-node-ids.d.ts +1 -0
- package/dist/code-node-ids.js +20 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +42 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/credentials.d.ts +7 -0
- package/dist/credentials.js +42 -0
- package/dist/forward-local.d.ts +14 -0
- package/dist/forward-local.js +76 -0
- package/dist/hash.d.ts +2 -0
- package/dist/hash.js +7 -0
- package/dist/listen-args.d.ts +9 -0
- package/dist/listen-args.js +37 -0
- package/dist/publish.d.ts +20 -0
- package/dist/publish.js +23 -0
- package/dist/studio-sources.d.ts +5 -0
- package/dist/studio-sources.js +65 -0
- package/examples/forward-test-server.mjs +30 -0
- package/examples/stripe-webhook/.eventpipe/bundle.js +3 -0
- package/examples/stripe-webhook/.eventpipe/code.bundle.js +3 -0
- package/examples/stripe-webhook/.eventpipe/code.reexport.ts +1 -0
- package/examples/stripe-webhook/.eventpipe/entry.reexport.ts +1 -0
- package/examples/stripe-webhook/README.md +42 -0
- package/examples/stripe-webhook/eventpipe.json +28 -0
- package/examples/stripe-webhook/eventpipe.json.bak +27 -0
- package/examples/stripe-webhook/package.json +8 -0
- package/examples/stripe-webhook/src/handler.ts +44 -0
- package/examples/stripe-webhook/src/other.ts +1 -0
- package/install/macos.sh +32 -0
- package/install/windows.ps1 +35 -0
- package/package.json +31 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { randomInt } from "node:crypto";
|
|
4
|
+
import { resolveEventpipeBaseUrl } from "./base-url.js";
|
|
5
|
+
import { saveCredentials } from "./credentials.js";
|
|
6
|
+
const cors = {
|
|
7
|
+
"Access-Control-Allow-Origin": "*",
|
|
8
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
9
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
10
|
+
};
|
|
11
|
+
function openBrowser(url) {
|
|
12
|
+
const safe = url.replace(/"/g, '\\"');
|
|
13
|
+
const cmd = process.platform === "darwin"
|
|
14
|
+
? `open "${safe}"`
|
|
15
|
+
: process.platform === "win32"
|
|
16
|
+
? `cmd /c start "" "${safe}"`
|
|
17
|
+
: `xdg-open "${safe}"`;
|
|
18
|
+
exec(cmd, () => { });
|
|
19
|
+
}
|
|
20
|
+
export async function cmdLogin() {
|
|
21
|
+
const base = resolveEventpipeBaseUrl();
|
|
22
|
+
const port = randomInt(47_890, 48_000);
|
|
23
|
+
await new Promise((resolve, reject) => {
|
|
24
|
+
let settled = false;
|
|
25
|
+
const done = (fn) => {
|
|
26
|
+
if (settled) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
settled = true;
|
|
30
|
+
fn();
|
|
31
|
+
};
|
|
32
|
+
const server = createServer((req, res) => {
|
|
33
|
+
const u = req.url ?? "";
|
|
34
|
+
if (req.method === "OPTIONS" && u.startsWith("/callback")) {
|
|
35
|
+
res.writeHead(204, cors);
|
|
36
|
+
res.end();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (req.method === "POST" && u.startsWith("/callback")) {
|
|
40
|
+
const chunks = [];
|
|
41
|
+
req.on("data", (c) => chunks.push(c));
|
|
42
|
+
req.on("end", () => {
|
|
43
|
+
void (async () => {
|
|
44
|
+
try {
|
|
45
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
46
|
+
const data = JSON.parse(raw);
|
|
47
|
+
if (!data.access_token || !data.refresh_token) {
|
|
48
|
+
res.writeHead(400, { "content-type": "application/json", ...cors });
|
|
49
|
+
res.end(JSON.stringify({ error: "missing tokens" }));
|
|
50
|
+
done(() => reject(new Error("Missing tokens in callback")));
|
|
51
|
+
server.close();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const stored = {
|
|
55
|
+
baseUrl: (data.base_url ?? base).replace(/\/$/, ""),
|
|
56
|
+
accessToken: data.access_token,
|
|
57
|
+
refreshToken: data.refresh_token,
|
|
58
|
+
};
|
|
59
|
+
await saveCredentials(stored);
|
|
60
|
+
res.writeHead(200, { "content-type": "application/json", ...cors });
|
|
61
|
+
res.end(JSON.stringify({ ok: true }));
|
|
62
|
+
server.close(() => done(() => resolve()));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
res.writeHead(400, { ...cors });
|
|
66
|
+
res.end();
|
|
67
|
+
done(() => reject(new Error("Invalid callback body")));
|
|
68
|
+
server.close();
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
res.writeHead(404);
|
|
75
|
+
res.end();
|
|
76
|
+
});
|
|
77
|
+
server.on("error", (e) => done(() => reject(e)));
|
|
78
|
+
server.listen(port, "127.0.0.1", () => {
|
|
79
|
+
const authorizeUrl = `${base}/cli/authorize?port=${port}`;
|
|
80
|
+
console.log("Opening browser to sign in…");
|
|
81
|
+
console.log(authorizeUrl);
|
|
82
|
+
openBrowser(authorizeUrl);
|
|
83
|
+
});
|
|
84
|
+
const timer = setTimeout(() => {
|
|
85
|
+
server.close();
|
|
86
|
+
done(() => reject(new Error("Login timed out (2 min)")));
|
|
87
|
+
}, 120_000);
|
|
88
|
+
server.on("close", () => clearTimeout(timer));
|
|
89
|
+
});
|
|
90
|
+
console.log("Saved credentials to ~/.eventpipe/credentials.json");
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cmdUpdate(): Promise<void>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
const PACKAGE = "@eventpipe/cli";
|
|
3
|
+
function runNpm(args) {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
const isWin = process.platform === "win32";
|
|
6
|
+
const cmd = isWin ? "npm.cmd" : "npm";
|
|
7
|
+
const child = spawn(cmd, args, {
|
|
8
|
+
stdio: "inherit",
|
|
9
|
+
shell: isWin,
|
|
10
|
+
});
|
|
11
|
+
child.on("error", () => resolve(1));
|
|
12
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export async function cmdUpdate() {
|
|
16
|
+
const code = await runNpm(["install", "-g", `${PACKAGE}@latest`]);
|
|
17
|
+
if (code !== 0) {
|
|
18
|
+
throw new Error(`npm exited with code ${code}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function collectCodeNodeIds(pipe: unknown): string[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function collectCodeNodeIds(pipe) {
|
|
2
|
+
if (typeof pipe !== "object" || pipe === null) {
|
|
3
|
+
return [];
|
|
4
|
+
}
|
|
5
|
+
const nodes = pipe.nodes;
|
|
6
|
+
if (!Array.isArray(nodes)) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
const out = [];
|
|
10
|
+
for (const n of nodes) {
|
|
11
|
+
if (typeof n !== "object" || n === null) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const rec = n;
|
|
15
|
+
if (rec.type === "code" && typeof rec.id === "string") {
|
|
16
|
+
out.push(rec.id);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
export async function loadManifest(projectDir) {
|
|
4
|
+
const path = resolve(projectDir, "eventpipe.json");
|
|
5
|
+
const raw = await readFile(path, "utf8");
|
|
6
|
+
const parsed = JSON.parse(raw);
|
|
7
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
8
|
+
throw new Error("eventpipe.json must be a JSON object");
|
|
9
|
+
}
|
|
10
|
+
const o = parsed;
|
|
11
|
+
const pipelineId = typeof o.pipelineId === "string" ? o.pipelineId.trim() : "";
|
|
12
|
+
if (!pipelineId) {
|
|
13
|
+
throw new Error("eventpipe.json: pipelineId (string) is required");
|
|
14
|
+
}
|
|
15
|
+
const settings = o.settings;
|
|
16
|
+
if (typeof settings !== "object" || settings === null || Array.isArray(settings)) {
|
|
17
|
+
throw new Error("eventpipe.json: settings must be an object");
|
|
18
|
+
}
|
|
19
|
+
const pipe = settings.pipe;
|
|
20
|
+
if (pipe === undefined || pipe === null) {
|
|
21
|
+
throw new Error("eventpipe.json: settings.pipe is required for publish");
|
|
22
|
+
}
|
|
23
|
+
let codeNodes = undefined;
|
|
24
|
+
if (o.codeNodes !== undefined) {
|
|
25
|
+
if (typeof o.codeNodes !== "object" || o.codeNodes === null || Array.isArray(o.codeNodes)) {
|
|
26
|
+
throw new Error("eventpipe.json: codeNodes must be an object map (string -> string)");
|
|
27
|
+
}
|
|
28
|
+
codeNodes = o.codeNodes;
|
|
29
|
+
for (const [k, v] of Object.entries(codeNodes)) {
|
|
30
|
+
if (typeof v !== "string") {
|
|
31
|
+
throw new Error(`eventpipe.json: codeNodes["${k}"] must be a string (node id)`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
pipelineId,
|
|
37
|
+
nodeId: typeof o.nodeId === "string" ? o.nodeId.trim() : undefined,
|
|
38
|
+
entry: typeof o.entry === "string" ? o.entry.trim() : undefined,
|
|
39
|
+
codeNodes,
|
|
40
|
+
settings: settings,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const dir = () => join(homedir(), ".eventpipe");
|
|
5
|
+
const filePath = () => join(dir(), "credentials.json");
|
|
6
|
+
export async function loadCredentials() {
|
|
7
|
+
try {
|
|
8
|
+
const raw = await readFile(filePath(), "utf8");
|
|
9
|
+
const data = JSON.parse(raw);
|
|
10
|
+
if (typeof data.baseUrl === "string" &&
|
|
11
|
+
typeof data.accessToken === "string" &&
|
|
12
|
+
typeof data.refreshToken === "string") {
|
|
13
|
+
return {
|
|
14
|
+
baseUrl: data.baseUrl.replace(/\/$/, ""),
|
|
15
|
+
accessToken: data.accessToken,
|
|
16
|
+
refreshToken: data.refreshToken,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
export async function saveCredentials(c) {
|
|
26
|
+
await mkdir(dir(), { recursive: true });
|
|
27
|
+
const normalized = {
|
|
28
|
+
...c,
|
|
29
|
+
baseUrl: c.baseUrl.replace(/\/$/, ""),
|
|
30
|
+
};
|
|
31
|
+
await writeFile(filePath(), JSON.stringify(normalized, null, 2), "utf8");
|
|
32
|
+
try {
|
|
33
|
+
await chmodSafe(filePath());
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* ignore */
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function chmodSafe(path) {
|
|
40
|
+
const { chmod } = await import("node:fs/promises");
|
|
41
|
+
await chmod(path, 0o600);
|
|
42
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type FlowEventWire = {
|
|
2
|
+
webhookId?: string;
|
|
3
|
+
method?: string;
|
|
4
|
+
headers?: Record<string, string>;
|
|
5
|
+
query?: Record<string, string>;
|
|
6
|
+
path?: string;
|
|
7
|
+
body?: unknown;
|
|
8
|
+
receivedAt?: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function forwardWebhookToLocal(forwardTo: string, event: FlowEventWire): Promise<{
|
|
11
|
+
ok: boolean;
|
|
12
|
+
status: number;
|
|
13
|
+
error?: string;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const HOP_BY_HOP = new Set([
|
|
2
|
+
"host",
|
|
3
|
+
"connection",
|
|
4
|
+
"content-length",
|
|
5
|
+
"transfer-encoding",
|
|
6
|
+
"keep-alive",
|
|
7
|
+
"proxy-connection",
|
|
8
|
+
"te",
|
|
9
|
+
"trailer",
|
|
10
|
+
"upgrade",
|
|
11
|
+
]);
|
|
12
|
+
export async function forwardWebhookToLocal(forwardTo, event) {
|
|
13
|
+
let url;
|
|
14
|
+
try {
|
|
15
|
+
url = new URL(forwardTo);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return { ok: false, status: 0, error: "Invalid --forward-to URL" };
|
|
19
|
+
}
|
|
20
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
21
|
+
return { ok: false, status: 0, error: "Only http(s) URLs are allowed for --forward-to" };
|
|
22
|
+
}
|
|
23
|
+
const q = event.query ?? {};
|
|
24
|
+
for (const [k, v] of Object.entries(q)) {
|
|
25
|
+
if (v === undefined || v === null) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (Array.isArray(v)) {
|
|
29
|
+
for (const item of v) {
|
|
30
|
+
url.searchParams.append(k, String(item));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
url.searchParams.append(k, String(v));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const method = (event.method ?? "POST").toUpperCase();
|
|
38
|
+
const headers = new Headers();
|
|
39
|
+
const raw = event.headers ?? {};
|
|
40
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
41
|
+
if (typeof v !== "string") {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const key = k.toLowerCase();
|
|
45
|
+
if (HOP_BY_HOP.has(key)) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
headers.set(k, v);
|
|
49
|
+
}
|
|
50
|
+
let body;
|
|
51
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
52
|
+
const b = event.body;
|
|
53
|
+
if (b !== undefined && b !== null) {
|
|
54
|
+
if (typeof b === "string") {
|
|
55
|
+
body = b;
|
|
56
|
+
}
|
|
57
|
+
else if (typeof b === "object") {
|
|
58
|
+
body = JSON.stringify(b);
|
|
59
|
+
if (!headers.has("content-type")) {
|
|
60
|
+
headers.set("content-type", "application/json; charset=utf-8");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
body = String(b);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const res = await fetch(url, { method, headers, body, redirect: "manual" });
|
|
70
|
+
return { ok: res.ok, status: res.status };
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
74
|
+
return { ok: false, status: 0, error: message };
|
|
75
|
+
}
|
|
76
|
+
}
|
package/dist/hash.d.ts
ADDED
package/dist/hash.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function parseListenArgv(argv) {
|
|
2
|
+
const options = {
|
|
3
|
+
verbose: false,
|
|
4
|
+
json: false,
|
|
5
|
+
forwardTo: null,
|
|
6
|
+
};
|
|
7
|
+
let webhookId = "";
|
|
8
|
+
for (let i = 0; i < argv.length; i++) {
|
|
9
|
+
const a = argv[i];
|
|
10
|
+
if (a === "--verbose" || a === "-v") {
|
|
11
|
+
options.verbose = true;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (a === "--json") {
|
|
15
|
+
options.json = true;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (a === "--forward-to") {
|
|
19
|
+
const u = argv[++i];
|
|
20
|
+
if (!u?.trim()) {
|
|
21
|
+
throw new Error("--forward-to requires a URL (e.g. http://127.0.0.1:3000/api/webhooks)");
|
|
22
|
+
}
|
|
23
|
+
options.forwardTo = u.trim();
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (a.startsWith("-")) {
|
|
27
|
+
throw new Error(`Unknown option: ${a}`);
|
|
28
|
+
}
|
|
29
|
+
if (!webhookId) {
|
|
30
|
+
webhookId = a.trim();
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
throw new Error(`Unexpected argument: ${a}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { webhookId, options };
|
|
37
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { EventpipeManifest } from "./config.js";
|
|
2
|
+
export type PublishResult = {
|
|
3
|
+
success?: boolean;
|
|
4
|
+
version?: number;
|
|
5
|
+
bundleHash?: string;
|
|
6
|
+
bundleSizeBytes?: number;
|
|
7
|
+
error?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function publishVersion(params: {
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
apiKey: string;
|
|
12
|
+
pipelineId: string;
|
|
13
|
+
manifest: EventpipeManifest;
|
|
14
|
+
bundles: Array<{
|
|
15
|
+
nodeId: string;
|
|
16
|
+
bundleCode: string;
|
|
17
|
+
bundleHash: string;
|
|
18
|
+
}>;
|
|
19
|
+
sourceCode?: string | null;
|
|
20
|
+
}): Promise<PublishResult>;
|
package/dist/publish.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export async function publishVersion(params) {
|
|
2
|
+
const res = await fetch(`${params.baseUrl}/api/account/pipelines/${params.pipelineId}/versions`, {
|
|
3
|
+
method: "POST",
|
|
4
|
+
headers: {
|
|
5
|
+
"content-type": "application/json",
|
|
6
|
+
"x-api-key": params.apiKey,
|
|
7
|
+
},
|
|
8
|
+
body: JSON.stringify({
|
|
9
|
+
sourceCode: params.sourceCode ?? null,
|
|
10
|
+
buildMeta: {
|
|
11
|
+
bundler: "eventpipe-cli",
|
|
12
|
+
generatedAt: new Date().toISOString(),
|
|
13
|
+
},
|
|
14
|
+
settings: params.manifest.settings,
|
|
15
|
+
codeBundles: params.bundles,
|
|
16
|
+
}),
|
|
17
|
+
});
|
|
18
|
+
const data = (await res.json());
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
return { error: data?.error ?? res.statusText };
|
|
21
|
+
}
|
|
22
|
+
return data;
|
|
23
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { collectCodeNodeIds } from "./code-node-ids.js";
|
|
2
|
+
const STUDIO_SOURCE_MAX_CHARS = 400_000;
|
|
3
|
+
export function codeNodeUsesLibrary(pipe, nodeId) {
|
|
4
|
+
if (typeof pipe !== "object" || pipe === null) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
const nodes = pipe.nodes;
|
|
8
|
+
if (!Array.isArray(nodes)) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
for (const n of nodes) {
|
|
12
|
+
if (typeof n !== "object" || n === null) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const rec = n;
|
|
16
|
+
if (rec.type === "code" && rec.id === nodeId) {
|
|
17
|
+
const cfg = rec.config;
|
|
18
|
+
if (typeof cfg !== "object" || cfg === null) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const v = cfg.libraryArtifactVersionId;
|
|
22
|
+
return typeof v === "string" && v.length > 0;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
export function applyPublishedStudioSources(pipe, params) {
|
|
28
|
+
if (typeof pipe !== "object" || pipe === null) {
|
|
29
|
+
return pipe;
|
|
30
|
+
}
|
|
31
|
+
const p = pipe;
|
|
32
|
+
const nodesRaw = p.nodes;
|
|
33
|
+
if (!Array.isArray(nodesRaw)) {
|
|
34
|
+
return pipe;
|
|
35
|
+
}
|
|
36
|
+
const ids = collectCodeNodeIds(pipe);
|
|
37
|
+
const single = ids.length === 1;
|
|
38
|
+
const newNodes = nodesRaw.map((raw) => {
|
|
39
|
+
if (typeof raw !== "object" || raw === null) {
|
|
40
|
+
return raw;
|
|
41
|
+
}
|
|
42
|
+
const n = raw;
|
|
43
|
+
if (n.type !== "code") {
|
|
44
|
+
return raw;
|
|
45
|
+
}
|
|
46
|
+
const id = n.id;
|
|
47
|
+
if (typeof id !== "string") {
|
|
48
|
+
return raw;
|
|
49
|
+
}
|
|
50
|
+
const prevCfg = typeof n.config === "object" && n.config !== null
|
|
51
|
+
? { ...n.config }
|
|
52
|
+
: {};
|
|
53
|
+
if (codeNodeUsesLibrary(pipe, id)) {
|
|
54
|
+
delete prevCfg.studioSource;
|
|
55
|
+
return { ...n, config: prevCfg };
|
|
56
|
+
}
|
|
57
|
+
const rawSrc = single ? params.flowSourceCode : (params.sourcesByNodeId[id] ?? "");
|
|
58
|
+
const trimmed = typeof rawSrc === "string" ? rawSrc.slice(0, STUDIO_SOURCE_MAX_CHARS) : "";
|
|
59
|
+
return {
|
|
60
|
+
...n,
|
|
61
|
+
config: { ...prevCfg, studioSource: trimmed },
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
return { ...p, nodes: newNodes };
|
|
65
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
|
|
3
|
+
const PORT = 4242;
|
|
4
|
+
|
|
5
|
+
const server = createServer((req, res) => {
|
|
6
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
7
|
+
|
|
8
|
+
if (url.pathname !== "/webhook") {
|
|
9
|
+
res.writeHead(404, { "content-type": "text/plain" });
|
|
10
|
+
res.end("Not found. Use POST /webhook");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const chunks = [];
|
|
15
|
+
req.on("data", (c) => chunks.push(c));
|
|
16
|
+
req.on("end", () => {
|
|
17
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
18
|
+
const ts = new Date().toISOString();
|
|
19
|
+
console.log(`\n--- ${ts} ${req.method} /webhook ---`);
|
|
20
|
+
console.log("Headers:", JSON.stringify(req.headers, null, 2));
|
|
21
|
+
console.log("Body:", raw || "(empty)");
|
|
22
|
+
|
|
23
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
24
|
+
res.end(JSON.stringify({ ok: true, receivedAt: ts }));
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
server.listen(PORT, "127.0.0.1", () => {
|
|
29
|
+
console.log(`Forward test server http://127.0.0.1:${PORT}/webhook (listening)`);
|
|
30
|
+
});
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";var __eventpipeExports=(()=>{var s=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var l=Object.getOwnPropertyNames;var d=Object.prototype.hasOwnProperty;var c=(n,e)=>{for(var r in e)s(n,r,{get:e[r],enumerable:!0})},p=(n,e,r,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of l(e))!d.call(n,t)&&t!==r&&s(n,t,{get:()=>e[t],enumerable:!(o=i(e,t))||o.enumerable});return n};var h=n=>p(s({},"__esModule",{value:!0}),n);var u={};c(u,{handler:()=>a});async function a(n,e){let r=e.env?.STRIPE_SECRET_KEY?.trim()??"";if(!r)return{ok:!1,error:"Set STRIPE_SECRET_KEY in the flow environment (Event tab). Never use process.env in code boxes."};let o=await fetch("https://api.stripe.com/v1/balance",{method:"GET",headers:{Authorization:`Bearer ${r}`,"Stripe-Version":"2024-11-20.acacia"}}),t=await o.json();return o.ok?{ok:!0,available:t.available,pending:t.pending,livemode:t.livemode,note:"Uses REST only so the bundle stays under the 200KB per-node limit; the stripe npm package is too large to bundle here."}:{ok:!1,status:o.status,stripeError:t.error??t}}return h(u);})();
|
|
2
|
+
|
|
3
|
+
;var handler = __eventpipeExports.handler;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";var __eventpipeExports=(()=>{var s=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var l=Object.getOwnPropertyNames;var d=Object.prototype.hasOwnProperty;var c=(n,e)=>{for(var r in e)s(n,r,{get:e[r],enumerable:!0})},p=(n,e,r,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of l(e))!d.call(n,t)&&t!==r&&s(n,t,{get:()=>e[t],enumerable:!(o=i(e,t))||o.enumerable});return n};var h=n=>p(s({},"__esModule",{value:!0}),n);var u={};c(u,{handler:()=>a});async function a(n,e){let r=e.env?.STRIPE_SECRET_KEY?.trim()??"";if(!r)return{ok:!1,error:"Set STRIPE_SECRET_KEY in the flow environment (Event tab). Never use process.env in code boxes."};let o=await fetch("https://api.stripe.com/v1/balance",{method:"GET",headers:{Authorization:`Bearer ${r}`,"Stripe-Version":"2024-11-20.acacia"}}),t=await o.json();return o.ok?{ok:!0,available:t.available,pending:t.pending,livemode:t.livemode,note:"Uses REST only so the bundle stays under the 200KB per-node limit; the stripe npm package is too large to bundle here."}:{ok:!1,status:o.status,stripeError:t.error??t}}return h(u);})();
|
|
2
|
+
|
|
3
|
+
;var handler = __eventpipeExports.handler;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { handler } from "../src/handler.ts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { handler } from "../src/handler.ts";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Stripe balance (REST) — Event Pipe example
|
|
2
|
+
|
|
3
|
+
This folder is a minimal **eventpipe** project: one code node that calls Stripe’s HTTP API with `context.env.STRIPE_SECRET_KEY`. Values are configured in the dashboard **Event** tab (`settings.env`), not in git.
|
|
4
|
+
|
|
5
|
+
## Why not `import "stripe"`?
|
|
6
|
+
|
|
7
|
+
The official `stripe` npm package bundles to **>200KB**, which exceeds the per-code-node limit enforced by the platform. This example uses `fetch` to `https://api.stripe.com/v1/balance` instead.
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
1. Create a pipeline in the dashboard and copy its **pipeline ID** into `eventpipe.json` (`pipelineId`).
|
|
12
|
+
2. Create an **API key** under account settings (used as `EVENTPIPE_API_KEY`).
|
|
13
|
+
3. In the flow **Event** tab, set `STRIPE_SECRET_KEY` to your `sk_test_...` or `sk_live_...` secret.
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
From the **`eventpipe-cli` repo root**:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm install
|
|
21
|
+
pnpm run build
|
|
22
|
+
cd examples/stripe-webhook
|
|
23
|
+
pnpm run build
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Publish a new version (requires env):
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
export EVENTPIPE_BASE_URL=https://your-app.example.com
|
|
30
|
+
export EVENTPIPE_API_KEY=evp_...
|
|
31
|
+
pnpm run push
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Override flow id without editing the file:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
node ../../dist/cli.js push --dir . --flow "<uuid>"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Contract
|
|
41
|
+
|
|
42
|
+
`eventpipe.json` declares `envContract` for `STRIPE_SECRET_KEY` so the studio can show hints; runtime reads **`context.env.STRIPE_SECRET_KEY`** only.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"codeNodes": { "src/handler.ts": "nodo1", "src/other.ts": "nodo2" },
|
|
3
|
+
"pipelineId": "00000000-0000-0000-0000-000000000000",
|
|
4
|
+
"nodeId": "code",
|
|
5
|
+
"entry": "src/handler.ts",
|
|
6
|
+
"settings": {
|
|
7
|
+
"timeoutMs": 3000,
|
|
8
|
+
"pipe": {
|
|
9
|
+
"schemaVersion": 3,
|
|
10
|
+
"nodes": [
|
|
11
|
+
{
|
|
12
|
+
"id": "code",
|
|
13
|
+
"type": "code",
|
|
14
|
+
"config": {}
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"edges": [],
|
|
18
|
+
"envContract": {
|
|
19
|
+
"variables": [
|
|
20
|
+
{
|
|
21
|
+
"key": "STRIPE_SECRET_KEY",
|
|
22
|
+
"description": "Stripe secret key (sk_test_... or sk_live_...). Set values in the dashboard Event tab, not in this repo."
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|