@secondlayer/cli 19.4.3 → 20.0.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 +9 -11
- package/dist/cli.js +153 -600
- package/dist/cli.js.map +9 -10
- package/package.json +9 -9
- package/templates/webhooks/cloudflare/.env.example +0 -1
- package/templates/webhooks/cloudflare/README.md +0 -44
- package/templates/webhooks/cloudflare/package.json +0 -15
- package/templates/webhooks/cloudflare/src/index.ts +0 -34
- package/templates/webhooks/cloudflare/tsconfig.json +0 -10
- package/templates/webhooks/cloudflare/wrangler.toml +0 -8
- package/templates/webhooks/inngest/.env.example +0 -2
- package/templates/webhooks/inngest/README.md +0 -29
- package/templates/webhooks/inngest/package.json +0 -18
- package/templates/webhooks/inngest/src/inngest.ts +0 -19
- package/templates/webhooks/inngest/src/server.ts +0 -14
- package/templates/webhooks/inngest/tsconfig.json +0 -11
- package/templates/webhooks/node/.env.example +0 -2
- package/templates/webhooks/node/README.md +0 -36
- package/templates/webhooks/node/package.json +0 -18
- package/templates/webhooks/node/src/index.ts +0 -53
- package/templates/webhooks/node/tsconfig.json +0 -11
- package/templates/webhooks/trigger/.env.example +0 -2
- package/templates/webhooks/trigger/README.md +0 -38
- package/templates/webhooks/trigger/package.json +0 -17
- package/templates/webhooks/trigger/src/tasks/subgraph-event.ts +0 -15
- package/templates/webhooks/trigger/trigger.config.ts +0 -6
- package/templates/webhooks/trigger/tsconfig.json +0 -10
package/dist/cli.js
CHANGED
|
@@ -29360,11 +29360,10 @@ function parseFilterArg(input) {
|
|
|
29360
29360
|
return {
|
|
29361
29361
|
field,
|
|
29362
29362
|
operator,
|
|
29363
|
-
value
|
|
29364
|
-
explicitOperator: true
|
|
29363
|
+
value
|
|
29365
29364
|
};
|
|
29366
29365
|
}
|
|
29367
|
-
return { field: key, operator: "eq", value
|
|
29366
|
+
return { field: key, operator: "eq", value };
|
|
29368
29367
|
}
|
|
29369
29368
|
function parseFilterArgs(args) {
|
|
29370
29369
|
return (args ?? []).map(parseFilterArg);
|
|
@@ -29377,25 +29376,6 @@ function parseQueryFilters(args) {
|
|
|
29377
29376
|
}
|
|
29378
29377
|
return Object.keys(filters).length > 0 ? filters : undefined;
|
|
29379
29378
|
}
|
|
29380
|
-
function parseWebhookFilter(args) {
|
|
29381
|
-
const filters = {};
|
|
29382
|
-
const seenFields = new Set;
|
|
29383
|
-
for (const filter of parseFilterArgs(args)) {
|
|
29384
|
-
if (filter.operator === "like") {
|
|
29385
|
-
throw new Error(`Webhook filters do not support ".like". Supported operators: ${FILTER_OPERATORS.join(", ")}.`);
|
|
29386
|
-
}
|
|
29387
|
-
if (seenFields.has(filter.field)) {
|
|
29388
|
-
throw new Error(`Webhook filters support one condition per field; got multiple filters for "${filter.field}".`);
|
|
29389
|
-
}
|
|
29390
|
-
seenFields.add(filter.field);
|
|
29391
|
-
if (!filter.explicitOperator || filter.operator === "eq") {
|
|
29392
|
-
filters[filter.field] = filter.value;
|
|
29393
|
-
} else {
|
|
29394
|
-
filters[filter.field] = { [filter.operator]: filter.value };
|
|
29395
|
-
}
|
|
29396
|
-
}
|
|
29397
|
-
return Object.keys(filters).length > 0 ? filters : undefined;
|
|
29398
|
-
}
|
|
29399
29379
|
var FILTER_OPERATORS, QUERY_ONLY_OPERATORS, ALL_OPERATORS, ALL_OPERATOR_LABELS;
|
|
29400
29380
|
var init_filter_params = __esm(() => {
|
|
29401
29381
|
FILTER_OPERATORS = ["eq", "neq", "gt", "gte", "lt", "lte"];
|
|
@@ -29443,7 +29423,7 @@ var init_git_status = () => {};
|
|
|
29443
29423
|
|
|
29444
29424
|
// ../../node_modules/typescript/lib/typescript.js
|
|
29445
29425
|
var require_typescript = __commonJS(function(exports, module) {
|
|
29446
|
-
var __dirname = "/Users/ryan/projects/secondlayer
|
|
29426
|
+
var __dirname = "/Users/ryan/projects/secondlayer/node_modules/typescript/lib", __filename = "/Users/ryan/projects/secondlayer/node_modules/typescript/lib/typescript.js";
|
|
29447
29427
|
/*! *****************************************************************************
|
|
29448
29428
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
29449
29429
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
@@ -198809,11 +198789,6 @@ import {
|
|
|
198809
198789
|
deriveVerification as deriveVerification2,
|
|
198810
198790
|
isDeterminismViolation
|
|
198811
198791
|
} from "@secondlayer/subgraphs/verification";
|
|
198812
|
-
function webhookHint(apiUrl, name, firstTable) {
|
|
198813
|
-
if (!firstTable || isMerchantUrl(apiUrl))
|
|
198814
|
-
return null;
|
|
198815
|
-
return `secondlayer webhooks create ${name}-hook --subgraph ${name} --table ${firstTable} --url <your-endpoint>`;
|
|
198816
|
-
}
|
|
198817
198792
|
async function loadSubgraphWithDepCheck(absPath) {
|
|
198818
198793
|
try {
|
|
198819
198794
|
return await import(absPath);
|
|
@@ -199604,9 +199579,6 @@ deploy does not backfill from genesis.`).action(async (file, options) => {
|
|
|
199604
199579
|
info(` Reindex: ${denom} started in the background — check status in ~30s for an ETA`);
|
|
199605
199580
|
}
|
|
199606
199581
|
info(` Watch: secondlayer subgraphs status ${effectiveDef.name}`);
|
|
199607
|
-
const hint = webhookHint(apiUrl, effectiveDef.name, firstTable);
|
|
199608
|
-
if (hint)
|
|
199609
|
-
info(` Webhook: ${hint}`);
|
|
199610
199582
|
} catch {}
|
|
199611
199583
|
};
|
|
199612
199584
|
if (result.action === "unchanged") {
|
|
@@ -200273,7 +200245,6 @@ var init_subgraphs2 = __esm(() => {
|
|
|
200273
200245
|
init_subgraphs();
|
|
200274
200246
|
init_api_client();
|
|
200275
200247
|
init_derive_abi();
|
|
200276
|
-
init_api_url();
|
|
200277
200248
|
init_config2();
|
|
200278
200249
|
init_filter_params();
|
|
200279
200250
|
init_fs();
|
|
@@ -202507,7 +202478,7 @@ var init_src = __esm(() => {
|
|
|
202507
202478
|
|
|
202508
202479
|
// src/lib/dev-state.ts
|
|
202509
202480
|
import { homedir as homedir4 } from "node:os";
|
|
202510
|
-
import { join as
|
|
202481
|
+
import { join as join12 } from "node:path";
|
|
202511
202482
|
function getLogsDir() {
|
|
202512
202483
|
return LOGS_DIR;
|
|
202513
202484
|
}
|
|
@@ -202532,9 +202503,9 @@ function isProcessRunning(pid) {
|
|
|
202532
202503
|
}
|
|
202533
202504
|
var STATE_DIR, DEV_STATE_PATH, LOGS_DIR;
|
|
202534
202505
|
var init_dev_state = __esm(() => {
|
|
202535
|
-
STATE_DIR =
|
|
202536
|
-
DEV_STATE_PATH =
|
|
202537
|
-
LOGS_DIR =
|
|
202506
|
+
STATE_DIR = join12(homedir4(), ".secondlayer");
|
|
202507
|
+
DEV_STATE_PATH = join12(STATE_DIR, "dev.json");
|
|
202508
|
+
LOGS_DIR = join12(STATE_DIR, "logs");
|
|
202538
202509
|
});
|
|
202539
202510
|
|
|
202540
202511
|
// ../../node_modules/commander/index.js
|
|
@@ -202598,7 +202569,7 @@ var {
|
|
|
202598
202569
|
// package.json
|
|
202599
202570
|
var package_default = {
|
|
202600
202571
|
name: "@secondlayer/cli",
|
|
202601
|
-
version: "
|
|
202572
|
+
version: "20.0.1",
|
|
202602
202573
|
description: "Run a Stacks index on your own hardware — set it up, restore verified history, and keep it current.",
|
|
202603
202574
|
repository: {
|
|
202604
202575
|
type: "git",
|
|
@@ -202619,7 +202590,7 @@ var package_default = {
|
|
|
202619
202590
|
import: "./dist/index.js"
|
|
202620
202591
|
}
|
|
202621
202592
|
},
|
|
202622
|
-
files: ["dist", "
|
|
202593
|
+
files: ["dist", "README.md"],
|
|
202623
202594
|
scripts: {
|
|
202624
202595
|
build: "bunup",
|
|
202625
202596
|
dev: "bunup --watch",
|
|
@@ -202635,15 +202606,15 @@ var package_default = {
|
|
|
202635
202606
|
"@biomejs/wasm-nodejs": "^1.9.0",
|
|
202636
202607
|
"@dsnp/parquetjs": "~1.8.8",
|
|
202637
202608
|
"@inquirer/prompts": "^8.2.0",
|
|
202638
|
-
"@secondlayer/bundler": "^0.7.
|
|
202639
|
-
"@secondlayer/scaffold": "^1.6.
|
|
202640
|
-
"@secondlayer/sdk": "^
|
|
202641
|
-
"@secondlayer/shared": "^
|
|
202609
|
+
"@secondlayer/bundler": "^0.7.1",
|
|
202610
|
+
"@secondlayer/scaffold": "^1.6.2",
|
|
202611
|
+
"@secondlayer/sdk": "^15.0.0",
|
|
202612
|
+
"@secondlayer/shared": "^12.0.0",
|
|
202642
202613
|
"@secondlayer/stacks": "^6.2.0",
|
|
202643
|
-
"@secondlayer/subgraphs": "^
|
|
202644
|
-
"@secondlayer/verify": "^0.
|
|
202614
|
+
"@secondlayer/subgraphs": "^7.0.1",
|
|
202615
|
+
"@secondlayer/verify": "^0.5.0",
|
|
202645
202616
|
esbuild: "^0.28.2",
|
|
202646
|
-
"smol-toml": "^1.
|
|
202617
|
+
"smol-toml": "^1.9.1",
|
|
202647
202618
|
zod: "^4.3.6"
|
|
202648
202619
|
},
|
|
202649
202620
|
peerDependencies: {
|
|
@@ -204080,24 +204051,12 @@ init_resolve_auth();
|
|
|
204080
204051
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
204081
204052
|
import {
|
|
204082
204053
|
BlockVerifier,
|
|
204054
|
+
LINKS,
|
|
204083
204055
|
MAINNET_CHECKPOINT,
|
|
204084
204056
|
NodeRpcProofSource,
|
|
204085
204057
|
SecondlayerProofSource,
|
|
204086
|
-
|
|
204087
|
-
unhex
|
|
204058
|
+
describeLinks
|
|
204088
204059
|
} from "@secondlayer/verify";
|
|
204089
|
-
var LINKS = [
|
|
204090
|
-
{ step: "ancestry", label: "ancestry" },
|
|
204091
|
-
{ step: "block", label: "block" },
|
|
204092
|
-
{ step: "bitcoin", label: "bitcoin" },
|
|
204093
|
-
{ step: "burn", label: "burn" },
|
|
204094
|
-
{ step: "signer-set", label: "cycle" },
|
|
204095
|
-
{ step: "signatures", label: "signatures" },
|
|
204096
|
-
{ step: "txs", label: "txs" },
|
|
204097
|
-
{ step: "witness", label: "state root" },
|
|
204098
|
-
{ step: "names", label: "diff" },
|
|
204099
|
-
{ step: "rows", label: "rows" }
|
|
204100
|
-
];
|
|
204101
204060
|
var LABEL_WIDTH = Math.max(...LINKS.map((l) => l.label.length));
|
|
204102
204061
|
var n = (v) => v.toLocaleString("en-US");
|
|
204103
204062
|
function parseBlockRef(raw) {
|
|
@@ -204151,81 +204110,20 @@ function prepareSource(src) {
|
|
|
204151
204110
|
};
|
|
204152
204111
|
return out;
|
|
204153
204112
|
}
|
|
204154
|
-
function linkDetail(step, r, cp) {
|
|
204155
|
-
switch (step) {
|
|
204156
|
-
case "ancestry": {
|
|
204157
|
-
const a = r.ancestry;
|
|
204158
|
-
if (!a)
|
|
204159
|
-
return "";
|
|
204160
|
-
const checkpoint = parseNakamotoHeader(unhex(cp.stacks.header));
|
|
204161
|
-
const start = a.fromHeight === Number(checkpoint.chainLength) ? `the checkpoint ${n(a.fromHeight)}` : `block ${n(a.fromHeight)}`;
|
|
204162
|
-
return a.via === "marf" ? `${n(r.height ?? 0)} proven in the state of ${start} (one MARF proof)` : `${n(r.height ?? 0)} reached from ${start} by parent links`;
|
|
204163
|
-
}
|
|
204164
|
-
case "block":
|
|
204165
|
-
return `${n(r.height ?? 0)} ${r.blockId}`;
|
|
204166
|
-
case "bitcoin":
|
|
204167
|
-
return `headers ${n(cp.bitcoin.height)}..${n(r.burnHeight ?? 0)}, proof-of-work and retargets checked`;
|
|
204168
|
-
case "burn":
|
|
204169
|
-
return `height ${n(r.burnHeight ?? 0)} ${r.bitcoinBlockHash}`;
|
|
204170
|
-
case "signer-set":
|
|
204171
|
-
return r.cycle === cp.stacks.cycle ? `${r.cycle}, the checkpoint's own signer set` : `${r.cycle}, signer set proven forward from checkpoint cycle ${cp.stacks.cycle}`;
|
|
204172
|
-
case "signatures":
|
|
204173
|
-
return `${n(r.signerWeight ?? 0n)} / ${n(r.totalWeight ?? 0n)} weight signed, threshold ${n(r.threshold ?? 0n)}`;
|
|
204174
|
-
case "txs":
|
|
204175
|
-
return `${n(r.transactions?.length ?? 0)} transactions match the tx merkle root`;
|
|
204176
|
-
case "witness":
|
|
204177
|
-
return `${r.stateRoot} matches the witness`;
|
|
204178
|
-
case "names": {
|
|
204179
|
-
const d = r.diff;
|
|
204180
|
-
if (!d)
|
|
204181
|
-
return "";
|
|
204182
|
-
const named = d.writes.filter((w) => w.named).length;
|
|
204183
|
-
const written = named ? `${d.writes.length} written (${named} named, ${d.writes.length - named} unnamed)` : `${d.writes.length} written (unnamed)`;
|
|
204184
|
-
return `${written}, ${d.carried.length} carried, ${d.internal.length} internal`;
|
|
204185
|
-
}
|
|
204186
|
-
case "rows":
|
|
204187
|
-
return `${n(r.rowsChecked ?? 0)} vm_events rows match the diff`;
|
|
204188
|
-
}
|
|
204189
|
-
}
|
|
204190
|
-
function notNeeded(step, r) {
|
|
204191
|
-
if (!r.ancestry)
|
|
204192
|
-
return false;
|
|
204193
|
-
if (step === "bitcoin" || step === "burn")
|
|
204194
|
-
return r.burnHeight === undefined;
|
|
204195
|
-
return step === "signer-set" || step === "signatures";
|
|
204196
|
-
}
|
|
204197
204113
|
function report(r, cp, rows) {
|
|
204198
|
-
const
|
|
204199
|
-
|
|
204200
|
-
|
|
204201
|
-
|
|
204202
|
-
|
|
204203
|
-
if (
|
|
204204
|
-
|
|
204205
|
-
if (failure)
|
|
204206
|
-
writeData(`${red("✗")} ${label} ${failure.message}`);
|
|
204207
|
-
else if (link.step === "rows" && !rows)
|
|
204208
|
-
writeData(dim(`· ${label} not checked (pass --rows)`));
|
|
204209
|
-
else if (i > broken)
|
|
204210
|
-
writeData(dim(`· ${label} not checked: an earlier link is broken`));
|
|
204211
|
-
else if (notNeeded(link.step, r))
|
|
204212
|
-
writeData(dim(`· ${label} not needed: the block is hash-chained to the checkpoint`));
|
|
204213
|
-
else if (link.step === "rows" && r.rowsChecked === undefined)
|
|
204214
|
-
writeData(dim(`· ${label} not checked: the source serves no indexed rows`));
|
|
204215
|
-
else if (link.step === "txs" && r.transactions === undefined)
|
|
204216
|
-
writeData(dim(`· ${label} not checked: no transaction bytes (epoch 2.x, or the source served the header only)`));
|
|
204114
|
+
const { lines, verdict } = describeLinks(r, cp, { rows });
|
|
204115
|
+
for (const line of lines) {
|
|
204116
|
+
const label = line.label.padEnd(LABEL_WIDTH);
|
|
204117
|
+
if (line.status === "failed")
|
|
204118
|
+
writeData(`${red("✗")} ${label} ${line.detail}`);
|
|
204119
|
+
else if (line.status === "skipped")
|
|
204120
|
+
writeData(dim(`· ${label} ${line.detail}`));
|
|
204217
204121
|
else
|
|
204218
|
-
writeData(`${green("✓")} ${label} ${
|
|
204122
|
+
writeData(`${green("✓")} ${label} ${line.detail}`);
|
|
204219
204123
|
}
|
|
204220
204124
|
for (const line of r.notes)
|
|
204221
204125
|
note(` note: ${line}`);
|
|
204222
|
-
|
|
204223
|
-
const from = n(parseNakamotoHeader(unhex(cp.stacks.header)).chainLength);
|
|
204224
|
-
writeData(green(`✓ Block ${n(r.height ?? 0)} is proven. Trusted: only the checkpoint (Stacks ${from}, Bitcoin ${n(cp.bitcoin.height)}).`));
|
|
204225
|
-
return;
|
|
204226
|
-
}
|
|
204227
|
-
const label = LINKS[broken]?.label ?? first.step;
|
|
204228
|
-
writeData(red(first.code === "unavailable" ? `✗ Could not check the ${label} link: the source could not serve it.` : `✗ Not proven: the ${label} link is broken.`));
|
|
204126
|
+
writeData(verdict.ok ? green(`✓ ${verdict.text}`) : red(`✗ ${verdict.text}`));
|
|
204229
204127
|
}
|
|
204230
204128
|
function verificationJson(r) {
|
|
204231
204129
|
const { transactions, ...rest } = r;
|
|
@@ -204309,8 +204207,8 @@ import {
|
|
|
204309
204207
|
} from "@secondlayer/subgraphs/verify";
|
|
204310
204208
|
import {
|
|
204311
204209
|
MAINNET_CHECKPOINT as MAINNET_CHECKPOINT2,
|
|
204312
|
-
parseNakamotoHeader
|
|
204313
|
-
unhex
|
|
204210
|
+
parseNakamotoHeader,
|
|
204211
|
+
unhex
|
|
204314
204212
|
} from "@secondlayer/verify";
|
|
204315
204213
|
var LINKS2 = [
|
|
204316
204214
|
{ step: "pin", label: "pin" },
|
|
@@ -204335,13 +204233,13 @@ function parseHeight2(raw, flag) {
|
|
|
204335
204233
|
throw new InputError(`${flag} must be a block height, got "${raw}"`);
|
|
204336
204234
|
return Number(raw);
|
|
204337
204235
|
}
|
|
204338
|
-
async function readTable(fetchRows, name, table) {
|
|
204236
|
+
async function readTable(fetchRows, name, table, pageSize = PAGE) {
|
|
204339
204237
|
const out = [];
|
|
204340
204238
|
let cursor;
|
|
204341
204239
|
for (;; ) {
|
|
204342
204240
|
const page = await fetchRows(name, table, cursor);
|
|
204343
204241
|
out.push(...page.rows);
|
|
204344
|
-
if (!page.next_cursor || page.rows.length
|
|
204242
|
+
if (!page.next_cursor || page.rows.length < pageSize)
|
|
204345
204243
|
return out;
|
|
204346
204244
|
cursor = page.next_cursor;
|
|
204347
204245
|
}
|
|
@@ -204398,7 +204296,7 @@ function report2(r, from, to) {
|
|
|
204398
204296
|
return VERIFY_EXIT.UNANCHORED;
|
|
204399
204297
|
}
|
|
204400
204298
|
const cp = MAINNET_CHECKPOINT2;
|
|
204401
|
-
const stacks = n2(Number(
|
|
204299
|
+
const stacks = n2(Number(parseNakamotoHeader(unhex(cp.stacks.header)).chainLength));
|
|
204402
204300
|
writeData(green(`✓ ${r.name} ${n2(from)}..${n2(to)} recomputed from proven inputs. Trusted: only the checkpoint (Stacks ${stacks}, Bitcoin ${n2(cp.bitcoin.height)}).`));
|
|
204403
204301
|
writeData(dim(` unproven, so not compared: ${[...r.unproven, "rows compare declared columns and _block_height only"].join("; ")}`));
|
|
204404
204302
|
return VERIFY_EXIT.CLEAN;
|
|
@@ -205574,84 +205472,19 @@ function printPublicStatus(status, diagnosis) {
|
|
|
205574
205472
|
}
|
|
205575
205473
|
// src/commands/webhooks.ts
|
|
205576
205474
|
init_api_client();
|
|
205577
|
-
init_filter_params();
|
|
205578
205475
|
init_output();
|
|
205579
205476
|
init_resolve_auth();
|
|
205580
205477
|
import { buildDoctorReport, isSuccessDelivery } from "@secondlayer/sdk";
|
|
205581
205478
|
import { sign } from "@secondlayer/shared/crypto/standard-webhooks";
|
|
205582
205479
|
|
|
205583
|
-
// src/lib/webhook-validation.ts
|
|
205584
|
-
import {
|
|
205585
|
-
validateWebhookFilterForTable
|
|
205586
|
-
} from "@secondlayer/shared/schemas/webhooks";
|
|
205587
|
-
async function validateWebhookTargetFromApi(client, input) {
|
|
205588
|
-
const subgraph = await client.subgraphs.status(input.subgraphName);
|
|
205589
|
-
const errors = validateWebhookFilterForTable({
|
|
205590
|
-
subgraphName: input.subgraphName,
|
|
205591
|
-
tableName: input.tableName,
|
|
205592
|
-
filter: input.filter,
|
|
205593
|
-
tables: subgraph.tables
|
|
205594
|
-
});
|
|
205595
|
-
if (errors.length > 0) {
|
|
205596
|
-
throw new Error(errors.join(`
|
|
205597
|
-
`));
|
|
205598
|
-
}
|
|
205599
|
-
}
|
|
205600
|
-
|
|
205601
205480
|
// src/commands/create.ts
|
|
205602
|
-
init_filter_params();
|
|
205603
205481
|
init_output();
|
|
205604
205482
|
init_resolve_auth();
|
|
205605
|
-
import {
|
|
205606
|
-
chmodSync,
|
|
205607
|
-
copyFileSync,
|
|
205608
|
-
existsSync as existsSync5,
|
|
205609
|
-
mkdirSync as mkdirSync3,
|
|
205610
|
-
readFileSync as readFileSync5,
|
|
205611
|
-
readdirSync,
|
|
205612
|
-
statSync as statSync2,
|
|
205613
|
-
writeFileSync as writeFileSync4
|
|
205614
|
-
} from "node:fs";
|
|
205615
|
-
import { dirname as dirname6, join as join9, relative as relative2, resolve as resolve7 } from "node:path";
|
|
205616
|
-
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
205617
|
-
import { input, select as select3 } from "@inquirer/prompts";
|
|
205483
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
205618
205484
|
import { SecondLayer as SecondLayer4 } from "@secondlayer/sdk";
|
|
205619
205485
|
import {
|
|
205620
205486
|
ChainTriggerSchema
|
|
205621
205487
|
} from "@secondlayer/shared/schemas/webhooks";
|
|
205622
|
-
var RUNTIMES = ["inngest", "trigger", "cloudflare", "node"];
|
|
205623
|
-
var FORMAT_BY_RUNTIME = {
|
|
205624
|
-
inngest: "inngest",
|
|
205625
|
-
trigger: "trigger",
|
|
205626
|
-
cloudflare: "cloudflare",
|
|
205627
|
-
node: "standard-webhooks"
|
|
205628
|
-
};
|
|
205629
|
-
function templatesRoot() {
|
|
205630
|
-
const here = dirname6(fileURLToPath4(import.meta.url));
|
|
205631
|
-
const candidateDist = resolve7(here, "..", "templates", "webhooks");
|
|
205632
|
-
if (existsSync5(candidateDist))
|
|
205633
|
-
return candidateDist;
|
|
205634
|
-
return resolve7(here, "..", "..", "templates", "webhooks");
|
|
205635
|
-
}
|
|
205636
|
-
function copyTemplate(src, dest, vars) {
|
|
205637
|
-
if (!existsSync5(src)) {
|
|
205638
|
-
throw new Error(`template dir missing: ${src}`);
|
|
205639
|
-
}
|
|
205640
|
-
mkdirSync3(dest, { recursive: true });
|
|
205641
|
-
const entries = readdirSync(src);
|
|
205642
|
-
for (const entry of entries) {
|
|
205643
|
-
const from = join9(src, entry);
|
|
205644
|
-
const to = join9(dest, entry);
|
|
205645
|
-
const st = statSync2(from);
|
|
205646
|
-
if (st.isDirectory()) {
|
|
205647
|
-
copyTemplate(from, to, vars);
|
|
205648
|
-
} else {
|
|
205649
|
-
const raw = readFileSync5(from, "utf8");
|
|
205650
|
-
const rendered = raw.replace(/\{\{([A-Z_]+)\}\}/g, (match, key) => vars[key] ?? match);
|
|
205651
|
-
writeFileSync4(to, rendered);
|
|
205652
|
-
}
|
|
205653
|
-
}
|
|
205654
|
-
}
|
|
205655
205488
|
function parseTriggersInput(opts) {
|
|
205656
205489
|
const raw = [];
|
|
205657
205490
|
if (opts.triggersFile) {
|
|
@@ -205684,53 +205517,6 @@ function parseTriggersInput(opts) {
|
|
|
205684
205517
|
return result.data;
|
|
205685
205518
|
});
|
|
205686
205519
|
}
|
|
205687
|
-
function resolveRuntime(opts) {
|
|
205688
|
-
if (opts.runtime)
|
|
205689
|
-
return opts.runtime;
|
|
205690
|
-
const scripted = opts.subgraph !== undefined || opts.table !== undefined || opts.url !== undefined || opts.scaffold === false;
|
|
205691
|
-
return scripted ? "node" : undefined;
|
|
205692
|
-
}
|
|
205693
|
-
function requireTtyFor(flag) {
|
|
205694
|
-
if (process.stdin.isTTY)
|
|
205695
|
-
return;
|
|
205696
|
-
error(`${flag} is required when stdin is not a TTY (no prompt can run). Pass it, or run from a terminal.`);
|
|
205697
|
-
process.exit(1);
|
|
205698
|
-
}
|
|
205699
|
-
async function promptFor(_name, opts) {
|
|
205700
|
-
let runtime = resolveRuntime(opts);
|
|
205701
|
-
if (runtime === undefined) {
|
|
205702
|
-
requireTtyFor("--runtime");
|
|
205703
|
-
runtime = await select3({
|
|
205704
|
-
message: "Runtime?",
|
|
205705
|
-
choices: RUNTIMES.map((r) => ({ name: r, value: r }))
|
|
205706
|
-
});
|
|
205707
|
-
}
|
|
205708
|
-
let subgraph = opts.subgraph;
|
|
205709
|
-
if (subgraph === undefined) {
|
|
205710
|
-
requireTtyFor("--subgraph");
|
|
205711
|
-
subgraph = await input({
|
|
205712
|
-
message: "Subgraph name (must already be deployed):",
|
|
205713
|
-
validate: (v) => v.trim().length > 0 ? true : "required"
|
|
205714
|
-
});
|
|
205715
|
-
}
|
|
205716
|
-
let table = opts.table;
|
|
205717
|
-
if (table === undefined) {
|
|
205718
|
-
requireTtyFor("--table");
|
|
205719
|
-
table = await input({
|
|
205720
|
-
message: "Table to subscribe to:",
|
|
205721
|
-
validate: (v) => v.trim().length > 0 ? true : "required"
|
|
205722
|
-
});
|
|
205723
|
-
}
|
|
205724
|
-
let url = opts.url;
|
|
205725
|
-
if (url === undefined) {
|
|
205726
|
-
requireTtyFor("--url");
|
|
205727
|
-
url = await input({
|
|
205728
|
-
message: runtime === "inngest" ? "Inngest event endpoint URL (e.g. https://inn.gs/e/<KEY> or http://localhost:8288/e/<KEY>):" : runtime === "trigger" ? "Trigger.dev task URL (e.g. https://api.trigger.dev/api/v1/tasks/<TASK_ID>/trigger):" : runtime === "cloudflare" ? "Cloudflare Workflows instances URL:" : "Your HTTP receiver URL (e.g. https://yourapp.example/webhook):",
|
|
205729
|
-
validate: (v) => v.startsWith("http://") || v.startsWith("https://") ? true : "must be http(s) URL"
|
|
205730
|
-
});
|
|
205731
|
-
}
|
|
205732
|
-
return { runtime, subgraph, table, url };
|
|
205733
|
-
}
|
|
205734
205520
|
function buildWebhookAuthConfig(authToken) {
|
|
205735
205521
|
if (authToken === undefined)
|
|
205736
205522
|
return;
|
|
@@ -205740,139 +205526,9 @@ function buildWebhookAuthConfig(authToken) {
|
|
|
205740
205526
|
}
|
|
205741
205527
|
return { authType: "bearer", token };
|
|
205742
205528
|
}
|
|
205743
|
-
function writeSigningSecretEnv(targetDir, signingSecret) {
|
|
205744
|
-
const envTarget = join9(targetDir, ".env");
|
|
205745
|
-
const envExample = join9(targetDir, ".env.example");
|
|
205746
|
-
if (existsSync5(envExample) && !existsSync5(envTarget)) {
|
|
205747
|
-
copyFileSync(envExample, envTarget);
|
|
205748
|
-
chmodSync(envTarget, 384);
|
|
205749
|
-
}
|
|
205750
|
-
if (!existsSync5(envTarget)) {
|
|
205751
|
-
writeFileSync4(envTarget, `SIGNING_SECRET=${signingSecret}
|
|
205752
|
-
`, {
|
|
205753
|
-
mode: 384
|
|
205754
|
-
});
|
|
205755
|
-
} else {
|
|
205756
|
-
const cur = readFileSync5(envTarget, "utf8");
|
|
205757
|
-
writeFileSync4(envTarget, cur.match(/^SIGNING_SECRET=/m) ? cur.replace(/^SIGNING_SECRET=.*/m, `SIGNING_SECRET=${signingSecret}`) : `${cur}
|
|
205758
|
-
SIGNING_SECRET=${signingSecret}
|
|
205759
|
-
`, { mode: 384 });
|
|
205760
|
-
chmodSync(envTarget, 384);
|
|
205761
|
-
}
|
|
205762
|
-
return envTarget;
|
|
205763
|
-
}
|
|
205764
|
-
async function createWebhook(name, opts) {
|
|
205765
|
-
if (opts.runtime && !RUNTIMES.includes(opts.runtime)) {
|
|
205766
|
-
error(`Unknown --runtime "${opts.runtime}". Valid: ${RUNTIMES.join(", ")}`);
|
|
205767
|
-
process.exit(1);
|
|
205768
|
-
}
|
|
205769
|
-
const { runtime, subgraph, table, url } = await promptFor(name, opts);
|
|
205770
|
-
let filter;
|
|
205771
|
-
let authConfig;
|
|
205772
|
-
try {
|
|
205773
|
-
filter = parseWebhookFilter(opts.filter);
|
|
205774
|
-
authConfig = buildWebhookAuthConfig(opts.authToken);
|
|
205775
|
-
} catch (err) {
|
|
205776
|
-
error(err instanceof Error ? err.message : String(err));
|
|
205777
|
-
process.exit(1);
|
|
205778
|
-
}
|
|
205779
|
-
let sl = null;
|
|
205780
|
-
if (!opts.skipApi) {
|
|
205781
|
-
try {
|
|
205782
|
-
sl = await getWebhookClient();
|
|
205783
|
-
await validateWebhookTargetFromApi(sl, {
|
|
205784
|
-
subgraphName: subgraph,
|
|
205785
|
-
tableName: table,
|
|
205786
|
-
filter
|
|
205787
|
-
});
|
|
205788
|
-
} catch (err) {
|
|
205789
|
-
error(err instanceof Error ? err.message : String(err));
|
|
205790
|
-
process.exit(1);
|
|
205791
|
-
}
|
|
205792
|
-
}
|
|
205793
|
-
const eventName = `${subgraph}.${table}.created`;
|
|
205794
|
-
const targetDir = resolve7(process.cwd(), name);
|
|
205795
|
-
if (opts.scaffold !== false) {
|
|
205796
|
-
if (existsSync5(targetDir)) {
|
|
205797
|
-
error(`Directory already exists: ${relative2(process.cwd(), targetDir)}`);
|
|
205798
|
-
process.exit(1);
|
|
205799
|
-
}
|
|
205800
|
-
const tplRoot = templatesRoot();
|
|
205801
|
-
const tplDir = join9(tplRoot, runtime);
|
|
205802
|
-
info(`Scaffolding ${blue(runtime)} template at ${blue(targetDir)}`);
|
|
205803
|
-
copyTemplate(tplDir, targetDir, {
|
|
205804
|
-
NAME: name,
|
|
205805
|
-
EVENT_NAME: eventName,
|
|
205806
|
-
TASK_ID: `${subgraph}-${table}`
|
|
205807
|
-
});
|
|
205808
|
-
}
|
|
205809
|
-
let signingSecret = null;
|
|
205810
|
-
let provisioningFailed = false;
|
|
205811
|
-
if (!opts.skipApi) {
|
|
205812
|
-
try {
|
|
205813
|
-
if (!sl)
|
|
205814
|
-
sl = await getWebhookClient();
|
|
205815
|
-
const res = await sl.webhooks.create({
|
|
205816
|
-
name,
|
|
205817
|
-
subgraphName: subgraph,
|
|
205818
|
-
tableName: table,
|
|
205819
|
-
url,
|
|
205820
|
-
format: FORMAT_BY_RUNTIME[runtime],
|
|
205821
|
-
runtime,
|
|
205822
|
-
...filter ? { filter } : {},
|
|
205823
|
-
...authConfig ? { authConfig } : {}
|
|
205824
|
-
});
|
|
205825
|
-
signingSecret = res.signingSecret;
|
|
205826
|
-
success(`Webhook provisioned: ${blue(res.webhook.id)}`);
|
|
205827
|
-
} catch (err) {
|
|
205828
|
-
provisioningFailed = true;
|
|
205829
|
-
warn(`Webhook provisioning failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
205830
|
-
info("Template copied, but the webhook was not created. Fix the API error, remove the template directory, and rerun.");
|
|
205831
|
-
}
|
|
205832
|
-
}
|
|
205833
|
-
let webhookStatus;
|
|
205834
|
-
if (sl) {
|
|
205835
|
-
try {
|
|
205836
|
-
const list = await sl.webhooks.list();
|
|
205837
|
-
const rows = list.data ?? [];
|
|
205838
|
-
const created = rows.find((s) => s.name === name);
|
|
205839
|
-
webhookStatus = created?.status;
|
|
205840
|
-
} catch {}
|
|
205841
|
-
}
|
|
205842
|
-
if (signingSecret && opts.scaffold !== false) {
|
|
205843
|
-
const envTarget = writeSigningSecretEnv(targetDir, signingSecret);
|
|
205844
|
-
success(`Signing secret written to ${relative2(process.cwd(), envTarget)}`);
|
|
205845
|
-
} else if (signingSecret && opts.scaffold === false) {
|
|
205846
|
-
info(`Signing secret (store securely): ${signingSecret}`);
|
|
205847
|
-
}
|
|
205848
|
-
console.log();
|
|
205849
|
-
if (provisioningFailed) {
|
|
205850
|
-
error("Webhook was not created.");
|
|
205851
|
-
process.exit(1);
|
|
205852
|
-
}
|
|
205853
|
-
const pausedLine = webhookStatus === "paused" ? `Webhook is paused. Resume:
|
|
205854
|
-
secondlayer webhooks resume ${name}
|
|
205855
|
-
` : "";
|
|
205856
|
-
const runHint = opts.scaffold === false ? `View deliveries:
|
|
205857
|
-
secondlayer webhooks get ${name}` : `cd ${name}
|
|
205858
|
-
bun install
|
|
205859
|
-
bun run dev`;
|
|
205860
|
-
success(`Done. Next:
|
|
205861
|
-
${pausedLine}${runHint}`);
|
|
205862
|
-
}
|
|
205863
205529
|
async function createChainWebhook(name, opts) {
|
|
205864
|
-
for (const [flag, value] of [
|
|
205865
|
-
["--subgraph", opts.subgraph],
|
|
205866
|
-
["--table", opts.table],
|
|
205867
|
-
["--runtime", opts.runtime]
|
|
205868
|
-
]) {
|
|
205869
|
-
if (value !== undefined) {
|
|
205870
|
-
error(`${flag} is not valid with --trigger/--triggers-file (chain mode)`);
|
|
205871
|
-
process.exit(1);
|
|
205872
|
-
}
|
|
205873
|
-
}
|
|
205874
205530
|
if (!opts.url) {
|
|
205875
|
-
error("--url is required
|
|
205531
|
+
error("--url is required");
|
|
205876
205532
|
process.exit(1);
|
|
205877
205533
|
}
|
|
205878
205534
|
let triggers;
|
|
@@ -205897,7 +205553,7 @@ async function createChainWebhook(name, opts) {
|
|
|
205897
205553
|
});
|
|
205898
205554
|
webhookId = res.webhook.id;
|
|
205899
205555
|
signingSecret = res.signingSecret;
|
|
205900
|
-
success(`
|
|
205556
|
+
success(`Webhook provisioned: ${blue(webhookId)}`);
|
|
205901
205557
|
} catch (err) {
|
|
205902
205558
|
error(`Webhook provisioning failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
205903
205559
|
process.exit(1);
|
|
@@ -205912,24 +205568,11 @@ async function getWebhookClient() {
|
|
|
205912
205568
|
const { apiUrl, ephemeralKey } = await resolveHostedAuth();
|
|
205913
205569
|
return new SecondLayer4({ baseUrl: apiUrl, apiKey: ephemeralKey });
|
|
205914
205570
|
}
|
|
205915
|
-
function addWebhookScaffold(parent, commandSpec, opts) {
|
|
205916
|
-
parent.command(commandSpec).description(opts.description).option("-r, --runtime <runtime>", "inngest | trigger | cloudflare | node (default: node once -s/-t/-u or --no-scaffold is given; prompts only in a terminal with no flags)").option("-s, --subgraph <name>", "Subgraph to subscribe to").option("-t, --table <name>", "Table to subscribe to").option("-u, --url <url>", "Webhook URL").option("--auth-token <token>", "Bearer token for receiver API auth; a flag lands in shell history and ps, so set it in the receiver's env and pass it here only for a throwaway token").option("--filter <kv...>", "Filter as key=value (supports .eq/.neq/.gt/.gte/.lt/.lte suffixes)").option("--trigger <json...>", `Chain-webhook trigger as JSON (repeatable), e.g. '{"type":"sbtc_deposit"}'`).option("--triggers-file <path>", "Chain-webhook triggers as a JSON array file").option("--format <format>", "Chain mode delivery format (default standard-webhooks)").option("--skip-api", "Copy template only, don't call the API").option("--no-scaffold", "Skip the local runtime template directory (webhook-only setups)").addHelpText("after", `
|
|
205917
|
-
Examples:
|
|
205918
|
-
$ ${opts.examplePrefix} my-sub -s my-graph -t transfers -u https://example.com/webhook
|
|
205919
|
-
$ ${opts.examplePrefix} my-sub -s my-graph -t balances -r inngest --filter amount.gte=1000
|
|
205920
|
-
$ ${opts.examplePrefix} my-hook -u https://example.com/webhook --trigger '{"type":"sbtc_deposit"}'`).action(async (name, options) => {
|
|
205921
|
-
if (options.trigger?.length || options.triggersFile) {
|
|
205922
|
-
await createChainWebhook(name, options);
|
|
205923
|
-
return;
|
|
205924
|
-
}
|
|
205925
|
-
await createWebhook(name, options);
|
|
205926
|
-
});
|
|
205927
|
-
}
|
|
205928
205571
|
function addWebhooksCreateCommand(parent) {
|
|
205929
|
-
|
|
205930
|
-
|
|
205931
|
-
|
|
205932
|
-
|
|
205572
|
+
parent.command("create <name>").description("Create a webhook on raw chain events").option("-u, --url <url>", "Webhook URL").option("--auth-token <token>", "Bearer token for receiver API auth; a flag lands in shell history and ps, so set it in the receiver's env and pass it here only for a throwaway token").option("--trigger <json...>", `Chain trigger as JSON (repeatable), e.g. '{"type":"sbtc_deposit"}'`).option("--triggers-file <path>", "Chain triggers as a JSON array file").option("--format <format>", "Delivery format (default standard-webhooks)").addHelpText("after", `
|
|
205573
|
+
Examples:
|
|
205574
|
+
$ secondlayer webhooks create my-hook -u https://example.com/webhook --trigger '{"type":"sbtc_deposit"}'
|
|
205575
|
+
$ secondlayer webhooks create my-hook -u https://example.com/webhook --triggers-file triggers.json`).action(createChainWebhook);
|
|
205933
205576
|
}
|
|
205934
205577
|
|
|
205935
205578
|
// src/commands/webhooks.ts
|
|
@@ -205999,17 +205642,6 @@ function buildWebhookUpdatePatch(options) {
|
|
|
205999
205642
|
if (options.format) {
|
|
206000
205643
|
patch.format = options.format;
|
|
206001
205644
|
}
|
|
206002
|
-
if (options.runtime !== undefined) {
|
|
206003
|
-
patch.runtime = options.runtime === "none" || options.runtime === "null" ? null : options.runtime;
|
|
206004
|
-
}
|
|
206005
|
-
if (options.clearFilter)
|
|
206006
|
-
patch.filter = {};
|
|
206007
|
-
if (options.filter) {
|
|
206008
|
-
if (options.clearFilter) {
|
|
206009
|
-
throw new Error("Use either --filter or --clear-filter, not both");
|
|
206010
|
-
}
|
|
206011
|
-
patch.filter = parseWebhookFilter(options.filter) ?? {};
|
|
206012
|
-
}
|
|
206013
205645
|
const maxRetries = parseIntegerOption(options.maxRetries, "--max-retries", 0);
|
|
206014
205646
|
if (maxRetries !== undefined)
|
|
206015
205647
|
patch.maxRetries = maxRetries;
|
|
@@ -206025,11 +205657,8 @@ function buildWebhookUpdatePatch(options) {
|
|
|
206025
205657
|
return patch;
|
|
206026
205658
|
}
|
|
206027
205659
|
function formatWebhookTarget(sub) {
|
|
206028
|
-
|
|
206029
|
-
|
|
206030
|
-
return types && types.length > 0 ? `chain.${types.join(",")}` : "chain";
|
|
206031
|
-
}
|
|
206032
|
-
return `${sub.subgraphName}.${sub.tableName}`;
|
|
205660
|
+
const types = "triggers" in sub ? sub.triggers.map((t) => t.type) : [];
|
|
205661
|
+
return types.length > 0 ? `chain.${types.join(",")}` : "chain";
|
|
206033
205662
|
}
|
|
206034
205663
|
function formatAuthLine(auth) {
|
|
206035
205664
|
const scheme = auth.type === "bearer" ? auth.hasSecret ? "bearer token (set, hidden)" : "bearer (no token set)" : auth.type === "basic" ? auth.hasSecret ? "basic auth (set, hidden)" : "basic (no credentials set)" : "none";
|
|
@@ -206042,7 +205671,6 @@ function printWebhookDetail(sub) {
|
|
|
206042
205671
|
["Status", sub.status],
|
|
206043
205672
|
["Target", formatWebhookTarget(sub)],
|
|
206044
205673
|
["Format", sub.format],
|
|
206045
|
-
["Runtime", sub.runtime ?? "none"],
|
|
206046
205674
|
["URL", sub.url],
|
|
206047
205675
|
["Auth", formatAuthLine(sub.auth)],
|
|
206048
205676
|
["Last Delivery", sub.lastDeliveryAt ?? "none"],
|
|
@@ -206058,8 +205686,8 @@ function printWebhookDetail(sub) {
|
|
|
206058
205686
|
["Updated", sub.updatedAt]
|
|
206059
205687
|
]));
|
|
206060
205688
|
console.log(dim(`
|
|
206061
|
-
|
|
206062
|
-
console.log(JSON.stringify(sub.
|
|
205689
|
+
Triggers:`));
|
|
205690
|
+
console.log(JSON.stringify(sub.triggers, null, 2));
|
|
206063
205691
|
}
|
|
206064
205692
|
var DOCTOR_ISSUE_TITLES = {
|
|
206065
205693
|
warning: "This webhook can't fire yet",
|
|
@@ -206067,8 +205695,6 @@ var DOCTOR_ISSUE_TITLES = {
|
|
|
206067
205695
|
last_error: "The last delivery failed",
|
|
206068
205696
|
circuit: "Your receiver is failing",
|
|
206069
205697
|
dead_letters: "Some events exhausted every retry",
|
|
206070
|
-
subgraph_gaps: "The linked subgraph has gaps",
|
|
206071
|
-
subgraph_catching_up: "The linked subgraph is still catching up",
|
|
206072
205698
|
no_deliveries: "No deliveries yet",
|
|
206073
205699
|
receiver_rate_limited: "Your receiver is rate-limiting us",
|
|
206074
205700
|
receiver_down: "Your receiver looks down",
|
|
@@ -206100,7 +205726,6 @@ function formatDoctorReport(report) {
|
|
|
206100
205726
|
["Status", sub.status],
|
|
206101
205727
|
["Target", formatWebhookTarget(sub)],
|
|
206102
205728
|
["Format", sub.format],
|
|
206103
|
-
["Runtime", sub.runtime ?? "none"],
|
|
206104
205729
|
["URL", sub.url],
|
|
206105
205730
|
[
|
|
206106
205731
|
"Circuit",
|
|
@@ -206126,24 +205751,6 @@ Delivery summary:`),
|
|
|
206126
205751
|
].join(`
|
|
206127
205752
|
`)
|
|
206128
205753
|
];
|
|
206129
|
-
if (report.subgraph) {
|
|
206130
|
-
sections.push([
|
|
206131
|
-
dim(`
|
|
206132
|
-
Linked subgraph:`),
|
|
206133
|
-
formatKeyValue([
|
|
206134
|
-
["Name", report.subgraph.name],
|
|
206135
|
-
["Status", report.subgraph.status],
|
|
206136
|
-
["Sync", report.subgraph.syncStatus],
|
|
206137
|
-
[
|
|
206138
|
-
"Blocks",
|
|
206139
|
-
`${report.subgraph.lastProcessedBlock} / ${report.subgraph.chainTip}`
|
|
206140
|
-
],
|
|
206141
|
-
["Integrity", report.subgraph.integrity],
|
|
206142
|
-
["Gaps", String(report.subgraph.gapCount)]
|
|
206143
|
-
])
|
|
206144
|
-
].join(`
|
|
206145
|
-
`));
|
|
206146
|
-
}
|
|
206147
205754
|
if (report.primary)
|
|
206148
205755
|
sections.push(formatPrimaryInsight(report.primary));
|
|
206149
205756
|
const primaryIndex = report.primary ? report.issues.indexOf(report.primary) : -1;
|
|
@@ -206159,35 +205766,6 @@ Next steps:`), ...rest.map((hint) => ` - ${hint}`)].join(`
|
|
|
206159
205766
|
function printDoctorReport(report) {
|
|
206160
205767
|
console.log(formatDoctorReport(report));
|
|
206161
205768
|
}
|
|
206162
|
-
function syntheticValue(type) {
|
|
206163
|
-
switch (type) {
|
|
206164
|
-
case "uint":
|
|
206165
|
-
case "int":
|
|
206166
|
-
return "1000";
|
|
206167
|
-
case "boolean":
|
|
206168
|
-
return true;
|
|
206169
|
-
case "timestamp":
|
|
206170
|
-
return new Date(0).toISOString();
|
|
206171
|
-
case "jsonb":
|
|
206172
|
-
return {};
|
|
206173
|
-
case "principal":
|
|
206174
|
-
return "SP000000000000000000002Q6VF78";
|
|
206175
|
-
default:
|
|
206176
|
-
return "example";
|
|
206177
|
-
}
|
|
206178
|
-
}
|
|
206179
|
-
function buildSyntheticRow(subgraph, tableName) {
|
|
206180
|
-
const table = subgraph?.tables[tableName];
|
|
206181
|
-
if (!table)
|
|
206182
|
-
return { id: "example", value: "example" };
|
|
206183
|
-
const row = {};
|
|
206184
|
-
for (const [column, def] of Object.entries(table.columns)) {
|
|
206185
|
-
if (column.startsWith("_"))
|
|
206186
|
-
continue;
|
|
206187
|
-
row[column] = syntheticValue(def.type);
|
|
206188
|
-
}
|
|
206189
|
-
return Object.keys(row).length > 0 ? row : { id: "example", value: "example" };
|
|
206190
|
-
}
|
|
206191
205769
|
function resolveSigningSecret(options, env = process.env) {
|
|
206192
205770
|
const secret = options.signingSecret ?? env.SIGNING_SECRET;
|
|
206193
205771
|
if (!secret) {
|
|
@@ -206201,17 +205779,16 @@ function shellQuote(value) {
|
|
|
206201
205779
|
function buildWebhookTestFixture(input) {
|
|
206202
205780
|
const nowSeconds = input.nowSeconds ?? Math.floor(Date.now() / 1000);
|
|
206203
205781
|
const sub = input.webhook;
|
|
206204
|
-
const
|
|
206205
|
-
const
|
|
206206
|
-
const data = isChain ? {
|
|
205782
|
+
const type = `chain.${sub.triggers[0]?.type ?? "event"}.apply`;
|
|
205783
|
+
const data = {
|
|
206207
205784
|
action: "apply",
|
|
206208
205785
|
canonical: true,
|
|
206209
205786
|
block_hash: "0xexampleblock",
|
|
206210
205787
|
block_height: 0,
|
|
206211
205788
|
tx_id: input.row.tx_id ?? "0xexampletransaction",
|
|
206212
|
-
trigger: sub.triggers
|
|
205789
|
+
trigger: sub.triggers[0]?.type ?? "event",
|
|
206213
205790
|
event: input.row
|
|
206214
|
-
}
|
|
205791
|
+
};
|
|
206215
205792
|
const body = JSON.stringify({
|
|
206216
205793
|
type,
|
|
206217
205794
|
timestamp: new Date(nowSeconds * 1000).toISOString(),
|
|
@@ -206234,25 +205811,12 @@ function buildWebhookTestFixture(input) {
|
|
|
206234
205811
|
`);
|
|
206235
205812
|
return { body, headers, curl };
|
|
206236
205813
|
}
|
|
206237
|
-
|
|
206238
|
-
|
|
206239
|
-
|
|
206240
|
-
|
|
206241
|
-
|
|
206242
|
-
|
|
206243
|
-
};
|
|
206244
|
-
}
|
|
206245
|
-
try {
|
|
206246
|
-
const { rows } = await client.subgraphs.rows(sub.subgraphName, sub.tableName, {
|
|
206247
|
-
sort: "_block_height",
|
|
206248
|
-
order: "desc",
|
|
206249
|
-
limit: 1
|
|
206250
|
-
});
|
|
206251
|
-
const row = rows[0];
|
|
206252
|
-
if (row && typeof row === "object")
|
|
206253
|
-
return row;
|
|
206254
|
-
} catch {}
|
|
206255
|
-
return buildSyntheticRow(subgraph, sub.tableName);
|
|
205814
|
+
function representativeRow(sub) {
|
|
205815
|
+
return {
|
|
205816
|
+
tx_id: "0xexampletransaction",
|
|
205817
|
+
contract_id: "SP000000000000000000002Q6VF78.example",
|
|
205818
|
+
...sub.triggers[0]?.type === "contract_call" ? { function_name: "example-fn" } : {}
|
|
205819
|
+
};
|
|
206256
205820
|
}
|
|
206257
205821
|
function printDeliveries(rows) {
|
|
206258
205822
|
if (rows.length === 0) {
|
|
@@ -206329,22 +205893,14 @@ ${data.length} webhook(s) total`));
|
|
|
206329
205893
|
handleApiError(err, "get webhook");
|
|
206330
205894
|
}
|
|
206331
205895
|
});
|
|
206332
|
-
parent.command("update <idOrName>").description("Update webhook config").option("--name <name>", "Rename webhook").option("--url <url>", "Webhook URL").option("--auth-token <token>", "Set bearer token auth config; a flag lands in shell history and ps, so pass it here only for a throwaway token").option("--format <format>", "standard-webhooks | inngest | trigger | cloudflare | cloudevents | raw").option("--
|
|
205896
|
+
parent.command("update <idOrName>").description("Update webhook config").option("--name <name>", "Rename webhook").option("--url <url>", "Webhook URL").option("--auth-token <token>", "Set bearer token auth config; a flag lands in shell history and ps, so pass it here only for a throwaway token").option("--format <format>", "standard-webhooks | inngest | trigger | cloudflare | cloudevents | raw").option("--max-retries <n>", "Maximum delivery retries").option("--timeout-ms <n>", "Delivery timeout in milliseconds").option("--concurrency <n>", "Per-webhook delivery concurrency").option("--json", "Output as JSON").addHelpText("after", `
|
|
206333
205897
|
Examples:
|
|
206334
205898
|
$ secondlayer webhooks update my-sub --url https://example.com/hook
|
|
206335
|
-
$ secondlayer webhooks update my-sub --
|
|
206336
|
-
$ secondlayer webhooks update my-sub --clear-filter`).action(async (idOrName, options) => {
|
|
205899
|
+
$ secondlayer webhooks update my-sub --max-retries 5 --timeout-ms 20000`).action(async (idOrName, options) => {
|
|
206337
205900
|
try {
|
|
206338
205901
|
const client = await getWebhookClient();
|
|
206339
205902
|
const patch = buildWebhookUpdatePatch(options);
|
|
206340
|
-
const { id
|
|
206341
|
-
if (patch.filter !== undefined && detail.subgraphName && detail.tableName) {
|
|
206342
|
-
await validateWebhookTargetFromApi(client, {
|
|
206343
|
-
subgraphName: detail.subgraphName,
|
|
206344
|
-
tableName: detail.tableName,
|
|
206345
|
-
filter: patch.filter
|
|
206346
|
-
});
|
|
206347
|
-
}
|
|
205903
|
+
const { id } = await resolveWebhookRef(client, idOrName);
|
|
206348
205904
|
const updated = await client.webhooks.update(id, patch);
|
|
206349
205905
|
if (options.json)
|
|
206350
205906
|
printJson(updated);
|
|
@@ -206495,16 +206051,14 @@ Examples:
|
|
|
206495
206051
|
try {
|
|
206496
206052
|
const client = await getWebhookClient();
|
|
206497
206053
|
const { id, detail } = await resolveWebhookRef(client, idOrName);
|
|
206498
|
-
const [deliveries, dead
|
|
206054
|
+
const [deliveries, dead] = await Promise.allSettled([
|
|
206499
206055
|
client.webhooks.deliveries(id),
|
|
206500
|
-
client.webhooks.dead(id)
|
|
206501
|
-
detail.subgraphName ? client.subgraphs.status(detail.subgraphName) : Promise.resolve(null)
|
|
206056
|
+
client.webhooks.dead(id)
|
|
206502
206057
|
]);
|
|
206503
206058
|
const report = buildDoctorReport({
|
|
206504
206059
|
webhook: detail,
|
|
206505
206060
|
deliveries: deliveries.status === "fulfilled" ? deliveries.value.data : [],
|
|
206506
|
-
dead: dead.status === "fulfilled" ? dead.value.data : []
|
|
206507
|
-
subgraph: subgraph.status === "fulfilled" ? subgraph.value : null
|
|
206061
|
+
dead: dead.status === "fulfilled" ? dead.value.data : []
|
|
206508
206062
|
});
|
|
206509
206063
|
if (options.json)
|
|
206510
206064
|
printJson(report);
|
|
@@ -206519,8 +206073,7 @@ Examples:
|
|
|
206519
206073
|
const signingSecret = resolveSigningSecret(options);
|
|
206520
206074
|
const client = await getWebhookClient();
|
|
206521
206075
|
const { detail } = await resolveWebhookRef(client, idOrName);
|
|
206522
|
-
const
|
|
206523
|
-
const row = await representativeRow(client, detail, subgraph);
|
|
206076
|
+
const row = representativeRow(detail);
|
|
206524
206077
|
const fixture = buildWebhookTestFixture({
|
|
206525
206078
|
webhook: detail,
|
|
206526
206079
|
row,
|
|
@@ -206580,8 +206133,8 @@ function registerWebhooksCommand(program) {
|
|
|
206580
206133
|
}
|
|
206581
206134
|
// src/commands/devnet.ts
|
|
206582
206135
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
206583
|
-
import { existsSync as
|
|
206584
|
-
import { dirname as
|
|
206136
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
206137
|
+
import { dirname as dirname7, join as join11 } from "node:path";
|
|
206585
206138
|
|
|
206586
206139
|
// src/lib/devnet-compose.ts
|
|
206587
206140
|
var DEFAULT_IMAGE_OWNER = "ryanwaits";
|
|
@@ -206735,23 +206288,23 @@ volumes:
|
|
|
206735
206288
|
}
|
|
206736
206289
|
|
|
206737
206290
|
// src/lib/devnet-config.ts
|
|
206738
|
-
import { existsSync as
|
|
206739
|
-
import { dirname as
|
|
206291
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
206292
|
+
import { dirname as dirname6, join as join9, parse as parsePath } from "node:path";
|
|
206740
206293
|
import { parse as parseToml } from "smol-toml";
|
|
206741
206294
|
var DEFAULT_INDEXER_OBSERVER = "host.docker.internal:3700";
|
|
206742
206295
|
function findClarinetProject(startDir) {
|
|
206743
206296
|
let dir = startDir;
|
|
206744
206297
|
const { root } = parsePath(dir);
|
|
206745
206298
|
while (true) {
|
|
206746
|
-
if (
|
|
206299
|
+
if (existsSync5(join9(dir, "Clarinet.toml")))
|
|
206747
206300
|
return dir;
|
|
206748
206301
|
if (dir === root)
|
|
206749
206302
|
return null;
|
|
206750
|
-
dir =
|
|
206303
|
+
dir = dirname6(dir);
|
|
206751
206304
|
}
|
|
206752
206305
|
}
|
|
206753
206306
|
function ensureEventObserver(devnetTomlPath, endpoint = DEFAULT_INDEXER_OBSERVER) {
|
|
206754
|
-
const exists =
|
|
206307
|
+
const exists = existsSync5(devnetTomlPath);
|
|
206755
206308
|
const original = exists ? readFileSync6(devnetTomlPath, "utf8") : "";
|
|
206756
206309
|
if (original.trim()) {
|
|
206757
206310
|
const parsed = parseToml(original);
|
|
@@ -206766,7 +206319,7 @@ function ensureEventObserver(devnetTomlPath, endpoint = DEFAULT_INDEXER_OBSERVER
|
|
|
206766
206319
|
const at = (arrayStart.index ?? 0) + arrayStart[0].length;
|
|
206767
206320
|
const next = original.slice(at).trimStart().startsWith("]") ? entry : `${entry}, `;
|
|
206768
206321
|
const patched = original.slice(0, at) + next + original.slice(at);
|
|
206769
|
-
|
|
206322
|
+
writeFileSync4(devnetTomlPath, patched);
|
|
206770
206323
|
return { status: "added" };
|
|
206771
206324
|
}
|
|
206772
206325
|
const header = original.match(/^\[devnet\]\s*$/m);
|
|
@@ -206774,7 +206327,7 @@ function ensureEventObserver(devnetTomlPath, endpoint = DEFAULT_INDEXER_OBSERVER
|
|
|
206774
206327
|
const at = (header.index ?? 0) + header[0].length;
|
|
206775
206328
|
const patched = `${original.slice(0, at)}
|
|
206776
206329
|
stacks_node_events_observers = [${entry}]${original.slice(at)}`;
|
|
206777
|
-
|
|
206330
|
+
writeFileSync4(devnetTomlPath, patched);
|
|
206778
206331
|
return { status: "added" };
|
|
206779
206332
|
}
|
|
206780
206333
|
const prefix = original && !original.endsWith(`
|
|
@@ -206784,7 +206337,7 @@ stacks_node_events_observers = [${entry}]${original.slice(at)}`;
|
|
|
206784
206337
|
` : ""}[devnet]
|
|
206785
206338
|
stacks_node_events_observers = [${entry}]
|
|
206786
206339
|
`;
|
|
206787
|
-
|
|
206340
|
+
writeFileSync4(devnetTomlPath, block);
|
|
206788
206341
|
return { status: exists ? "added" : "created" };
|
|
206789
206342
|
}
|
|
206790
206343
|
|
|
@@ -206795,7 +206348,7 @@ init_resolve_auth();
|
|
|
206795
206348
|
// src/commands/faucet.ts
|
|
206796
206349
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
206797
206350
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
206798
|
-
import { join as
|
|
206351
|
+
import { join as join10 } from "node:path";
|
|
206799
206352
|
import { http as http3, createWalletClient } from "@secondlayer/stacks";
|
|
206800
206353
|
import { mnemonicToAccount } from "@secondlayer/stacks/accounts";
|
|
206801
206354
|
import { devnet } from "@secondlayer/stacks/chains";
|
|
@@ -206805,7 +206358,7 @@ init_output();
|
|
|
206805
206358
|
var DUST_LIMIT_SATS = 546n;
|
|
206806
206359
|
var DEFAULT_AMOUNT_SATS = 1000000n;
|
|
206807
206360
|
async function readDeployerMnemonic(projectDir) {
|
|
206808
|
-
const path =
|
|
206361
|
+
const path = join10(projectDir, "settings", "Devnet.toml");
|
|
206809
206362
|
let raw;
|
|
206810
206363
|
try {
|
|
206811
206364
|
raw = await readFile5(path, "utf8");
|
|
@@ -206922,7 +206475,7 @@ is what makes minting possible without Bitcoin.`).action(async (opts) => {
|
|
|
206922
206475
|
}
|
|
206923
206476
|
|
|
206924
206477
|
// src/commands/devnet.ts
|
|
206925
|
-
var COMPOSE_REL =
|
|
206478
|
+
var COMPOSE_REL = join11(".secondlayer", "docker-compose.yml");
|
|
206926
206479
|
function ensureDocker() {
|
|
206927
206480
|
const probe = spawnSync3("docker", ["info"], { stdio: "ignore" });
|
|
206928
206481
|
if (probe.error || probe.status !== 0) {
|
|
@@ -206967,7 +206520,7 @@ function registerDevnetCommand(program) {
|
|
|
206967
206520
|
}
|
|
206968
206521
|
function resolveProject(explicit) {
|
|
206969
206522
|
if (explicit) {
|
|
206970
|
-
if (!
|
|
206523
|
+
if (!existsSync6(join11(explicit, "Clarinet.toml"))) {
|
|
206971
206524
|
error(`No Clarinet.toml in ${explicit}`);
|
|
206972
206525
|
process.exit(1);
|
|
206973
206526
|
}
|
|
@@ -206983,8 +206536,8 @@ function resolveProject(explicit) {
|
|
|
206983
206536
|
}
|
|
206984
206537
|
async function connect(options) {
|
|
206985
206538
|
const project = resolveProject(options.project);
|
|
206986
|
-
const devnetToml =
|
|
206987
|
-
|
|
206539
|
+
const devnetToml = join11(project, "settings", "Devnet.toml");
|
|
206540
|
+
mkdirSync3(dirname7(devnetToml), { recursive: true });
|
|
206988
206541
|
const result = ensureEventObserver(devnetToml, DEFAULT_INDEXER_OBSERVER);
|
|
206989
206542
|
console.log(`${green("✓")} found Clarinet.toml`);
|
|
206990
206543
|
if (result.status === "present") {
|
|
@@ -206993,9 +206546,9 @@ async function connect(options) {
|
|
|
206993
206546
|
console.log(`${green("✓")} ${result.status === "created" ? "created" : "patched"} settings/Devnet.toml`);
|
|
206994
206547
|
console.log(dim(` + stacks_node_events_observers = ["${DEFAULT_INDEXER_OBSERVER}"]`));
|
|
206995
206548
|
}
|
|
206996
|
-
const composePath =
|
|
206997
|
-
|
|
206998
|
-
|
|
206549
|
+
const composePath = join11(project, COMPOSE_REL);
|
|
206550
|
+
mkdirSync3(dirname7(composePath), { recursive: true });
|
|
206551
|
+
writeFileSync5(composePath, buildDevnetCompose({ owner: options.owner, imageTag: options.imageTag }));
|
|
206999
206552
|
console.log(`${green("✓")} wrote ${COMPOSE_REL}`);
|
|
207000
206553
|
if (!options.up) {
|
|
207001
206554
|
console.log(dim(`
|
|
@@ -207026,8 +206579,8 @@ Stop with: secondlayer devnet down`));
|
|
|
207026
206579
|
}
|
|
207027
206580
|
async function down(options) {
|
|
207028
206581
|
const project = resolveProject(options.project);
|
|
207029
|
-
const composePath =
|
|
207030
|
-
if (!
|
|
206582
|
+
const composePath = join11(project, COMPOSE_REL);
|
|
206583
|
+
if (!existsSync6(composePath)) {
|
|
207031
206584
|
error(`No ${COMPOSE_REL} in ${project} — nothing to stop. Run \`secondlayer devnet connect\` first.`);
|
|
207032
206585
|
process.exit(1);
|
|
207033
206586
|
}
|
|
@@ -207041,8 +206594,8 @@ async function down(options) {
|
|
|
207041
206594
|
}
|
|
207042
206595
|
async function logs(service, options) {
|
|
207043
206596
|
const project = resolveProject(options.project);
|
|
207044
|
-
const composePath =
|
|
207045
|
-
if (!
|
|
206597
|
+
const composePath = join11(project, COMPOSE_REL);
|
|
206598
|
+
if (!existsSync6(composePath)) {
|
|
207046
206599
|
error(`No ${COMPOSE_REL} in ${project} — run \`secondlayer devnet connect\` first.`);
|
|
207047
206600
|
process.exit(1);
|
|
207048
206601
|
}
|
|
@@ -207174,7 +206727,7 @@ init_output();
|
|
|
207174
206727
|
init_resolve_auth();
|
|
207175
206728
|
import { createHash as createHash5 } from "node:crypto";
|
|
207176
206729
|
import { mkdir as mkdir3, open as open2, rename as rename2 } from "node:fs/promises";
|
|
207177
|
-
import { dirname as
|
|
206730
|
+
import { dirname as dirname8, isAbsolute, resolve as resolve7, sep as sep2 } from "node:path";
|
|
207178
206731
|
import { createStreamsClient } from "@secondlayer/sdk";
|
|
207179
206732
|
import { STREAMS_EVENT_TYPES } from "@secondlayer/shared";
|
|
207180
206733
|
var VALID_TYPES = STREAMS_EVENT_TYPES;
|
|
@@ -207197,12 +206750,12 @@ function dumpsClient(dumpsUrl) {
|
|
|
207197
206750
|
});
|
|
207198
206751
|
}
|
|
207199
206752
|
function resolveDumpDest(to, filePath) {
|
|
207200
|
-
const root =
|
|
206753
|
+
const root = resolve7(to);
|
|
207201
206754
|
const segments = filePath.split(/[\\/]/);
|
|
207202
206755
|
if (filePath.length === 0 || isAbsolute(filePath) || filePath.includes("\\") || segments.some((s) => s === "..")) {
|
|
207203
206756
|
throw new Error(`refusing dump path "${filePath}": paths must be relative and stay under ${root}`);
|
|
207204
206757
|
}
|
|
207205
|
-
const dest =
|
|
206758
|
+
const dest = resolve7(root, filePath);
|
|
207206
206759
|
if (!dest.startsWith(root + sep2)) {
|
|
207207
206760
|
throw new Error(`refusing dump path "${filePath}": resolves outside ${root}`);
|
|
207208
206761
|
}
|
|
@@ -207213,7 +206766,7 @@ async function downloadDumpTo(file, dest, fetchImpl, url) {
|
|
|
207213
206766
|
if (!res.ok || !res.body) {
|
|
207214
206767
|
throw new Error(`could not download dump ${file.path} (${res.status})`);
|
|
207215
206768
|
}
|
|
207216
|
-
await mkdir3(
|
|
206769
|
+
await mkdir3(dirname8(dest), { recursive: true });
|
|
207217
206770
|
const part = `${dest}.part`;
|
|
207218
206771
|
const hash = createHash5("sha256");
|
|
207219
206772
|
let bytes = 0;
|
|
@@ -207897,10 +207450,10 @@ init_output();
|
|
|
207897
207450
|
import { resolveApiKey as resolveApiKey4 } from "@secondlayer/sdk";
|
|
207898
207451
|
|
|
207899
207452
|
// src/lib/project-file.ts
|
|
207900
|
-
import { existsSync as
|
|
207453
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
207901
207454
|
import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
|
|
207902
207455
|
import { homedir as homedir5 } from "node:os";
|
|
207903
|
-
import { dirname as
|
|
207456
|
+
import { dirname as dirname9, join as join13, parse as parsePath2, resolve as resolve8 } from "node:path";
|
|
207904
207457
|
import { z as z4 } from "zod";
|
|
207905
207458
|
var ProjectFileSchema = z4.object({
|
|
207906
207459
|
slug: z4.string().min(1)
|
|
@@ -207908,23 +207461,23 @@ var ProjectFileSchema = z4.object({
|
|
|
207908
207461
|
var FILENAME = "project";
|
|
207909
207462
|
var DIRNAME = ".secondlayer";
|
|
207910
207463
|
async function readActiveProject(cwd, globalDefault) {
|
|
207911
|
-
let dir =
|
|
207912
|
-
const home =
|
|
207464
|
+
let dir = resolve8(cwd);
|
|
207465
|
+
const home = resolve8(homedir5());
|
|
207913
207466
|
const fsRoot = parsePath2(dir).root;
|
|
207914
207467
|
while (true) {
|
|
207915
|
-
const candidate =
|
|
207916
|
-
if (
|
|
207468
|
+
const candidate = join13(dir, DIRNAME, FILENAME);
|
|
207469
|
+
if (existsSync7(candidate)) {
|
|
207917
207470
|
try {
|
|
207918
207471
|
const raw = await readFile6(candidate, "utf8");
|
|
207919
207472
|
const parsed = ProjectFileSchema.parse(JSON.parse(raw));
|
|
207920
207473
|
return { slug: parsed.slug, resolvedFrom: candidate };
|
|
207921
207474
|
} catch {}
|
|
207922
207475
|
}
|
|
207923
|
-
if (
|
|
207476
|
+
if (existsSync7(join13(dir, ".git")))
|
|
207924
207477
|
break;
|
|
207925
207478
|
if (dir === home || dir === fsRoot)
|
|
207926
207479
|
break;
|
|
207927
|
-
const parent =
|
|
207480
|
+
const parent = dirname9(dir);
|
|
207928
207481
|
if (parent === dir)
|
|
207929
207482
|
break;
|
|
207930
207483
|
dir = parent;
|
|
@@ -207932,7 +207485,7 @@ async function readActiveProject(cwd, globalDefault) {
|
|
|
207932
207485
|
if (globalDefault) {
|
|
207933
207486
|
return {
|
|
207934
207487
|
slug: globalDefault,
|
|
207935
|
-
resolvedFrom:
|
|
207488
|
+
resolvedFrom: join13(homedir5(), ".secondlayer", "config.json")
|
|
207936
207489
|
};
|
|
207937
207490
|
}
|
|
207938
207491
|
return null;
|
|
@@ -208005,7 +207558,7 @@ init_api_url();
|
|
|
208005
207558
|
init_http();
|
|
208006
207559
|
init_output();
|
|
208007
207560
|
init_session();
|
|
208008
|
-
import { confirm as confirm3, input
|
|
207561
|
+
import { confirm as confirm3, input } from "@inquirer/prompts";
|
|
208009
207562
|
function sessionExpiry() {
|
|
208010
207563
|
return new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString();
|
|
208011
207564
|
}
|
|
@@ -208065,7 +207618,7 @@ async function runLoginFlow(options = {}) {
|
|
|
208065
207618
|
}
|
|
208066
207619
|
}
|
|
208067
207620
|
}
|
|
208068
|
-
const email = await
|
|
207621
|
+
const email = await input({
|
|
208069
207622
|
message: "Email",
|
|
208070
207623
|
validate: (v) => /^.+@.+\..+$/.test(v) ? true : "Invalid email"
|
|
208071
207624
|
});
|
|
@@ -208086,7 +207639,7 @@ async function runLoginFlow(options = {}) {
|
|
|
208086
207639
|
}
|
|
208087
207640
|
process.exit(1);
|
|
208088
207641
|
}
|
|
208089
|
-
const code = await
|
|
207642
|
+
const code = await input({
|
|
208090
207643
|
message: "Enter the 6-digit code",
|
|
208091
207644
|
validate: (v) => /^\d{6}$/.test(v) ? true : "Expected 6 digits"
|
|
208092
207645
|
});
|
|
@@ -208147,15 +207700,15 @@ function registerLogoutCommand(program) {
|
|
|
208147
207700
|
}
|
|
208148
207701
|
// src/commands/uninstall.ts
|
|
208149
207702
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
208150
|
-
import { existsSync as
|
|
208151
|
-
import { join as
|
|
207703
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
|
|
207704
|
+
import { join as join14, resolve as resolve10 } from "node:path";
|
|
208152
207705
|
import { planUninstall, uninstallCommand } from "@secondlayer/shared/runtime";
|
|
208153
207706
|
|
|
208154
207707
|
// src/lib/instance-init.ts
|
|
208155
207708
|
init_output();
|
|
208156
207709
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
208157
|
-
import { existsSync as
|
|
208158
|
-
import { resolve as
|
|
207710
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
207711
|
+
import { resolve as resolve9 } from "node:path";
|
|
208159
207712
|
import { ARCHIVE_ROOT_PUBLIC_KEY_PEM } from "@secondlayer/shared/archive/root-key";
|
|
208160
207713
|
import { generateEd25519KeyPair } from "@secondlayer/shared/crypto/ed25519";
|
|
208161
207714
|
import { INSTANCE_NETWORKS } from "@secondlayer/shared/db/queries/instance";
|
|
@@ -208204,7 +207757,7 @@ function unescapeEnvValue(raw) {
|
|
|
208204
207757
|
`).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
208205
207758
|
}
|
|
208206
207759
|
function readEnvValue(envPath, key) {
|
|
208207
|
-
if (!
|
|
207760
|
+
if (!existsSync8(envPath))
|
|
208208
207761
|
return null;
|
|
208209
207762
|
const match = readFileSync7(envPath, "utf8").match(new RegExp(`^${key}=(.*)$`, "m"));
|
|
208210
207763
|
return match ? unescapeEnvValue(match[1]) : null;
|
|
@@ -208252,12 +207805,12 @@ function renderInstanceEnv(env) {
|
|
|
208252
207805
|
`);
|
|
208253
207806
|
}
|
|
208254
207807
|
function writeInstanceEnv(cwd, env, file = INSTANCE_ENV_FILE) {
|
|
208255
|
-
const path =
|
|
208256
|
-
|
|
207808
|
+
const path = resolve9(cwd, file);
|
|
207809
|
+
writeFileSync6(path, renderInstanceEnv(env), { mode: 384 });
|
|
208257
207810
|
return path;
|
|
208258
207811
|
}
|
|
208259
207812
|
function loadExistingInstanceEnv(cwd, file = INSTANCE_ENV_FILE) {
|
|
208260
|
-
const path =
|
|
207813
|
+
const path = resolve9(cwd, file);
|
|
208261
207814
|
return {
|
|
208262
207815
|
INSTANCE_TOKEN: readEnvValue(path, "INSTANCE_TOKEN"),
|
|
208263
207816
|
SECONDLAYER_SECRETS_KEY: readEnvValue(path, "SECONDLAYER_SECRETS_KEY"),
|
|
@@ -208275,8 +207828,8 @@ init_resolve_auth();
|
|
|
208275
207828
|
var UNINSTALL_EXIT = { OK: 0, FAILED: 1, REFUSED: 2 };
|
|
208276
207829
|
var REPO_COMPOSE = "docker/oss/docker-compose.yml";
|
|
208277
207830
|
function bundleHasKeys(bundleDir) {
|
|
208278
|
-
const manifest =
|
|
208279
|
-
if (!
|
|
207831
|
+
const manifest = join14(bundleDir, "manifest.json");
|
|
207832
|
+
if (!existsSync9(manifest))
|
|
208280
207833
|
return false;
|
|
208281
207834
|
try {
|
|
208282
207835
|
const parsed = JSON.parse(readFileSync8(manifest, "utf8"));
|
|
@@ -208286,27 +207839,27 @@ function bundleHasKeys(bundleDir) {
|
|
|
208286
207839
|
}
|
|
208287
207840
|
}
|
|
208288
207841
|
function resolveUninstallLayout(dir, composeOverride) {
|
|
208289
|
-
const setupCompose =
|
|
208290
|
-
const setupEnv =
|
|
208291
|
-
const initEnv =
|
|
208292
|
-
const secretsFile = [setupEnv, initEnv].find((file) => readEnvValue(file, "SECONDLAYER_SECRETS_KEY") !== null) ?? [setupEnv, initEnv].find((file) =>
|
|
207842
|
+
const setupCompose = resolve10(dir, "docker-compose.yml");
|
|
207843
|
+
const setupEnv = resolve10(dir, ".env");
|
|
207844
|
+
const initEnv = resolve10(dir, ".env.local");
|
|
207845
|
+
const secretsFile = [setupEnv, initEnv].find((file) => readEnvValue(file, "SECONDLAYER_SECRETS_KEY") !== null) ?? [setupEnv, initEnv].find((file) => existsSync9(file));
|
|
208293
207846
|
if (composeOverride) {
|
|
208294
|
-
const composeFile =
|
|
208295
|
-
const sibling =
|
|
207847
|
+
const composeFile = resolve10(dir, composeOverride);
|
|
207848
|
+
const sibling = resolve10(composeFile, "..", ".env");
|
|
208296
207849
|
return {
|
|
208297
207850
|
composeFile,
|
|
208298
|
-
envFile:
|
|
207851
|
+
envFile: existsSync9(sibling) ? sibling : undefined,
|
|
208299
207852
|
secretsFile
|
|
208300
207853
|
};
|
|
208301
207854
|
}
|
|
208302
|
-
if (
|
|
207855
|
+
if (existsSync9(setupCompose)) {
|
|
208303
207856
|
return {
|
|
208304
207857
|
composeFile: setupCompose,
|
|
208305
|
-
envFile:
|
|
207858
|
+
envFile: existsSync9(setupEnv) ? setupEnv : undefined,
|
|
208306
207859
|
secretsFile
|
|
208307
207860
|
};
|
|
208308
207861
|
}
|
|
208309
|
-
return { composeFile:
|
|
207862
|
+
return { composeFile: resolve10(dir, REPO_COMPOSE), secretsFile };
|
|
208310
207863
|
}
|
|
208311
207864
|
function composeDownArgs(plan, layout) {
|
|
208312
207865
|
const base = uninstallCommand(plan, layout.composeFile);
|
|
@@ -208326,7 +207879,7 @@ function registerUninstallCommand(program) {
|
|
|
208326
207879
|
const dataDir = process.env.DATA_DIR ?? "./data";
|
|
208327
207880
|
const layout = resolveUninstallLayout(process.cwd(), opts.compose);
|
|
208328
207881
|
const secretsPresent = !!process.env.SECONDLAYER_SECRETS_KEY || !!layout.secretsFile;
|
|
208329
|
-
const keysBackedUp = opts.backup ? bundleHasKeys(
|
|
207882
|
+
const keysBackedUp = opts.backup ? bundleHasKeys(resolve10(opts.backup)) : false;
|
|
208330
207883
|
const decision = planUninstall({
|
|
208331
207884
|
purge: opts.purge === true,
|
|
208332
207885
|
confirmed: opts.yes === true,
|
|
@@ -208399,14 +207952,14 @@ init_fs();
|
|
|
208399
207952
|
init_output();
|
|
208400
207953
|
init_resolve_auth();
|
|
208401
207954
|
import {
|
|
208402
|
-
chmodSync
|
|
208403
|
-
existsSync as
|
|
208404
|
-
mkdirSync as
|
|
207955
|
+
chmodSync,
|
|
207956
|
+
existsSync as existsSync10,
|
|
207957
|
+
mkdirSync as mkdirSync4,
|
|
208405
207958
|
readFileSync as readFileSync9,
|
|
208406
|
-
statSync as
|
|
208407
|
-
writeFileSync as
|
|
207959
|
+
statSync as statSync2,
|
|
207960
|
+
writeFileSync as writeFileSync7
|
|
208408
207961
|
} from "node:fs";
|
|
208409
|
-
import { join as
|
|
207962
|
+
import { join as join15, resolve as resolve11 } from "node:path";
|
|
208410
207963
|
import { closeDb, getDb as getDb4, sql as sql2 } from "@secondlayer/shared/db";
|
|
208411
207964
|
import {
|
|
208412
207965
|
decryptBundle,
|
|
@@ -208555,10 +208108,10 @@ async function runBackup(opts) {
|
|
|
208555
208108
|
printError(plan.reason);
|
|
208556
208109
|
process.exit(BACKUP_EXIT.REFUSED);
|
|
208557
208110
|
}
|
|
208558
|
-
const outDir =
|
|
208559
|
-
|
|
208111
|
+
const outDir = resolve11(opts.out);
|
|
208112
|
+
mkdirSync4(outDir, { recursive: true });
|
|
208560
208113
|
console.error("dumping database…");
|
|
208561
|
-
const dumpPath =
|
|
208114
|
+
const dumpPath = join15(outDir, DB_FILE);
|
|
208562
208115
|
const dumpCmd = pgDumpInvocation(databaseUrl, dumpPath);
|
|
208563
208116
|
const dump = await runProcess(dumpCmd.cmd, "pg_dump", dumpCmd.env);
|
|
208564
208117
|
if (!dump.ok) {
|
|
@@ -208570,7 +208123,7 @@ async function runBackup(opts) {
|
|
|
208570
208123
|
db: {
|
|
208571
208124
|
file: DB_FILE,
|
|
208572
208125
|
sha256: await sha256File(dumpPath),
|
|
208573
|
-
bytes:
|
|
208126
|
+
bytes: statSync2(dumpPath).size,
|
|
208574
208127
|
format: "pg_dump-custom"
|
|
208575
208128
|
}
|
|
208576
208129
|
};
|
|
@@ -208578,16 +208131,16 @@ async function runBackup(opts) {
|
|
|
208578
208131
|
const lines = SECRET_ENV_KEYS.filter((k) => process.env[k]).map((k) => `${k}=${process.env[k]}`);
|
|
208579
208132
|
const sealed = encryptBundle(lines.join(`
|
|
208580
208133
|
`), passphrase);
|
|
208581
|
-
const secretsPath =
|
|
208582
|
-
|
|
208583
|
-
|
|
208134
|
+
const secretsPath = join15(outDir, SECRETS_FILE);
|
|
208135
|
+
writeFileSync7(secretsPath, sealed);
|
|
208136
|
+
chmodSync(secretsPath, 384);
|
|
208584
208137
|
manifest.secrets = {
|
|
208585
208138
|
file: SECRETS_FILE,
|
|
208586
208139
|
encrypted: true,
|
|
208587
208140
|
canary: sealKeyCanary(secretsKey)
|
|
208588
208141
|
};
|
|
208589
208142
|
}
|
|
208590
|
-
|
|
208143
|
+
writeFileSync7(join15(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
|
|
208591
208144
|
`);
|
|
208592
208145
|
output({
|
|
208593
208146
|
json: opts.json,
|
|
@@ -208616,14 +208169,14 @@ function attachRestoreCommand(cmd) {
|
|
|
208616
208169
|
}
|
|
208617
208170
|
async function runRestore(opts) {
|
|
208618
208171
|
const databaseUrl = requireDatabaseUrl();
|
|
208619
|
-
const dir =
|
|
208620
|
-
const manifestPath =
|
|
208621
|
-
if (!
|
|
208172
|
+
const dir = resolve11(opts.from);
|
|
208173
|
+
const manifestPath = join15(dir, MANIFEST_FILE);
|
|
208174
|
+
if (!existsSync10(manifestPath)) {
|
|
208622
208175
|
printError(`no ${MANIFEST_FILE} in ${dir}`);
|
|
208623
208176
|
process.exit(BACKUP_EXIT.REFUSED);
|
|
208624
208177
|
}
|
|
208625
208178
|
const manifest = JSON.parse(readFileSync9(manifestPath, "utf8"));
|
|
208626
|
-
const dumpPath =
|
|
208179
|
+
const dumpPath = join15(dir, manifest.db?.file ?? DB_FILE);
|
|
208627
208180
|
if (manifest.db && await sha256File(dumpPath) !== manifest.db.sha256) {
|
|
208628
208181
|
printError("the database dump does not match the digest in the manifest; this bundle is corrupt or truncated");
|
|
208629
208182
|
process.exit(BACKUP_EXIT.REFUSED);
|
|
@@ -208637,7 +208190,7 @@ async function runRestore(opts) {
|
|
|
208637
208190
|
process.exit(BACKUP_EXIT.REFUSED);
|
|
208638
208191
|
}
|
|
208639
208192
|
try {
|
|
208640
|
-
const env = decryptBundle(readFileSync9(
|
|
208193
|
+
const env = decryptBundle(readFileSync9(join15(dir, manifest.secrets.file)), passphrase);
|
|
208641
208194
|
bundleKey = env.split(`
|
|
208642
208195
|
`).find((l) => l.startsWith("SECONDLAYER_SECRETS_KEY="))?.slice("SECONDLAYER_SECRETS_KEY=".length).trim();
|
|
208643
208196
|
} catch {
|
|
@@ -208827,7 +208380,7 @@ init_resolve_auth();
|
|
|
208827
208380
|
|
|
208828
208381
|
// src/lib/setup-prompts.ts
|
|
208829
208382
|
import { resolve as resolvePath3 } from "node:path";
|
|
208830
|
-
import { confirm as confirm4, input as
|
|
208383
|
+
import { confirm as confirm4, input as input2, select as select3 } from "@inquirer/prompts";
|
|
208831
208384
|
|
|
208832
208385
|
// src/lib/oss-compose.ts
|
|
208833
208386
|
function buildOssCompose(opts = {}) {
|
|
@@ -209005,10 +208558,10 @@ dbcache=4096
|
|
|
209005
208558
|
// src/lib/setup-wizard.ts
|
|
209006
208559
|
import { spawn as spawn3, spawnSync as spawnSync5 } from "node:child_process";
|
|
209007
208560
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
209008
|
-
import { existsSync as
|
|
208561
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
209009
208562
|
import { writeFile as writeFile6 } from "node:fs/promises";
|
|
209010
208563
|
import { createServer } from "node:net";
|
|
209011
|
-
import { join as
|
|
208564
|
+
import { join as join16, resolve as resolvePath2 } from "node:path";
|
|
209012
208565
|
import { closeDb as closeDb2, getRawClientFor } from "@secondlayer/shared/db";
|
|
209013
208566
|
import { FLOORS, diskFloorGb } from "@secondlayer/shared/runtime";
|
|
209014
208567
|
init_resolve_auth();
|
|
@@ -209108,8 +208661,8 @@ function randomHex(bytes) {
|
|
|
209108
208661
|
return randomBytes4(bytes).toString("hex");
|
|
209109
208662
|
}
|
|
209110
208663
|
function resolveSecrets(config) {
|
|
209111
|
-
const envPath =
|
|
209112
|
-
const existing = config.force || !
|
|
208664
|
+
const envPath = join16(config.dir, ".env");
|
|
208665
|
+
const existing = config.force || !existsSync11(envPath) ? {} : readEnvFile(config.dir, envPath);
|
|
209113
208666
|
const instance = buildInstanceEnv({
|
|
209114
208667
|
network: config.network,
|
|
209115
208668
|
existing: {
|
|
@@ -209201,13 +208754,13 @@ function renderSetupEnv(config, secrets) {
|
|
|
209201
208754
|
`);
|
|
209202
208755
|
}
|
|
209203
208756
|
async function writeSetupFiles(config, secrets) {
|
|
209204
|
-
const composePath =
|
|
209205
|
-
const envPath =
|
|
208757
|
+
const composePath = join16(config.dir, "docker-compose.yml");
|
|
208758
|
+
const envPath = join16(config.dir, ".env");
|
|
209206
208759
|
await writeFile6(composePath, buildOssCompose({ owner: config.owner, imageTag: config.imageTag }));
|
|
209207
208760
|
await writeFile6(envPath, renderSetupEnv(config, secrets), { mode: 384 });
|
|
209208
208761
|
const result = { composePath, envPath };
|
|
209209
208762
|
if (config.nodeMode === "full") {
|
|
209210
|
-
const configTomlPath =
|
|
208763
|
+
const configTomlPath = join16(config.dir, "Config.toml");
|
|
209211
208764
|
await writeFile6(configTomlPath, buildOssStacksConfigToml({
|
|
209212
208765
|
network: config.network,
|
|
209213
208766
|
bitcoinRpcPassword: secrets.bitcoinRpcPassword ?? ""
|
|
@@ -209215,7 +208768,7 @@ async function writeSetupFiles(config, secrets) {
|
|
|
209215
208768
|
result.configTomlPath = configTomlPath;
|
|
209216
208769
|
}
|
|
209217
208770
|
if (config.nodeMode === "full") {
|
|
209218
|
-
const bitcoinConfPath =
|
|
208771
|
+
const bitcoinConfPath = join16(config.dir, "bitcoin.conf");
|
|
209219
208772
|
await writeFile6(bitcoinConfPath, buildOssBitcoinConf({ rpcPassword: secrets.bitcoinRpcPassword ?? "" }));
|
|
209220
208773
|
result.bitcoinConfPath = bitcoinConfPath;
|
|
209221
208774
|
}
|
|
@@ -209593,7 +209146,7 @@ var NODE_MODE_DESCRIPTIONS = {
|
|
|
209593
209146
|
full: "Bundled Stacks node + bitcoind."
|
|
209594
209147
|
};
|
|
209595
209148
|
async function promptSetupConfig(flags) {
|
|
209596
|
-
const network = safeParse(flags.network, parseInstanceNetwork) ?? await
|
|
209149
|
+
const network = safeParse(flags.network, parseInstanceNetwork) ?? await select3({
|
|
209597
209150
|
message: "Network",
|
|
209598
209151
|
choices: [
|
|
209599
209152
|
{
|
|
@@ -209613,7 +209166,7 @@ async function promptSetupConfig(flags) {
|
|
|
209613
209166
|
}
|
|
209614
209167
|
]
|
|
209615
209168
|
});
|
|
209616
|
-
const nodeMode = safeParse(flags.nodeMode, parseSetupNodeMode) ?? await
|
|
209169
|
+
const nodeMode = safeParse(flags.nodeMode, parseSetupNodeMode) ?? await select3({
|
|
209617
209170
|
message: "Node mode",
|
|
209618
209171
|
choices: SETUP_NODE_MODES.map((mode) => {
|
|
209619
209172
|
const floor = guardrailPreview(mode, network);
|
|
@@ -209627,7 +209180,7 @@ async function promptSetupConfig(flags) {
|
|
|
209627
209180
|
let against = flags.against;
|
|
209628
209181
|
let skipBootstrap = !!flags.skipBootstrap;
|
|
209629
209182
|
if (!skipBootstrap && !against) {
|
|
209630
|
-
const choice = await
|
|
209183
|
+
const choice = await select3({
|
|
209631
209184
|
message: "Bootstrap from",
|
|
209632
209185
|
choices: [
|
|
209633
209186
|
{
|
|
@@ -209652,7 +209205,7 @@ async function promptSetupConfig(flags) {
|
|
|
209652
209205
|
} else if (choice === "skip") {
|
|
209653
209206
|
skipBootstrap = true;
|
|
209654
209207
|
} else {
|
|
209655
|
-
against = (await
|
|
209208
|
+
against = (await input2({
|
|
209656
209209
|
message: "Manifest URL or local path",
|
|
209657
209210
|
default: DEFAULT_ARCHIVE_MANIFEST
|
|
209658
209211
|
})).trim();
|
|
@@ -209840,5 +209393,5 @@ registerCreditsCommand(program);
|
|
|
209840
209393
|
registerArchiveCommand(program);
|
|
209841
209394
|
program.parse();
|
|
209842
209395
|
|
|
209843
|
-
//# debugId=
|
|
209396
|
+
//# debugId=255DFF8A4862FC7D64756E2164756E21
|
|
209844
209397
|
//# sourceMappingURL=cli.js.map
|