@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.
Files changed (51) hide show
  1. package/README.md +79 -0
  2. package/dist/auth-fetch.d.ts +5 -0
  3. package/dist/auth-fetch.js +29 -0
  4. package/dist/base-url.d.ts +2 -0
  5. package/dist/base-url.js +8 -0
  6. package/dist/build-bundle.d.ts +12 -0
  7. package/dist/build-bundle.js +48 -0
  8. package/dist/cli-version.d.ts +3 -0
  9. package/dist/cli-version.js +48 -0
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +221 -0
  12. package/dist/cmd-create.d.ts +1 -0
  13. package/dist/cmd-create.js +34 -0
  14. package/dist/cmd-listen.d.ts +2 -0
  15. package/dist/cmd-listen.js +90 -0
  16. package/dist/cmd-login.d.ts +1 -0
  17. package/dist/cmd-login.js +91 -0
  18. package/dist/cmd-update.d.ts +1 -0
  19. package/dist/cmd-update.js +20 -0
  20. package/dist/code-node-ids.d.ts +1 -0
  21. package/dist/code-node-ids.js +20 -0
  22. package/dist/config.d.ts +8 -0
  23. package/dist/config.js +42 -0
  24. package/dist/constants.d.ts +2 -0
  25. package/dist/constants.js +2 -0
  26. package/dist/credentials.d.ts +7 -0
  27. package/dist/credentials.js +42 -0
  28. package/dist/forward-local.d.ts +14 -0
  29. package/dist/forward-local.js +76 -0
  30. package/dist/hash.d.ts +2 -0
  31. package/dist/hash.js +7 -0
  32. package/dist/listen-args.d.ts +9 -0
  33. package/dist/listen-args.js +37 -0
  34. package/dist/publish.d.ts +20 -0
  35. package/dist/publish.js +23 -0
  36. package/dist/studio-sources.d.ts +5 -0
  37. package/dist/studio-sources.js +65 -0
  38. package/examples/forward-test-server.mjs +30 -0
  39. package/examples/stripe-webhook/.eventpipe/bundle.js +3 -0
  40. package/examples/stripe-webhook/.eventpipe/code.bundle.js +3 -0
  41. package/examples/stripe-webhook/.eventpipe/code.reexport.ts +1 -0
  42. package/examples/stripe-webhook/.eventpipe/entry.reexport.ts +1 -0
  43. package/examples/stripe-webhook/README.md +42 -0
  44. package/examples/stripe-webhook/eventpipe.json +28 -0
  45. package/examples/stripe-webhook/eventpipe.json.bak +27 -0
  46. package/examples/stripe-webhook/package.json +8 -0
  47. package/examples/stripe-webhook/src/handler.ts +44 -0
  48. package/examples/stripe-webhook/src/other.ts +1 -0
  49. package/install/macos.sh +32 -0
  50. package/install/windows.ps1 +35 -0
  51. 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
+ }
@@ -0,0 +1,8 @@
1
+ export type EventpipeManifest = {
2
+ pipelineId: string;
3
+ nodeId?: string;
4
+ entry?: string;
5
+ codeNodes?: Record<string, string>;
6
+ settings: Record<string, unknown>;
7
+ };
8
+ export declare function loadManifest(projectDir: string): Promise<EventpipeManifest>;
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,2 @@
1
+ export declare const BUNDLE_MAX_BYTES: number;
2
+ export declare const GLOBAL_NAME = "__eventpipeExports";
@@ -0,0 +1,2 @@
1
+ export const BUNDLE_MAX_BYTES = 200 * 1024;
2
+ export const GLOBAL_NAME = "__eventpipeExports";
@@ -0,0 +1,7 @@
1
+ export type StoredCredentials = {
2
+ baseUrl: string;
3
+ accessToken: string;
4
+ refreshToken: string;
5
+ };
6
+ export declare function loadCredentials(): Promise<StoredCredentials | null>;
7
+ export declare function saveCredentials(c: StoredCredentials): Promise<void>;
@@ -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
@@ -0,0 +1,2 @@
1
+ export declare function sha256Utf8(input: string): string;
2
+ export declare function byteLenUtf8(s: string): number;
package/dist/hash.js ADDED
@@ -0,0 +1,7 @@
1
+ import { createHash } from "node:crypto";
2
+ export function sha256Utf8(input) {
3
+ return createHash("sha256").update(input, "utf8").digest("hex");
4
+ }
5
+ export function byteLenUtf8(s) {
6
+ return new TextEncoder().encode(s).byteLength;
7
+ }
@@ -0,0 +1,9 @@
1
+ export type ListenOptions = {
2
+ verbose: boolean;
3
+ json: boolean;
4
+ forwardTo: string | null;
5
+ };
6
+ export declare function parseListenArgv(argv: string[]): {
7
+ webhookId: string;
8
+ options: ListenOptions;
9
+ };
@@ -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>;
@@ -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,5 @@
1
+ export declare function codeNodeUsesLibrary(pipe: unknown, nodeId: string): boolean;
2
+ export declare function applyPublishedStudioSources(pipe: unknown, params: {
3
+ sourcesByNodeId: Record<string, string>;
4
+ flowSourceCode: string;
5
+ }): unknown;
@@ -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
+ }