@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
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @eventpipe/cli
|
|
2
|
+
|
|
3
|
+
Build **Event Pipe** code-node bundles with [esbuild](https://esbuild.github.io/), publish with an **API key**, and use **login / create / listen** against the dashboard (Supabase session + relay WebSocket).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add -D @eventpipe/cli
|
|
9
|
+
# or: npm i -D @eventpipe/cli
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Global install (provides `eventpipe` and `eventpipe-cli`):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g @eventpipe/cli
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**macOS / Linux** — from a clone of this repo, installs globally (requires Node 20+ and `npm` on `PATH`):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bash install/macos.sh
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Windows** — PowerShell:
|
|
25
|
+
|
|
26
|
+
```powershell
|
|
27
|
+
Set-ExecutionPolicy -Scope Process Bypass; .\install\windows.ps1
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
From a git checkout:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd eventpipe-cli && pnpm install && pnpm run build
|
|
34
|
+
# binaries: ./dist/cli.js
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Project layout
|
|
38
|
+
|
|
39
|
+
| File | Purpose |
|
|
40
|
+
|------|---------|
|
|
41
|
+
| `eventpipe.json` | `pipelineId`, optional `nodeId` / `entry`, and `settings` (must include `pipe` v3) |
|
|
42
|
+
| `src/handler.ts` | Default entry — `export async function handler(event, context)` |
|
|
43
|
+
|
|
44
|
+
Runtime uses **`context.env`** for secrets (configure values in the app **Event** tab), not `process.env`.
|
|
45
|
+
|
|
46
|
+
## Commands
|
|
47
|
+
|
|
48
|
+
### Auth & endpoints
|
|
49
|
+
|
|
50
|
+
- **`login`** — Opens the browser to complete Supabase login; saves `~/.eventpipe/credentials.json`. Uses `https://eventpipe.app` unless `EVENTPIPE_BASE_URL` is set.
|
|
51
|
+
- **`create [--name <slug>]`** — `POST /api/account/endpoints` (session auth). `--name` sets the URL slug (`/api/webhook/your-slug`) if it’s free; otherwise a random id is used. With no `--name`, the URL and display label are random.
|
|
52
|
+
- **`listen <webhookId> [options]`** — Connects to the relay; prints one line per webhook. **`--verbose` / `-v`** prints the full event JSON (method, headers, query, body). **`--json`** prints one JSON object per line (stdout) for scripts. **`--forward-to http://127.0.0.1:PORT/path`** replays each request to your local server (forward status on stderr).
|
|
53
|
+
|
|
54
|
+
### Bundles
|
|
55
|
+
|
|
56
|
+
- **`build`** — Writes `.eventpipe/*.bundle.js` and prints size + sha256 (must be ≤ 200KB).
|
|
57
|
+
- **`push`** — Runs `build`, then `POST /api/account/pipelines/:pipelineId/versions` with `codeBundles`.
|
|
58
|
+
|
|
59
|
+
### Environment
|
|
60
|
+
|
|
61
|
+
| Variable | Used by | Description |
|
|
62
|
+
|----------|---------|-------------|
|
|
63
|
+
| `EVENTPIPE_BASE_URL` | login, push | Origin of the Next app (default `https://eventpipe.app`; no trailing slash) |
|
|
64
|
+
| `EVENTPIPE_API_KEY` | push | Plaintext key from account API keys (`evp_...`) |
|
|
65
|
+
|
|
66
|
+
Optional: `--pipeline <uuid>` (or `--flow`) overrides `pipelineId` in `eventpipe.json` for `push`.
|
|
67
|
+
|
|
68
|
+
### Server requirements for `listen`
|
|
69
|
+
|
|
70
|
+
The app needs a deployed **eventpipe-relay** (or compatible) service plus env vars documented in the app `.env.example`: `EVENTPIPE_RELAY_URL`, `EVENTPIPE_RELAY_WS_URL`, `EVENTPIPE_RELAY_INGEST_SECRET`, `EVENTPIPE_LISTEN_JWT_SECRET` (shared with the relay).
|
|
71
|
+
|
|
72
|
+
## Example
|
|
73
|
+
|
|
74
|
+
See `examples/stripe-webhook` (Stripe Balance via REST to stay under the bundle size cap).
|
|
75
|
+
|
|
76
|
+
## Limits
|
|
77
|
+
|
|
78
|
+
- One **code node** per project in this CLI version (multi-node flows: publish from the dashboard or extend the CLI).
|
|
79
|
+
- Per-node bundle max **200KB** UTF-8 (same as the server).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { saveCredentials } from "./credentials.js";
|
|
2
|
+
export async function fetchWithSession(url, init, cred) {
|
|
3
|
+
let c = cred;
|
|
4
|
+
const headers = new Headers(init.headers);
|
|
5
|
+
headers.set("authorization", `Bearer ${c.accessToken}`);
|
|
6
|
+
let res = await fetch(url, { ...init, headers });
|
|
7
|
+
if (res.status === 401 && c.refreshToken) {
|
|
8
|
+
const r = await fetch(`${c.baseUrl}/api/cli/refresh`, {
|
|
9
|
+
method: "POST",
|
|
10
|
+
headers: { "content-type": "application/json" },
|
|
11
|
+
body: JSON.stringify({ refresh_token: c.refreshToken }),
|
|
12
|
+
});
|
|
13
|
+
if (r.ok) {
|
|
14
|
+
const t = (await r.json());
|
|
15
|
+
if (t.access_token && t.refresh_token) {
|
|
16
|
+
c = {
|
|
17
|
+
...c,
|
|
18
|
+
accessToken: t.access_token,
|
|
19
|
+
refreshToken: t.refresh_token,
|
|
20
|
+
};
|
|
21
|
+
await saveCredentials(c);
|
|
22
|
+
const h2 = new Headers(init.headers);
|
|
23
|
+
h2.set("authorization", `Bearer ${c.accessToken}`);
|
|
24
|
+
res = await fetch(url, { ...init, headers: h2 });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { response: res, credentials: c };
|
|
29
|
+
}
|
package/dist/base-url.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const DEFAULT_EVENTPIPE_BASE_URL = "https://eventpipe.app";
|
|
2
|
+
export function resolveEventpipeBaseUrl() {
|
|
3
|
+
const fromEnv = process.env.EVENTPIPE_BASE_URL?.replace(/\/$/, "").trim();
|
|
4
|
+
if (fromEnv) {
|
|
5
|
+
return fromEnv;
|
|
6
|
+
}
|
|
7
|
+
return DEFAULT_EVENTPIPE_BASE_URL;
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type BuildResult = {
|
|
2
|
+
bundleCode: string;
|
|
3
|
+
bundleHash: string;
|
|
4
|
+
bundleSizeBytes: number;
|
|
5
|
+
outFile: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function buildBundle(params: {
|
|
8
|
+
projectDir: string;
|
|
9
|
+
entryRelative: string;
|
|
10
|
+
nodeId: string;
|
|
11
|
+
pipe: unknown;
|
|
12
|
+
}): Promise<BuildResult>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, relative, resolve } from "node:path";
|
|
3
|
+
import * as esbuild from "esbuild";
|
|
4
|
+
import { collectCodeNodeIds } from "./code-node-ids.js";
|
|
5
|
+
import { BUNDLE_MAX_BYTES, GLOBAL_NAME } from "./constants.js";
|
|
6
|
+
import { byteLenUtf8, sha256Utf8 } from "./hash.js";
|
|
7
|
+
export async function buildBundle(params) {
|
|
8
|
+
const codeIds = collectCodeNodeIds(params.pipe);
|
|
9
|
+
if (codeIds.length === 0) {
|
|
10
|
+
throw new Error("settings.pipe must contain at least one code node");
|
|
11
|
+
}
|
|
12
|
+
if (!codeIds.includes(params.nodeId)) {
|
|
13
|
+
throw new Error(`nodeId "${params.nodeId}" is not a code node id in settings.pipe`);
|
|
14
|
+
}
|
|
15
|
+
const userEntry = resolve(params.projectDir, params.entryRelative);
|
|
16
|
+
const epDir = resolve(params.projectDir, ".eventpipe");
|
|
17
|
+
await mkdir(epDir, { recursive: true });
|
|
18
|
+
const shimPath = resolve(epDir, `${params.nodeId}.reexport.ts`);
|
|
19
|
+
const relToShim = relative(dirname(shimPath), userEntry).replace(/\\/g, "/");
|
|
20
|
+
const shim = `export { handler } from "${relToShim}";\n`;
|
|
21
|
+
await writeFile(shimPath, shim, "utf8");
|
|
22
|
+
const outFile = resolve(epDir, `${params.nodeId}.bundle.js`);
|
|
23
|
+
await esbuild.build({
|
|
24
|
+
absWorkingDir: params.projectDir,
|
|
25
|
+
entryPoints: [shimPath],
|
|
26
|
+
bundle: true,
|
|
27
|
+
platform: "node",
|
|
28
|
+
format: "iife",
|
|
29
|
+
globalName: GLOBAL_NAME,
|
|
30
|
+
outfile: outFile,
|
|
31
|
+
minify: true,
|
|
32
|
+
target: "es2022",
|
|
33
|
+
logLevel: "warning",
|
|
34
|
+
});
|
|
35
|
+
const raw = await readFile(outFile, "utf8");
|
|
36
|
+
const footer = `\n;var handler = ${GLOBAL_NAME}.handler;\n`;
|
|
37
|
+
const bundleCode = (raw + footer).trim();
|
|
38
|
+
await writeFile(outFile, bundleCode, "utf8");
|
|
39
|
+
if (!bundleCode.includes("handler")) {
|
|
40
|
+
throw new Error("Bundle validation failed: output must reference handler");
|
|
41
|
+
}
|
|
42
|
+
const bundleSizeBytes = byteLenUtf8(bundleCode);
|
|
43
|
+
if (bundleSizeBytes > BUNDLE_MAX_BYTES) {
|
|
44
|
+
throw new Error(`Bundle is ${bundleSizeBytes} bytes (max ${BUNDLE_MAX_BYTES}). Use smaller dependencies or fetch-based APIs.`);
|
|
45
|
+
}
|
|
46
|
+
const bundleHash = sha256Utf8(bundleCode);
|
|
47
|
+
return { bundleCode, bundleHash, bundleSizeBytes, outFile };
|
|
48
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const NPM_LATEST_URL = "https://registry.npmjs.org/@eventpipe%2fcli/latest";
|
|
5
|
+
export async function readInstalledCliVersion() {
|
|
6
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const pkgPath = join(dir, "..", "package.json");
|
|
8
|
+
const raw = await readFile(pkgPath, "utf8");
|
|
9
|
+
const pkg = JSON.parse(raw);
|
|
10
|
+
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
11
|
+
}
|
|
12
|
+
export async function fetchLatestPublishedVersion() {
|
|
13
|
+
try {
|
|
14
|
+
const ac = new AbortController();
|
|
15
|
+
const t = setTimeout(() => ac.abort(), 5_000);
|
|
16
|
+
const res = await fetch(NPM_LATEST_URL, {
|
|
17
|
+
signal: ac.signal,
|
|
18
|
+
headers: { accept: "application/json" },
|
|
19
|
+
});
|
|
20
|
+
clearTimeout(t);
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const data = (await res.json());
|
|
25
|
+
return typeof data.version === "string" ? data.version : null;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function compareSemverLike(a, b) {
|
|
32
|
+
const coreA = a.replace(/^v/, "").split(/[-+]/)[0] ?? "0";
|
|
33
|
+
const coreB = b.replace(/^v/, "").split(/[-+]/)[0] ?? "0";
|
|
34
|
+
const partsA = coreA.split(".").map((x) => Number.parseInt(x, 10) || 0);
|
|
35
|
+
const partsB = coreB.split(".").map((x) => Number.parseInt(x, 10) || 0);
|
|
36
|
+
const n = Math.max(partsA.length, partsB.length);
|
|
37
|
+
for (let i = 0; i < n; i++) {
|
|
38
|
+
const da = partsA[i] ?? 0;
|
|
39
|
+
const db = partsB[i] ?? 0;
|
|
40
|
+
if (da !== db) {
|
|
41
|
+
return da - db;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
export function isPublishedVersionNewer(installed, published) {
|
|
47
|
+
return compareSemverLike(published, installed) > 0;
|
|
48
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { buildBundle } from "./build-bundle.js";
|
|
5
|
+
import { collectCodeNodeIds } from "./code-node-ids.js";
|
|
6
|
+
import { loadManifest } from "./config.js";
|
|
7
|
+
import { publishVersion } from "./publish.js";
|
|
8
|
+
import { applyPublishedStudioSources, codeNodeUsesLibrary } from "./studio-sources.js";
|
|
9
|
+
import { cmdLogin } from "./cmd-login.js";
|
|
10
|
+
import { cmdCreate } from "./cmd-create.js";
|
|
11
|
+
import { resolveEventpipeBaseUrl } from "./base-url.js";
|
|
12
|
+
import { fetchLatestPublishedVersion, isPublishedVersionNewer, readInstalledCliVersion, } from "./cli-version.js";
|
|
13
|
+
import { cmdUpdate } from "./cmd-update.js";
|
|
14
|
+
import { cmdListen } from "./cmd-listen.js";
|
|
15
|
+
import { parseListenArgv } from "./listen-args.js";
|
|
16
|
+
function usage() {
|
|
17
|
+
console.log(`eventpipe — Event Pipe CLI
|
|
18
|
+
|
|
19
|
+
Environment:
|
|
20
|
+
EVENTPIPE_BASE_URL App origin (default: https://eventpipe.app); override for self-hosted
|
|
21
|
+
EVENTPIPE_API_KEY Account API key (x-api-key) for push when not using session
|
|
22
|
+
EVENTPIPE_SKIP_UPDATE_CHECK Set to 1 to disable the npm version hint on stderr
|
|
23
|
+
|
|
24
|
+
Commands:
|
|
25
|
+
login Browser login (stores ~/.eventpipe/credentials.json)
|
|
26
|
+
create [--name <s>] Create a webhook endpoint (requires login)
|
|
27
|
+
listen <webhookId> [--verbose|-v] [--json] [--forward-to <url>]
|
|
28
|
+
Stream webhooks; --verbose prints full JSON event; --json one NDJSON line per event;
|
|
29
|
+
--forward-to replays the request to your local server (status on stderr)
|
|
30
|
+
build [--dir <path>] Bundle TS into .eventpipe/
|
|
31
|
+
push [--dir <path>] build + POST /api/account/pipelines/:id/versions (needs EVENTPIPE_API_KEY)
|
|
32
|
+
update npm install -g @eventpipe/cli@latest
|
|
33
|
+
help
|
|
34
|
+
|
|
35
|
+
eventpipe.json must define pipelineId and settings.pipe (v3) for build/push.
|
|
36
|
+
`);
|
|
37
|
+
}
|
|
38
|
+
async function maybeSuggestUpdate() {
|
|
39
|
+
try {
|
|
40
|
+
if (process.env.EVENTPIPE_SKIP_UPDATE_CHECK === "1") {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const v = await readInstalledCliVersion();
|
|
44
|
+
const latest = await fetchLatestPublishedVersion();
|
|
45
|
+
if (!latest || !isPublishedVersionNewer(v, latest)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.error(`\nA newer @eventpipe/cli is available (latest: ${latest}). Run: eventpipe update\n`);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
/* ignore */
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function parseDir(argv) {
|
|
55
|
+
let dir = process.cwd();
|
|
56
|
+
for (let i = 0; i < argv.length; i++) {
|
|
57
|
+
if (argv[i] === "--dir" && argv[i + 1]) {
|
|
58
|
+
dir = resolve(argv[++i]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return dir;
|
|
62
|
+
}
|
|
63
|
+
function parsePipelineOverride(argv) {
|
|
64
|
+
for (let i = 0; i < argv.length; i++) {
|
|
65
|
+
if ((argv[i] === "--pipeline" || argv[i] === "--flow") && argv[i + 1]) {
|
|
66
|
+
return argv[++i].trim();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
function resolveTargets(manifest, ids) {
|
|
72
|
+
if (manifest.codeNodes) {
|
|
73
|
+
return manifest.codeNodes;
|
|
74
|
+
}
|
|
75
|
+
const entry = manifest.entry && manifest.entry.length > 0 ? manifest.entry : "src/handler.ts";
|
|
76
|
+
const nodeId = manifest.nodeId?.trim();
|
|
77
|
+
if (nodeId) {
|
|
78
|
+
return { [entry]: nodeId };
|
|
79
|
+
}
|
|
80
|
+
if (ids.length === 0) {
|
|
81
|
+
throw new Error("No code node ids found in settings.pipe");
|
|
82
|
+
}
|
|
83
|
+
if (ids.length === 1) {
|
|
84
|
+
return { [entry]: ids[0] };
|
|
85
|
+
}
|
|
86
|
+
throw new Error("Multiple code nodes detected in your flow. You must specify a 'codeNodes' map in eventpipe.json (e.g. { \"src/node.ts\": \"uuid\" }).");
|
|
87
|
+
}
|
|
88
|
+
async function cmdBuild(projectDir) {
|
|
89
|
+
const manifest = await loadManifest(projectDir);
|
|
90
|
+
const pipe = manifest.settings.pipe;
|
|
91
|
+
const ids = collectCodeNodeIds(pipe);
|
|
92
|
+
const targets = resolveTargets(manifest, ids);
|
|
93
|
+
for (const [entryRelative, nodeId] of Object.entries(targets)) {
|
|
94
|
+
const r = await buildBundle({
|
|
95
|
+
projectDir,
|
|
96
|
+
entryRelative,
|
|
97
|
+
nodeId,
|
|
98
|
+
pipe,
|
|
99
|
+
});
|
|
100
|
+
console.log(`OK bundle (${entryRelative} -> ${nodeId}) ${r.bundleSizeBytes} bytes sha256:${r.bundleHash.slice(0, 12)}…`);
|
|
101
|
+
console.log(`Written ${r.outFile}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function cmdPush(projectDir, pipelineOverride) {
|
|
105
|
+
const base = resolveEventpipeBaseUrl();
|
|
106
|
+
const key = process.env.EVENTPIPE_API_KEY?.trim();
|
|
107
|
+
if (!key) {
|
|
108
|
+
throw new Error("EVENTPIPE_API_KEY is required for push");
|
|
109
|
+
}
|
|
110
|
+
const manifest = await loadManifest(projectDir);
|
|
111
|
+
const pipelineId = pipelineOverride ?? manifest.pipelineId;
|
|
112
|
+
const pipe = manifest.settings.pipe;
|
|
113
|
+
const ids = collectCodeNodeIds(pipe);
|
|
114
|
+
const targets = resolveTargets(manifest, ids);
|
|
115
|
+
const bundles = [];
|
|
116
|
+
const sourcesByNodeId = {};
|
|
117
|
+
let totalSize = 0;
|
|
118
|
+
for (const [entryRelative, nodeId] of Object.entries(targets)) {
|
|
119
|
+
const sourcePath = resolve(projectDir, entryRelative);
|
|
120
|
+
sourcesByNodeId[nodeId] = await readFile(sourcePath, "utf8");
|
|
121
|
+
const built = await buildBundle({
|
|
122
|
+
projectDir,
|
|
123
|
+
entryRelative,
|
|
124
|
+
nodeId,
|
|
125
|
+
pipe,
|
|
126
|
+
});
|
|
127
|
+
bundles.push({
|
|
128
|
+
nodeId,
|
|
129
|
+
bundleCode: built.bundleCode,
|
|
130
|
+
bundleHash: built.bundleHash,
|
|
131
|
+
});
|
|
132
|
+
totalSize += built.bundleSizeBytes;
|
|
133
|
+
}
|
|
134
|
+
const flowSourceCode = ids.length === 1 && ids[0] ? sourcesByNodeId[ids[0]] ?? "" : "";
|
|
135
|
+
const mergedPipe = applyPublishedStudioSources(pipe, {
|
|
136
|
+
sourcesByNodeId,
|
|
137
|
+
flowSourceCode,
|
|
138
|
+
});
|
|
139
|
+
const manifestForPublish = {
|
|
140
|
+
...manifest,
|
|
141
|
+
settings: { ...manifest.settings, pipe: mergedPipe },
|
|
142
|
+
};
|
|
143
|
+
const sourceCode = ids.length > 0 && ids[0] && !codeNodeUsesLibrary(mergedPipe, ids[0])
|
|
144
|
+
? sourcesByNodeId[ids[0]] ?? null
|
|
145
|
+
: null;
|
|
146
|
+
const result = await publishVersion({
|
|
147
|
+
baseUrl: base,
|
|
148
|
+
apiKey: key,
|
|
149
|
+
pipelineId,
|
|
150
|
+
manifest: manifestForPublish,
|
|
151
|
+
bundles,
|
|
152
|
+
sourceCode,
|
|
153
|
+
});
|
|
154
|
+
if (result.error) {
|
|
155
|
+
throw new Error(result.error);
|
|
156
|
+
}
|
|
157
|
+
console.log(`Published pipeline ${pipelineId} version ${result.version} (${result.bundleSizeBytes ?? totalSize} bytes in ${bundles.length} bundles)`);
|
|
158
|
+
}
|
|
159
|
+
async function main() {
|
|
160
|
+
const argv = process.argv.slice(2);
|
|
161
|
+
const cmd = argv[0];
|
|
162
|
+
if (!cmd || cmd === "-h" || cmd === "--help" || cmd === "help") {
|
|
163
|
+
usage();
|
|
164
|
+
process.exit(cmd && cmd !== "help" ? 0 : 1);
|
|
165
|
+
}
|
|
166
|
+
if (cmd === "-v" || cmd === "--version") {
|
|
167
|
+
const v = await readInstalledCliVersion();
|
|
168
|
+
console.log(v);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (cmd === "login") {
|
|
172
|
+
await cmdLogin();
|
|
173
|
+
void maybeSuggestUpdate();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (cmd === "create") {
|
|
177
|
+
await cmdCreate(argv.slice(1));
|
|
178
|
+
void maybeSuggestUpdate();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (cmd === "update") {
|
|
182
|
+
await cmdUpdate();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (cmd === "listen") {
|
|
186
|
+
let parsed;
|
|
187
|
+
try {
|
|
188
|
+
parsed = parseListenArgv(argv.slice(1));
|
|
189
|
+
}
|
|
190
|
+
catch (e) {
|
|
191
|
+
console.error(e instanceof Error ? e.message : e);
|
|
192
|
+
console.error("Usage: eventpipe listen <webhookId> [--verbose|-v] [--json] [--forward-to <url>]");
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
if (!parsed.webhookId) {
|
|
196
|
+
console.error("Usage: eventpipe listen <webhookId> [--verbose|-v] [--json] [--forward-to <url>]");
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
void maybeSuggestUpdate();
|
|
200
|
+
await cmdListen(parsed.webhookId, parsed.options);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const projectDir = parseDir(argv);
|
|
204
|
+
const pipelineOverride = parsePipelineOverride(argv);
|
|
205
|
+
if (cmd === "build") {
|
|
206
|
+
await cmdBuild(projectDir);
|
|
207
|
+
void maybeSuggestUpdate();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (cmd === "push") {
|
|
211
|
+
await cmdPush(projectDir, pipelineOverride);
|
|
212
|
+
void maybeSuggestUpdate();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
usage();
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
main().catch((e) => {
|
|
219
|
+
console.error(e instanceof Error ? e.message : e);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cmdCreate(argv: string[]): Promise<void>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { loadCredentials } from "./credentials.js";
|
|
2
|
+
import { fetchWithSession } from "./auth-fetch.js";
|
|
3
|
+
function parseName(argv) {
|
|
4
|
+
for (let i = 0; i < argv.length; i++) {
|
|
5
|
+
if (argv[i] === "--name" && argv[i + 1]) {
|
|
6
|
+
return argv[++i].trim();
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return "";
|
|
10
|
+
}
|
|
11
|
+
export async function cmdCreate(argv) {
|
|
12
|
+
const cred = await loadCredentials();
|
|
13
|
+
if (!cred) {
|
|
14
|
+
throw new Error("Run eventpipe login first (or set credentials via eventpipe login)");
|
|
15
|
+
}
|
|
16
|
+
const name = parseName(argv);
|
|
17
|
+
const { response } = await fetchWithSession(`${cred.baseUrl}/api/account/endpoints`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "content-type": "application/json" },
|
|
20
|
+
body: JSON.stringify({ name: name || undefined }),
|
|
21
|
+
}, cred);
|
|
22
|
+
const data = (await response.json());
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(data.error ?? response.statusText);
|
|
25
|
+
}
|
|
26
|
+
if (!data.webhookId || !data.webhookUrl) {
|
|
27
|
+
throw new Error("Unexpected response from API");
|
|
28
|
+
}
|
|
29
|
+
if (data.slugUnavailable && data.requestedSlug) {
|
|
30
|
+
console.log(`⚠ Slug "${data.requestedSlug}" is already taken; created with a random id instead.`);
|
|
31
|
+
}
|
|
32
|
+
const labelNote = data.label ? ` (${data.label})` : "";
|
|
33
|
+
console.log(`✓ Endpoint created: ${data.webhookUrl}${labelNote}`);
|
|
34
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import { forwardWebhookToLocal } from "./forward-local.js";
|
|
3
|
+
import { loadCredentials } from "./credentials.js";
|
|
4
|
+
import { fetchWithSession } from "./auth-fetch.js";
|
|
5
|
+
function formatKb(bytes) {
|
|
6
|
+
return (bytes / 1024).toFixed(1);
|
|
7
|
+
}
|
|
8
|
+
export async function cmdListen(webhookId, options) {
|
|
9
|
+
const wid = webhookId.trim();
|
|
10
|
+
if (!wid) {
|
|
11
|
+
throw new Error("webhook id is required");
|
|
12
|
+
}
|
|
13
|
+
const cred = await loadCredentials();
|
|
14
|
+
if (!cred) {
|
|
15
|
+
throw new Error("Run eventpipe login first");
|
|
16
|
+
}
|
|
17
|
+
const { response } = await fetchWithSession(`${cred.baseUrl}/api/cli/listen-token`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "content-type": "application/json" },
|
|
20
|
+
body: JSON.stringify({ webhookId: wid }),
|
|
21
|
+
}, cred);
|
|
22
|
+
const data = (await response.json());
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(data.error ?? response.statusText);
|
|
25
|
+
}
|
|
26
|
+
if (!data.token || !data.relayWsUrl) {
|
|
27
|
+
throw new Error(data.error ?? "listen-token misconfigured on server (EVENTPIPE_RELAY_WS_URL / EVENTPIPE_LISTEN_JWT_SECRET)");
|
|
28
|
+
}
|
|
29
|
+
const wsUrl = data.relayWsUrl;
|
|
30
|
+
console.log(`🔌 Conectado a ${wid}`);
|
|
31
|
+
if (options.forwardTo) {
|
|
32
|
+
console.error(`↪ forwarding to ${options.forwardTo}`);
|
|
33
|
+
}
|
|
34
|
+
const ws = new WebSocket(wsUrl);
|
|
35
|
+
await new Promise((resolve, reject) => {
|
|
36
|
+
const shutdown = () => {
|
|
37
|
+
try {
|
|
38
|
+
ws.close();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
/* ignore */
|
|
42
|
+
}
|
|
43
|
+
resolve();
|
|
44
|
+
};
|
|
45
|
+
process.once("SIGINT", shutdown);
|
|
46
|
+
process.once("SIGTERM", shutdown);
|
|
47
|
+
ws.on("open", () => {
|
|
48
|
+
ws.send(JSON.stringify({ type: "auth", token: data.token }));
|
|
49
|
+
});
|
|
50
|
+
ws.on("message", (buf) => {
|
|
51
|
+
void (async () => {
|
|
52
|
+
try {
|
|
53
|
+
const msg = JSON.parse(buf.toString());
|
|
54
|
+
if (msg.type === "ready") {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (msg.type !== "webhook") {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const summary = msg.summary ?? "webhook";
|
|
61
|
+
const bytes = typeof msg.bytes === "number" ? msg.bytes : 0;
|
|
62
|
+
const event = (msg.event ?? {});
|
|
63
|
+
if (options.json) {
|
|
64
|
+
console.log(JSON.stringify({ summary, bytes, event }));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.log(`📩 ${summary} (${formatKb(bytes)}KB)`);
|
|
68
|
+
if (options.verbose) {
|
|
69
|
+
console.log(JSON.stringify(event, null, 2));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (options.forwardTo) {
|
|
73
|
+
const r = await forwardWebhookToLocal(options.forwardTo, event);
|
|
74
|
+
if (r.error) {
|
|
75
|
+
console.error(`↪ forward failed: ${r.error}`);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.error(`↪ forwarded (${r.status})`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
/* ignore */
|
|
84
|
+
}
|
|
85
|
+
})();
|
|
86
|
+
});
|
|
87
|
+
ws.on("close", () => resolve());
|
|
88
|
+
ws.on("error", (e) => reject(e));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cmdLogin(): Promise<void>;
|