@secondlayer/cli 9.10.0 → 10.0.0
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 +4 -2
- package/dist/cli.js +376 -98
- package/dist/cli.js.map +26 -22
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2481,6 +2481,25 @@ var require_commander = __commonJS((exports) => {
|
|
|
2481
2481
|
exports.InvalidOptionArgumentError = InvalidArgumentError;
|
|
2482
2482
|
});
|
|
2483
2483
|
|
|
2484
|
+
// ../../node_modules/commander/esm.mjs
|
|
2485
|
+
var import__, program, createCommand, createArgument, createOption, CommanderError, InvalidArgumentError, InvalidOptionArgumentError, Command, Argument, Option, Help;
|
|
2486
|
+
var init_esm = __esm(() => {
|
|
2487
|
+
import__ = __toESM(require_commander(), 1);
|
|
2488
|
+
({
|
|
2489
|
+
program,
|
|
2490
|
+
createCommand,
|
|
2491
|
+
createArgument,
|
|
2492
|
+
createOption,
|
|
2493
|
+
CommanderError,
|
|
2494
|
+
InvalidArgumentError,
|
|
2495
|
+
InvalidOptionArgumentError,
|
|
2496
|
+
Command,
|
|
2497
|
+
Argument,
|
|
2498
|
+
Option,
|
|
2499
|
+
Help
|
|
2500
|
+
} = import__.default);
|
|
2501
|
+
});
|
|
2502
|
+
|
|
2484
2503
|
// src/lib/session.ts
|
|
2485
2504
|
import {
|
|
2486
2505
|
chmod,
|
|
@@ -2543,6 +2562,9 @@ async function resolveAuth() {
|
|
|
2543
2562
|
if (envKey) {
|
|
2544
2563
|
return { apiUrl, ephemeralKey: envKey, fromEnv: true };
|
|
2545
2564
|
}
|
|
2565
|
+
if (isOssMode()) {
|
|
2566
|
+
return { apiUrl, ephemeralKey: "", fromEnv: true };
|
|
2567
|
+
}
|
|
2546
2568
|
const session = await readSession();
|
|
2547
2569
|
if (!session) {
|
|
2548
2570
|
const err = new Error("Not logged in — run `sl login`");
|
|
@@ -2551,6 +2573,9 @@ async function resolveAuth() {
|
|
|
2551
2573
|
}
|
|
2552
2574
|
return { apiUrl, ephemeralKey: session.token, fromEnv: false };
|
|
2553
2575
|
}
|
|
2576
|
+
function isOssMode() {
|
|
2577
|
+
return !!process.env.SL_API_URL;
|
|
2578
|
+
}
|
|
2554
2579
|
var DEFAULT_API_URL = "https://api.secondlayer.tools";
|
|
2555
2580
|
var init_resolve_auth = __esm(() => {
|
|
2556
2581
|
init_session();
|
|
@@ -2590,7 +2615,7 @@ async function httpPlatform(path2, opts = {}) {
|
|
|
2590
2615
|
try {
|
|
2591
2616
|
auth = await resolveAuth();
|
|
2592
2617
|
} catch {
|
|
2593
|
-
throw new CliHttpError(401, "SESSION_EXPIRED", { error: "Not logged in" }, "Not logged in — run `sl login`");
|
|
2618
|
+
throw new CliHttpError(401, "SESSION_EXPIRED", { error: "Not logged in" }, isOssMode() ? "Not authenticated — set SL_API_KEY from `sl init`" : "Not logged in — run `sl login`");
|
|
2594
2619
|
}
|
|
2595
2620
|
return request(`${auth.apiUrl}${path2}`, {
|
|
2596
2621
|
...opts,
|
|
@@ -2745,7 +2770,7 @@ function handleApiError(err, action) {
|
|
|
2745
2770
|
const status = err instanceof ApiError || err instanceof CliHttpError ? err.status : undefined;
|
|
2746
2771
|
if (err instanceof CliHttpError && err.code === "SESSION_EXPIRED" || status === 401) {
|
|
2747
2772
|
printError("Authentication required.", {
|
|
2748
|
-
hint: "Run `sl login` to re-authenticate."
|
|
2773
|
+
hint: isOssMode() ? "Set SL_API_KEY / INSTANCE_TOKEN from `sl init`." : "Run `sl login` to re-authenticate."
|
|
2749
2774
|
});
|
|
2750
2775
|
process.exit(1);
|
|
2751
2776
|
}
|
|
@@ -28826,7 +28851,7 @@ async function runLoginFlow(options3 = {}) {
|
|
|
28826
28851
|
}
|
|
28827
28852
|
}
|
|
28828
28853
|
function registerLoginCommand(program2) {
|
|
28829
|
-
program2.command("login").description("Log in to Secondlayer (magic-link email)").option("--force", "Skip the already-logged-in check and re-run the flow").option("--with-token", "Read an API key from stdin (non-interactive)").addHelpText("after", `
|
|
28854
|
+
program2.command("login", { hidden: true }).description("Log in to Secondlayer (magic-link email)").option("--force", "Skip the already-logged-in check and re-run the flow").option("--with-token", "Read an API key from stdin (non-interactive)").addHelpText("after", `
|
|
28830
28855
|
Examples:
|
|
28831
28856
|
$ sl login
|
|
28832
28857
|
$ echo "$SL_API_KEY" | sl login --with-token`).action((opts) => opts.withToken ? runTokenLogin() : runLoginFlow({ force: opts.force }));
|
|
@@ -28839,6 +28864,8 @@ var init_login = __esm(() => {
|
|
|
28839
28864
|
|
|
28840
28865
|
// src/lib/require-auth.ts
|
|
28841
28866
|
async function requireAuth() {
|
|
28867
|
+
if (isOssMode())
|
|
28868
|
+
return;
|
|
28842
28869
|
const session = await readSession();
|
|
28843
28870
|
if (session)
|
|
28844
28871
|
return;
|
|
@@ -28848,6 +28875,7 @@ async function requireAuth() {
|
|
|
28848
28875
|
var init_require_auth = __esm(() => {
|
|
28849
28876
|
init_login();
|
|
28850
28877
|
init_output();
|
|
28878
|
+
init_resolve_auth();
|
|
28851
28879
|
init_session();
|
|
28852
28880
|
});
|
|
28853
28881
|
|
|
@@ -30104,7 +30132,7 @@ Stopped watching.`);
|
|
|
30104
30132
|
});
|
|
30105
30133
|
await new Promise(() => {});
|
|
30106
30134
|
});
|
|
30107
|
-
subgraphs.command("deploy <file>").alias("update").description("Deploy a subgraph definition file (alias: update — deploy is create-or-update)").option("--start-block <n>", "Override the subgraph definition startBlock for this deploy").option("--tip-first", "Go live at chain tip immediately; history backfills in the background (requires order-tolerant handlers — commutative or insert-only writes)").option("--dry-run", "Validate and preview deploy without writing changes").option("-y, --yes", "Skip the reindex confirmation prompt").option("--database-url <url>", "BYO data plane: write the subgraph's schema/rows to your own Postgres. With --dry-run, prints the DDL + grant script and verifies the connection.").option("--strict", "Run `tsc --noEmit` against the handler before deploy (slower; catches TS type errors)").
|
|
30135
|
+
subgraphs.command("deploy <file>").alias("update").description("Deploy a subgraph definition file (alias: update — deploy is create-or-update)").option("--start-block <n>", "Override the subgraph definition startBlock for this deploy").option("--tip-first", "Go live at chain tip immediately; history backfills in the background (requires order-tolerant handlers — commutative or insert-only writes)").option("--dry-run", "Validate and preview deploy without writing changes").option("-y, --yes", "Skip the reindex confirmation prompt").option("--database-url <url>", "BYO data plane: write the subgraph's schema/rows to your own Postgres. With --dry-run, prints the DDL + grant script and verifies the connection.").option("--strict", "Run `tsc --noEmit` against the handler before deploy (slower; catches TS type errors)").addOption(new Option("--visibility <visibility>", "Hosted only. Public/private namespace.").hideHelp()).option("--allow-uncommitted", "Deploy even though the source file is not committed to git (the deployed definition will exist only in the database)").action(async (file, options3) => {
|
|
30108
30136
|
try {
|
|
30109
30137
|
const absPath = resolve3(file);
|
|
30110
30138
|
if (!options3.dryRun) {
|
|
@@ -30132,7 +30160,7 @@ Stopped watching.`);
|
|
|
30132
30160
|
}
|
|
30133
30161
|
}
|
|
30134
30162
|
const config = await loadConfig2();
|
|
30135
|
-
if (config.network !== "local") {
|
|
30163
|
+
if (config.network !== "local" && !isOssMode()) {
|
|
30136
30164
|
await requireAuth();
|
|
30137
30165
|
}
|
|
30138
30166
|
const dryRun = options3.dryRun;
|
|
@@ -30666,7 +30694,7 @@ ${rows.length} row(s)`));
|
|
|
30666
30694
|
handleApiError(err, "delete subgraph");
|
|
30667
30695
|
}
|
|
30668
30696
|
});
|
|
30669
|
-
subgraphs.command("publish <name>").description("Make a subgraph publicly readable on /v1/subgraphs/<name> (claims the name globally, no key needed to read)").action(async (name) => {
|
|
30697
|
+
subgraphs.command("publish <name>", { hidden: true }).description("Make a subgraph publicly readable on /v1/subgraphs/<name> (claims the name globally, no key needed to read)").action(async (name) => {
|
|
30670
30698
|
try {
|
|
30671
30699
|
await requireAuth();
|
|
30672
30700
|
const result = await publishSubgraphApi(name);
|
|
@@ -30684,7 +30712,7 @@ ${rows.length} row(s)`));
|
|
|
30684
30712
|
handleApiError(err, "publish subgraph");
|
|
30685
30713
|
}
|
|
30686
30714
|
});
|
|
30687
|
-
subgraphs.command("unpublish <name>").description("Make a subgraph private again — reads require your API key, the public name claim is released").action(async (name) => {
|
|
30715
|
+
subgraphs.command("unpublish <name>", { hidden: true }).description("Make a subgraph private again — reads require your API key, the public name claim is released").action(async (name) => {
|
|
30688
30716
|
try {
|
|
30689
30717
|
await requireAuth();
|
|
30690
30718
|
await unpublishSubgraphApi(name);
|
|
@@ -30839,6 +30867,7 @@ async function runSubgraphSchemaCodegen(file, options3, optionSource, defaultTar
|
|
|
30839
30867
|
}
|
|
30840
30868
|
var ORM_ONLY_CODEGEN_FLAGS;
|
|
30841
30869
|
var init_subgraphs2 = __esm(() => {
|
|
30870
|
+
init_esm();
|
|
30842
30871
|
init_subgraph_scaffold();
|
|
30843
30872
|
init_subgraphs();
|
|
30844
30873
|
init_api_client();
|
|
@@ -30850,6 +30879,7 @@ var init_subgraphs2 = __esm(() => {
|
|
|
30850
30879
|
init_output();
|
|
30851
30880
|
init_require_auth();
|
|
30852
30881
|
init_resolve_auth();
|
|
30882
|
+
init_resolve_auth();
|
|
30853
30883
|
init_clarity();
|
|
30854
30884
|
init_subgraph();
|
|
30855
30885
|
init_api();
|
|
@@ -34749,75 +34779,12 @@ var init_node_impl = __esm(() => {
|
|
|
34749
34779
|
init_output();
|
|
34750
34780
|
});
|
|
34751
34781
|
|
|
34752
|
-
// src/
|
|
34753
|
-
|
|
34754
|
-
__export(exports_init, {
|
|
34755
|
-
init: () => init2
|
|
34756
|
-
});
|
|
34757
|
-
import { promises as fs7 } from "node:fs";
|
|
34758
|
-
import path11 from "node:path";
|
|
34759
|
-
async function init2() {
|
|
34760
|
-
const configPath = path11.join(process.cwd(), "secondlayer.config.ts");
|
|
34761
|
-
try {
|
|
34762
|
-
await fs7.access(configPath);
|
|
34763
|
-
console.log(source_default.yellow("⚠ secondlayer.config.ts already exists"));
|
|
34764
|
-
return;
|
|
34765
|
-
} catch {}
|
|
34766
|
-
const hasClarinetProject = await fileExists2("./Clarinet.toml");
|
|
34767
|
-
let config;
|
|
34768
|
-
if (hasClarinetProject) {
|
|
34769
|
-
config = `import { defineConfig } from '@secondlayer/cli';
|
|
34770
|
-
import { clarinet } from '@secondlayer/cli/plugins';
|
|
34771
|
-
|
|
34772
|
-
export default defineConfig({
|
|
34773
|
-
out: './src/generated/contracts.ts',
|
|
34774
|
-
plugins: [
|
|
34775
|
-
clarinet() // Found Clarinet.toml in current directory
|
|
34776
|
-
]
|
|
34777
|
-
});`;
|
|
34778
|
-
} else {
|
|
34779
|
-
config = `import { defineConfig } from '@secondlayer/cli';
|
|
34780
|
-
|
|
34781
|
-
export default defineConfig({
|
|
34782
|
-
out: './src/generated/contracts.ts',
|
|
34783
|
-
plugins: [],
|
|
34784
|
-
});`;
|
|
34785
|
-
}
|
|
34786
|
-
await fs7.writeFile(configPath, config);
|
|
34787
|
-
console.log(source_default.green("✓ Created `secondlayer.config.ts`"));
|
|
34788
|
-
console.log("\nRun `secondlayer generate` to generate type-safe interfaces, functions, and hooks!");
|
|
34789
|
-
}
|
|
34790
|
-
async function fileExists2(filePath) {
|
|
34791
|
-
try {
|
|
34792
|
-
await fs7.access(filePath);
|
|
34793
|
-
return true;
|
|
34794
|
-
} catch {
|
|
34795
|
-
return false;
|
|
34796
|
-
}
|
|
34797
|
-
}
|
|
34798
|
-
var init_init = __esm(() => {
|
|
34799
|
-
init_source();
|
|
34800
|
-
});
|
|
34801
|
-
|
|
34802
|
-
// ../../node_modules/commander/esm.mjs
|
|
34803
|
-
var import__ = __toESM(require_commander(), 1);
|
|
34804
|
-
var {
|
|
34805
|
-
program,
|
|
34806
|
-
createCommand,
|
|
34807
|
-
createArgument,
|
|
34808
|
-
createOption,
|
|
34809
|
-
CommanderError,
|
|
34810
|
-
InvalidArgumentError,
|
|
34811
|
-
InvalidOptionArgumentError,
|
|
34812
|
-
Command,
|
|
34813
|
-
Argument,
|
|
34814
|
-
Option,
|
|
34815
|
-
Help
|
|
34816
|
-
} = import__.default;
|
|
34782
|
+
// src/cli.ts
|
|
34783
|
+
init_esm();
|
|
34817
34784
|
// package.json
|
|
34818
34785
|
var package_default = {
|
|
34819
34786
|
name: "@secondlayer/cli",
|
|
34820
|
-
version: "
|
|
34787
|
+
version: "10.0.0",
|
|
34821
34788
|
repository: {
|
|
34822
34789
|
type: "git",
|
|
34823
34790
|
url: "git+https://github.com/ryanwaits/secondlayer.git",
|
|
@@ -34861,7 +34828,7 @@ var package_default = {
|
|
|
34861
34828
|
"@secondlayer/bundler": "^0.4.1",
|
|
34862
34829
|
"@secondlayer/scaffold": "^1.5.1",
|
|
34863
34830
|
"@secondlayer/sdk": "^6.51.0",
|
|
34864
|
-
"@secondlayer/shared": "^7.
|
|
34831
|
+
"@secondlayer/shared": "^7.9.0",
|
|
34865
34832
|
"@secondlayer/stacks": "^3.3.1",
|
|
34866
34833
|
"@secondlayer/subgraphs": "^3.26.4",
|
|
34867
34834
|
esbuild: "^0.27.4",
|
|
@@ -35003,7 +34970,7 @@ async function applyProfileUpdate(options, getOptionValueSource) {
|
|
|
35003
34970
|
});
|
|
35004
34971
|
}
|
|
35005
34972
|
function registerAccountCommand(program2) {
|
|
35006
|
-
const account = program2.command("account").description("Manage your account profile");
|
|
34973
|
+
const account = program2.command("account", { hidden: true }).description("Manage your account profile");
|
|
35007
34974
|
addBillingCommand(account);
|
|
35008
34975
|
account.command("get").description("Show your account profile").option("--json", "Output as JSON").action(withErrorHandling(async (options) => showProfile(options.json), { action: "show profile" }));
|
|
35009
34976
|
account.command("update").description("Update your public profile").option("--name <name>", "Set display name").option("--bio <bio>", "Set bio").option("--slug <slug>", "Set public URL slug").option("--notify-reindex-complete", "Email me when a subgraph reindex finishes").option("--no-notify-reindex-complete", "Don't email me when a subgraph reindex finishes").option("--json", "Output as JSON").action(withErrorHandling(async (options, command) => applyProfileUpdate(options, (key) => command.getOptionValueSource(key)), {
|
|
@@ -36536,6 +36503,7 @@ function buildDevnetCompose(opts = {}) {
|
|
|
36536
36503
|
const indexerImage = `ghcr.io/${owner}/secondlayer-indexer:${tag}`;
|
|
36537
36504
|
const dbUrl = "postgres://secondlayer:secondlayer@postgres:5432/secondlayer";
|
|
36538
36505
|
const DEV_SECRETS_KEY = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
|
|
36506
|
+
const DEV_INSTANCE_TOKEN = "dev-instance-token";
|
|
36539
36507
|
return `# Generated by \`sl devnet connect\` — do not edit by hand.
|
|
36540
36508
|
# Secondlayer app services for local clarinet devnet development.
|
|
36541
36509
|
name: secondlayer-devnet
|
|
@@ -36572,6 +36540,8 @@ services:
|
|
|
36572
36540
|
INSTANCE_MODE: oss
|
|
36573
36541
|
DATABASE_URL: ${dbUrl}
|
|
36574
36542
|
PORT: 3800
|
|
36543
|
+
LISTEN_HOST: "0.0.0.0"
|
|
36544
|
+
INSTANCE_TOKEN: ${DEV_INSTANCE_TOKEN}
|
|
36575
36545
|
NODE_ENV: production
|
|
36576
36546
|
DATA_DIR: /data
|
|
36577
36547
|
# Shared across api + subgraph-processor so the secret the api encrypts
|
|
@@ -37317,7 +37287,7 @@ init_resolve_auth();
|
|
|
37317
37287
|
import { SecondLayer as SecondLayer4 } from "@secondlayer/sdk";
|
|
37318
37288
|
var DEFAULT_BASE_URL4 = "https://api.secondlayer.tools";
|
|
37319
37289
|
function registerKeysCommand(program2) {
|
|
37320
|
-
const keys = program2.command("keys").description("Manage API keys");
|
|
37290
|
+
const keys = program2.command("keys", { hidden: true }).description("Manage API keys");
|
|
37321
37291
|
keys.command("create").description("Mint a scoped streams/index read key (requires an account-level key)").option("--product <product>", "Key scope: streams or index", "streams").option("--name <name>", "Optional label for the key").option("--json", "Print the full response as JSON").action(async (o3) => {
|
|
37322
37292
|
if (o3.product && o3.product !== "streams" && o3.product !== "index") {
|
|
37323
37293
|
error("--product must be 'streams' or 'index'");
|
|
@@ -38607,7 +38577,7 @@ async function writeActiveProject(slug, cwd) {
|
|
|
38607
38577
|
// src/commands/whoami.ts
|
|
38608
38578
|
init_resolve_auth();
|
|
38609
38579
|
function registerWhoamiCommand(program2) {
|
|
38610
|
-
program2.command("whoami").description("Show the active account, credential source, and project").option("--json", "Output as JSON").action(async (options3) => {
|
|
38580
|
+
program2.command("whoami", { hidden: true }).description("Show the active account, credential source, and project").option("--json", "Output as JSON").action(async (options3) => {
|
|
38611
38581
|
let auth;
|
|
38612
38582
|
try {
|
|
38613
38583
|
auth = await resolveAuth();
|
|
@@ -38663,7 +38633,7 @@ init_http();
|
|
|
38663
38633
|
init_output();
|
|
38664
38634
|
init_session();
|
|
38665
38635
|
function registerLogoutCommand(program2) {
|
|
38666
|
-
program2.command("logout").description("Log out and revoke the local session").action(async () => {
|
|
38636
|
+
program2.command("logout", { hidden: true }).description("Log out and revoke the local session").action(async () => {
|
|
38667
38637
|
const session = await readSession();
|
|
38668
38638
|
if (!session) {
|
|
38669
38639
|
info("Not logged in.");
|
|
@@ -38685,7 +38655,7 @@ init_config2();
|
|
|
38685
38655
|
init_http();
|
|
38686
38656
|
init_output();
|
|
38687
38657
|
function registerProjectCommand(program2) {
|
|
38688
|
-
const project = program2.command("projects").alias("project").description("Manage your Secondlayer project");
|
|
38658
|
+
const project = program2.command("projects", { hidden: true }).alias("project").description("Manage your Secondlayer project");
|
|
38689
38659
|
project.command("list").alias("ls").description("List projects in your account").option("--json", "Output as JSON").action(async (options3) => {
|
|
38690
38660
|
try {
|
|
38691
38661
|
const res = await httpPlatform("/api/projects");
|
|
@@ -38764,6 +38734,10 @@ import {
|
|
|
38764
38734
|
compareRangeDigests,
|
|
38765
38735
|
computeRangeDigest
|
|
38766
38736
|
} from "@secondlayer/shared/archive/range-digest";
|
|
38737
|
+
import {
|
|
38738
|
+
comparePartitionSemanticDigests,
|
|
38739
|
+
computePartitionSemanticDigest
|
|
38740
|
+
} from "@secondlayer/shared/archive/semantic-digest-builder";
|
|
38767
38741
|
import { getDb as getDb2 } from "@secondlayer/shared/db";
|
|
38768
38742
|
|
|
38769
38743
|
// src/lib/archive-reference.ts
|
|
@@ -38840,6 +38814,7 @@ function checkSignature(manifest, publicKeyPem, insecure) {
|
|
|
38840
38814
|
};
|
|
38841
38815
|
}
|
|
38842
38816
|
}
|
|
38817
|
+
var HOSTED_API = "https://api.secondlayer.tools";
|
|
38843
38818
|
async function resolvePublicKey(explicitPem, apiUrl) {
|
|
38844
38819
|
if (explicitPem)
|
|
38845
38820
|
return explicitPem;
|
|
@@ -38853,6 +38828,16 @@ async function resolvePublicKey(explicitPem, apiUrl) {
|
|
|
38853
38828
|
return;
|
|
38854
38829
|
}
|
|
38855
38830
|
}
|
|
38831
|
+
async function resolveArchivePublicKey(input4) {
|
|
38832
|
+
if (input4.explicitPem)
|
|
38833
|
+
return input4.explicitPem;
|
|
38834
|
+
if (input4.envPem)
|
|
38835
|
+
return input4.envPem;
|
|
38836
|
+
if (input4.allowHostedApi) {
|
|
38837
|
+
return resolvePublicKey(undefined, HOSTED_API);
|
|
38838
|
+
}
|
|
38839
|
+
return;
|
|
38840
|
+
}
|
|
38856
38841
|
async function fetchVerifiedPartition(reference, partition) {
|
|
38857
38842
|
let bytes;
|
|
38858
38843
|
if (reference.isRemote) {
|
|
@@ -38896,7 +38881,7 @@ function statusLabel(status2) {
|
|
|
38896
38881
|
return yellow("missing");
|
|
38897
38882
|
}
|
|
38898
38883
|
function registerVerifyCommand(program2) {
|
|
38899
|
-
program2.command("verify").description("Compare local chain data against a signed archive (read-only; nothing is uploaded)").requiredOption("--against <manifest>", "archive manifest: an https URL or a local file path").option("--from-block <n>", "first height to check").option("--to-block <n>", "last height to check").option("--counts", "also compare transaction/event row counts (slower)").option("--public-key <pem>", "pin the signing key instead of fetching it").option("--insecure", "skip the manifest signature check (not recommended)").option("--json", "Output as JSON").addHelpText("after", `
|
|
38884
|
+
program2.command("verify").description("Compare local chain data against a signed archive (read-only; nothing is uploaded)").requiredOption("--against <manifest>", "archive manifest: an https URL or a local file path").option("--from-block <n>", "first height to check").option("--to-block <n>", "last height to check").option("--counts", "also compare transaction/event row counts (slower)").option("--semantic", "also recompute per-partition semantic digests locally and compare (slow: full re-stream)").option("--public-key <pem>", "pin the signing key instead of fetching it").option("--insecure", "skip the manifest signature check (not recommended)").option("--json", "Output as JSON").addHelpText("after", `
|
|
38900
38885
|
Examples:
|
|
38901
38886
|
$ sl verify --against https://archive.secondlayer.tools/.../snapshots/<digest>.json
|
|
38902
38887
|
$ sl verify --against ./snapshot.json --from-block 8000000 --to-block 8499999
|
|
@@ -38976,13 +38961,52 @@ Exit codes:
|
|
|
38976
38961
|
});
|
|
38977
38962
|
}
|
|
38978
38963
|
}
|
|
38979
|
-
const
|
|
38964
|
+
const semanticChecks = [];
|
|
38965
|
+
const referenceSemantic = (manifest.partition_semantic_digests ?? []).filter((d2) => {
|
|
38966
|
+
if (fromBlock !== undefined && d2.to_block < fromBlock)
|
|
38967
|
+
return false;
|
|
38968
|
+
if (toBlock !== undefined && d2.from_block > toBlock)
|
|
38969
|
+
return false;
|
|
38970
|
+
return true;
|
|
38971
|
+
});
|
|
38972
|
+
if (opts.semantic) {
|
|
38973
|
+
if (referenceSemantic.length === 0) {
|
|
38974
|
+
warn("Reference publishes no semantic digests; --semantic had nothing to compare.");
|
|
38975
|
+
} else {
|
|
38976
|
+
note(`Recomputing ${referenceSemantic.length} partition semantic digests (this is the slow pass).`);
|
|
38977
|
+
const localSemantic = [];
|
|
38978
|
+
let semanticDone = 0;
|
|
38979
|
+
for (const partition of referenceSemantic) {
|
|
38980
|
+
localSemantic.push(await computePartitionSemanticDigest(db, partition.dataset, partition.from_block, partition.to_block));
|
|
38981
|
+
semanticDone++;
|
|
38982
|
+
if (semanticDone % 5 === 0 || semanticDone === referenceSemantic.length) {
|
|
38983
|
+
note(` checked ${semanticDone}/${referenceSemantic.length} partitions`);
|
|
38984
|
+
}
|
|
38985
|
+
}
|
|
38986
|
+
semanticChecks.push(...comparePartitionSemanticDigests(localSemantic, referenceSemantic));
|
|
38987
|
+
}
|
|
38988
|
+
}
|
|
38989
|
+
const all2 = [
|
|
38990
|
+
...comparisons,
|
|
38991
|
+
...countChecks,
|
|
38992
|
+
...semanticChecks.map((s2) => ({
|
|
38993
|
+
dataset: s2.dataset,
|
|
38994
|
+
from_block: s2.from_block,
|
|
38995
|
+
to_block: s2.to_block,
|
|
38996
|
+
status: s2.status,
|
|
38997
|
+
expected_digest: s2.expected_digest,
|
|
38998
|
+
actual_digest: s2.actual_digest,
|
|
38999
|
+
expected_rows: s2.expected_rows,
|
|
39000
|
+
actual_rows: s2.actual_rows
|
|
39001
|
+
}))
|
|
39002
|
+
];
|
|
38980
39003
|
const diverged = all2.filter((c4) => c4.status !== "match");
|
|
38981
39004
|
const report = {
|
|
38982
39005
|
status: diverged.length === 0 ? "clean" : "diverged",
|
|
38983
39006
|
reference: origin,
|
|
38984
39007
|
signature_verified: signature.verified,
|
|
38985
39008
|
ranges_checked: all2.length,
|
|
39009
|
+
semantic_checks: semanticChecks.length,
|
|
38986
39010
|
divergent_ranges: diverged
|
|
38987
39011
|
};
|
|
38988
39012
|
output({
|
|
@@ -39281,7 +39305,35 @@ import {
|
|
|
39281
39305
|
} from "@secondlayer/shared/archive/copy-loader";
|
|
39282
39306
|
import { computeRangeDigest as computeRangeDigest3 } from "@secondlayer/shared/archive/range-digest";
|
|
39283
39307
|
import { getDb as getDb4, getRawClient, sql as sql3 } from "@secondlayer/shared/db";
|
|
39308
|
+
|
|
39309
|
+
// src/lib/bootstrap-resume.ts
|
|
39310
|
+
function planTornImport(input4) {
|
|
39311
|
+
if (input4.hasIndexProgress) {
|
|
39312
|
+
return {
|
|
39313
|
+
action: "refuse",
|
|
39314
|
+
reason: "This database already holds a completed bootstrap."
|
|
39315
|
+
};
|
|
39316
|
+
}
|
|
39317
|
+
if (input4.maxBlockHeight === null || input4.maxBlockHeight <= 0) {
|
|
39318
|
+
return { action: "fresh" };
|
|
39319
|
+
}
|
|
39320
|
+
const blockPartitions = input4.partitions.filter((p2) => p2.dataset === "blocks").sort((a4, b3) => a4.from_block - b3.from_block);
|
|
39321
|
+
const sealed = blockPartitions.filter((p2) => p2.to_block <= (input4.maxBlockHeight ?? 0));
|
|
39322
|
+
const skipThrough = sealed.length > 0 ? Math.max(...sealed.map((p2) => p2.to_block)) : 0;
|
|
39323
|
+
const torn = blockPartitions.find((p2) => p2.from_block <= (input4.maxBlockHeight ?? 0) && p2.to_block > (input4.maxBlockHeight ?? 0));
|
|
39324
|
+
return {
|
|
39325
|
+
action: "resume",
|
|
39326
|
+
truncateFrom: torn ? torn.from_block : null,
|
|
39327
|
+
skipThrough
|
|
39328
|
+
};
|
|
39329
|
+
}
|
|
39330
|
+
function partitionIsLoaded(partition, skipThrough) {
|
|
39331
|
+
return partition.to_block <= skipThrough;
|
|
39332
|
+
}
|
|
39333
|
+
|
|
39334
|
+
// src/commands/bootstrap.ts
|
|
39284
39335
|
init_output();
|
|
39336
|
+
init_resolve_auth();
|
|
39285
39337
|
var BOOTSTRAP_EXIT = {
|
|
39286
39338
|
OK: 0,
|
|
39287
39339
|
INCOMPLETE: 1,
|
|
@@ -39342,8 +39394,8 @@ async function loadPartition(reference, partition, rawClient) {
|
|
|
39342
39394
|
}
|
|
39343
39395
|
return written;
|
|
39344
39396
|
}
|
|
39345
|
-
function
|
|
39346
|
-
|
|
39397
|
+
function attachBootstrapCommand(cmd) {
|
|
39398
|
+
return cmd.requiredOption("--against <manifest>", "archive manifest: an https URL or a local file path").option("--to-block <n>", "stop at this height instead of the archive tip").option("--public-key <pem>", "pin the signing key instead of fetching it").option("-y, --yes", "skip the confirmation prompt").option("--json", "Output as JSON").addHelpText("after", `
|
|
39347
39399
|
Examples:
|
|
39348
39400
|
$ sl bootstrap --against https://archive.secondlayer.tools/.../snapshots/<digest>.json
|
|
39349
39401
|
$ sl bootstrap --against ./snapshot.json --to-block 4000000 --yes
|
|
@@ -39354,7 +39406,11 @@ Exit codes:
|
|
|
39354
39406
|
2 refused (non-empty target, or an untrusted reference)`).action(async (opts) => {
|
|
39355
39407
|
try {
|
|
39356
39408
|
const reference = await loadReference(opts.against);
|
|
39357
|
-
const publicKey = await
|
|
39409
|
+
const publicKey = await resolveArchivePublicKey({
|
|
39410
|
+
explicitPem: opts.publicKey,
|
|
39411
|
+
envPem: process.env.ARCHIVE_SIGNING_PUBLIC_KEY ?? process.env.STREAMS_SIGNING_PUBLIC_KEY,
|
|
39412
|
+
allowHostedApi: !isOssMode()
|
|
39413
|
+
});
|
|
39358
39414
|
const signature = checkSignature(reference.manifest, publicKey, false);
|
|
39359
39415
|
if (!signature.verified) {
|
|
39360
39416
|
printError(`Refusing to bootstrap from an untrusted archive: ${signature.reason}.`, {
|
|
@@ -39363,15 +39419,31 @@ Exit codes:
|
|
|
39363
39419
|
process.exit(BOOTSTRAP_EXIT.REFUSED);
|
|
39364
39420
|
}
|
|
39365
39421
|
const db = getDb4();
|
|
39366
|
-
const
|
|
39367
|
-
|
|
39368
|
-
|
|
39422
|
+
const progress = await db.selectFrom("index_progress").select("network").executeTakeFirst();
|
|
39423
|
+
const maxHeightRow = await db.selectFrom("blocks").select(({ fn }) => fn.max("height").as("max")).executeTakeFirst();
|
|
39424
|
+
const maxBlockHeight = maxHeightRow?.max === null || maxHeightRow?.max === undefined ? null : Number(maxHeightRow.max);
|
|
39425
|
+
const toBlock = opts.toBlock === undefined ? undefined : Number(opts.toBlock);
|
|
39426
|
+
const declared = (reference.manifest.partitions ?? []).filter((p2) => toBlock === undefined || p2.to_block <= toBlock);
|
|
39427
|
+
const resume = planTornImport({
|
|
39428
|
+
hasIndexProgress: !!progress,
|
|
39429
|
+
maxBlockHeight,
|
|
39430
|
+
partitions: declared
|
|
39431
|
+
});
|
|
39432
|
+
if (resume.action === "refuse") {
|
|
39433
|
+
printError(resume.reason, {
|
|
39369
39434
|
hint: "Bootstrap is for a fresh instance. To fix an existing one, use `sl repair`."
|
|
39370
39435
|
});
|
|
39371
39436
|
process.exit(BOOTSTRAP_EXIT.REFUSED);
|
|
39372
39437
|
}
|
|
39373
|
-
|
|
39374
|
-
|
|
39438
|
+
if (resume.action === "resume" && resume.truncateFrom !== null) {
|
|
39439
|
+
await sql3`DELETE FROM events WHERE block_height >= ${resume.truncateFrom}`.execute(db);
|
|
39440
|
+
await sql3`DELETE FROM transactions WHERE block_height >= ${resume.truncateFrom}`.execute(db);
|
|
39441
|
+
await sql3`DELETE FROM blocks WHERE height >= ${resume.truncateFrom}`.execute(db);
|
|
39442
|
+
note(`Resuming torn import: truncated from height ${resume.truncateFrom}.`);
|
|
39443
|
+
} else if (resume.action === "resume") {
|
|
39444
|
+
note(`Resuming import after sealed height ${resume.skipThrough.toLocaleString()}.`);
|
|
39445
|
+
}
|
|
39446
|
+
const partitions = declared.filter((p2) => resume.action === "fresh" || !partitionIsLoaded(p2, resume.skipThrough));
|
|
39375
39447
|
if (partitions.length === 0) {
|
|
39376
39448
|
printError("The archive has no partitions for that range.", {
|
|
39377
39449
|
hint: "Check --to-block against the manifest's coverage."
|
|
@@ -39496,9 +39568,217 @@ Exit codes:
|
|
|
39496
39568
|
}
|
|
39497
39569
|
});
|
|
39498
39570
|
}
|
|
39571
|
+
function registerBootstrapCommand(program2) {
|
|
39572
|
+
attachBootstrapCommand(program2.command("bootstrap").description("Restore chain history from a verified archive instead of syncing from genesis"));
|
|
39573
|
+
}
|
|
39574
|
+
// src/lib/instance-init.ts
|
|
39575
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
39576
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
39577
|
+
import { resolve as resolve13 } from "node:path";
|
|
39578
|
+
import { generateEd25519KeyPair } from "@secondlayer/shared/crypto/ed25519";
|
|
39579
|
+
import { INSTANCE_NETWORKS } from "@secondlayer/shared/db/queries/instance";
|
|
39580
|
+
var INSTANCE_ENV_FILE = ".env.local";
|
|
39581
|
+
function parseInstanceNetwork(value) {
|
|
39582
|
+
const network = value.trim().toLowerCase();
|
|
39583
|
+
if (INSTANCE_NETWORKS.includes(network)) {
|
|
39584
|
+
return network;
|
|
39585
|
+
}
|
|
39586
|
+
throw new Error(`network must be mainnet, testnet, or devnet (got ${value})`);
|
|
39587
|
+
}
|
|
39588
|
+
function generateInstanceToken() {
|
|
39589
|
+
return randomBytes3(32).toString("hex");
|
|
39590
|
+
}
|
|
39591
|
+
function generateSecretsKey() {
|
|
39592
|
+
return randomBytes3(32).toString("hex");
|
|
39593
|
+
}
|
|
39594
|
+
function generateSigningPrivateKey() {
|
|
39595
|
+
return generateEd25519KeyPair().privateKeyPem;
|
|
39596
|
+
}
|
|
39597
|
+
function escapeEnvValue(value) {
|
|
39598
|
+
if (!/[\s#"']/.test(value) && !value.includes("\\"))
|
|
39599
|
+
return value;
|
|
39600
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n")}"`;
|
|
39601
|
+
}
|
|
39602
|
+
function unescapeEnvValue(raw) {
|
|
39603
|
+
let value = raw.trim();
|
|
39604
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
39605
|
+
value = value.slice(1, -1);
|
|
39606
|
+
}
|
|
39607
|
+
return value.replace(/\\n/g, `
|
|
39608
|
+
`).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
39609
|
+
}
|
|
39610
|
+
function readEnvValue(envPath, key) {
|
|
39611
|
+
if (!existsSync8(envPath))
|
|
39612
|
+
return null;
|
|
39613
|
+
const match = readFileSync7(envPath, "utf8").match(new RegExp(`^${key}=(.*)$`, "m"));
|
|
39614
|
+
return match ? unescapeEnvValue(match[1]) : null;
|
|
39615
|
+
}
|
|
39616
|
+
function buildInstanceEnv(input4) {
|
|
39617
|
+
const token = input4.existing?.INSTANCE_TOKEN || input4.existing?.SL_API_KEY || generateInstanceToken();
|
|
39618
|
+
const secrets = input4.existing?.SECONDLAYER_SECRETS_KEY || generateSecretsKey();
|
|
39619
|
+
const signing = input4.existing?.STREAMS_SIGNING_PRIVATE_KEY || input4.existing?.SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY || generateSigningPrivateKey();
|
|
39620
|
+
const apiUrl = input4.apiUrl ?? "http://127.0.0.1:3800";
|
|
39621
|
+
const archiveKey = input4.existing?.ARCHIVE_SIGNING_PUBLIC_KEY;
|
|
39622
|
+
return {
|
|
39623
|
+
INSTANCE_MODE: "oss",
|
|
39624
|
+
STACKS_NETWORK: input4.network,
|
|
39625
|
+
INSTANCE_TOKEN: token,
|
|
39626
|
+
SECONDLAYER_SECRETS_KEY: secrets,
|
|
39627
|
+
STREAMS_SIGNING_PRIVATE_KEY: signing,
|
|
39628
|
+
SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY: signing,
|
|
39629
|
+
ALLOW_UNSIGNED_WEBHOOKS: "false",
|
|
39630
|
+
SL_API_URL: input4.existing?.SL_API_URL || apiUrl,
|
|
39631
|
+
SL_API_KEY: token,
|
|
39632
|
+
...archiveKey ? { ARCHIVE_SIGNING_PUBLIC_KEY: archiveKey } : {}
|
|
39633
|
+
};
|
|
39634
|
+
}
|
|
39635
|
+
function renderInstanceEnv(env2) {
|
|
39636
|
+
const lines2 = [
|
|
39637
|
+
"# Generated by sl init. Do not commit.",
|
|
39638
|
+
`INSTANCE_MODE=${env2.INSTANCE_MODE}`,
|
|
39639
|
+
`STACKS_NETWORK=${env2.STACKS_NETWORK}`,
|
|
39640
|
+
`INSTANCE_TOKEN=${env2.INSTANCE_TOKEN}`,
|
|
39641
|
+
`SECONDLAYER_SECRETS_KEY=${env2.SECONDLAYER_SECRETS_KEY}`,
|
|
39642
|
+
`STREAMS_SIGNING_PRIVATE_KEY=${escapeEnvValue(env2.STREAMS_SIGNING_PRIVATE_KEY)}`,
|
|
39643
|
+
`SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY=${escapeEnvValue(env2.SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY)}`,
|
|
39644
|
+
`ALLOW_UNSIGNED_WEBHOOKS=${env2.ALLOW_UNSIGNED_WEBHOOKS}`,
|
|
39645
|
+
`SL_API_URL=${env2.SL_API_URL}`,
|
|
39646
|
+
`SL_API_KEY=${env2.SL_API_KEY}`
|
|
39647
|
+
];
|
|
39648
|
+
if (env2.ARCHIVE_SIGNING_PUBLIC_KEY) {
|
|
39649
|
+
lines2.push(`ARCHIVE_SIGNING_PUBLIC_KEY=${escapeEnvValue(env2.ARCHIVE_SIGNING_PUBLIC_KEY)}`);
|
|
39650
|
+
}
|
|
39651
|
+
lines2.push("");
|
|
39652
|
+
return lines2.join(`
|
|
39653
|
+
`);
|
|
39654
|
+
}
|
|
39655
|
+
function writeInstanceEnv(cwd, env2, file = INSTANCE_ENV_FILE) {
|
|
39656
|
+
const path11 = resolve13(cwd, file);
|
|
39657
|
+
writeFileSync7(path11, renderInstanceEnv(env2), { mode: 384 });
|
|
39658
|
+
return path11;
|
|
39659
|
+
}
|
|
39660
|
+
function loadExistingInstanceEnv(cwd, file = INSTANCE_ENV_FILE) {
|
|
39661
|
+
const path11 = resolve13(cwd, file);
|
|
39662
|
+
return {
|
|
39663
|
+
INSTANCE_TOKEN: readEnvValue(path11, "INSTANCE_TOKEN"),
|
|
39664
|
+
SECONDLAYER_SECRETS_KEY: readEnvValue(path11, "SECONDLAYER_SECRETS_KEY"),
|
|
39665
|
+
STREAMS_SIGNING_PRIVATE_KEY: readEnvValue(path11, "STREAMS_SIGNING_PRIVATE_KEY"),
|
|
39666
|
+
SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY: readEnvValue(path11, "SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY"),
|
|
39667
|
+
SL_API_URL: readEnvValue(path11, "SL_API_URL"),
|
|
39668
|
+
SL_API_KEY: readEnvValue(path11, "SL_API_KEY"),
|
|
39669
|
+
ARCHIVE_SIGNING_PUBLIC_KEY: readEnvValue(path11, "ARCHIVE_SIGNING_PUBLIC_KEY")
|
|
39670
|
+
};
|
|
39671
|
+
}
|
|
39672
|
+
|
|
39673
|
+
// src/commands/init.ts
|
|
39674
|
+
init_output();
|
|
39675
|
+
function registerInitCommand(program2) {
|
|
39676
|
+
program2.command("init").description("Write local env (token, secrets key, webhook signing key). Idempotent.").option("--network <network>", "mainnet, testnet, or devnet", process.env.STACKS_NETWORK ?? "mainnet").option("--api-url <url>", "Local API URL", "http://127.0.0.1:3800").option("--force", "Overwrite generated values even if .env.local exists").action((opts) => {
|
|
39677
|
+
const network = parseInstanceNetwork(opts.network);
|
|
39678
|
+
const existing = opts.force ? {} : loadExistingInstanceEnv(process.cwd());
|
|
39679
|
+
const env2 = buildInstanceEnv({
|
|
39680
|
+
network,
|
|
39681
|
+
existing,
|
|
39682
|
+
apiUrl: opts.apiUrl
|
|
39683
|
+
});
|
|
39684
|
+
const path11 = writeInstanceEnv(process.cwd(), env2);
|
|
39685
|
+
success(`Wrote ${INSTANCE_ENV_FILE}`);
|
|
39686
|
+
note(formatKeyValue([
|
|
39687
|
+
["network", env2.STACKS_NETWORK],
|
|
39688
|
+
["api", env2.SL_API_URL],
|
|
39689
|
+
["env", path11]
|
|
39690
|
+
]));
|
|
39691
|
+
note("Export these or pass --env-file to compose. Do not commit the file.");
|
|
39692
|
+
});
|
|
39693
|
+
}
|
|
39694
|
+
// src/lib/observer-stanza.ts
|
|
39695
|
+
var OBSERVER_MODES = ["indexer", "signer-shared"];
|
|
39696
|
+
var RECOVERY_SOURCES = ["journal", "archive"];
|
|
39697
|
+
|
|
39698
|
+
class UnsupportedObserverError extends Error {
|
|
39699
|
+
name = "UnsupportedObserverError";
|
|
39700
|
+
}
|
|
39701
|
+
function parseObserverMode(value) {
|
|
39702
|
+
const mode = value.trim().toLowerCase();
|
|
39703
|
+
if (OBSERVER_MODES.includes(mode)) {
|
|
39704
|
+
return mode;
|
|
39705
|
+
}
|
|
39706
|
+
throw new UnsupportedObserverError(`observer mode must be indexer or signer-shared (got ${value})`);
|
|
39707
|
+
}
|
|
39708
|
+
function parseRecoverySource(value) {
|
|
39709
|
+
const source = value.trim().toLowerCase();
|
|
39710
|
+
if (RECOVERY_SOURCES.includes(source)) {
|
|
39711
|
+
return source;
|
|
39712
|
+
}
|
|
39713
|
+
throw new UnsupportedObserverError(`recovery source must be journal or archive (got ${value})`);
|
|
39714
|
+
}
|
|
39715
|
+
function parseObserverEndpoint(value) {
|
|
39716
|
+
const trimmed = value.trim();
|
|
39717
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) || trimmed.startsWith("unix:")) {
|
|
39718
|
+
throw new UnsupportedObserverError(`observer endpoint must be host:port, not a URL (${value})`);
|
|
39719
|
+
}
|
|
39720
|
+
const match = trimmed.match(/^([a-zA-Z0-9._-]+):(\d+)$/);
|
|
39721
|
+
if (!match) {
|
|
39722
|
+
throw new UnsupportedObserverError(`observer endpoint must be host:port (got ${value})`);
|
|
39723
|
+
}
|
|
39724
|
+
const port = Number(match[2]);
|
|
39725
|
+
if (port < 1 || port > 65535) {
|
|
39726
|
+
throw new UnsupportedObserverError(`observer port out of range (${port})`);
|
|
39727
|
+
}
|
|
39728
|
+
return trimmed;
|
|
39729
|
+
}
|
|
39730
|
+
var LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
|
|
39731
|
+
function validateObserverStanza(input4) {
|
|
39732
|
+
const endpoint = parseObserverEndpoint(input4.endpoint);
|
|
39733
|
+
const host = endpoint.slice(0, endpoint.lastIndexOf(":"));
|
|
39734
|
+
if (input4.network !== "devnet" && LOOPBACK_HOSTS.has(host.toLowerCase())) {
|
|
39735
|
+
throw new UnsupportedObserverError(`loopback observer endpoint is not container-visible on ${input4.network}; use a docker DNS name such as indexer:3700`);
|
|
39736
|
+
}
|
|
39737
|
+
if (input4.mode === "signer-shared" && !input4.recovery) {
|
|
39738
|
+
throw new UnsupportedObserverError("signer-shared mode requires --recovery journal or --recovery archive");
|
|
39739
|
+
}
|
|
39740
|
+
return { endpoint };
|
|
39741
|
+
}
|
|
39742
|
+
function renderObserverStanza(input4) {
|
|
39743
|
+
const { endpoint } = validateObserverStanza(input4);
|
|
39744
|
+
const indexer = input4.mode === "indexer";
|
|
39745
|
+
const timeoutMs = indexer ? 2000 : 500;
|
|
39746
|
+
const disableRetries = !indexer;
|
|
39747
|
+
const comment = indexer ? "# Pure indexer: retry delivery. A slow observer can stall the node." : "# Signer-shared: do not retry. Missed blocks refill from the journal/archive.";
|
|
39748
|
+
return [
|
|
39749
|
+
comment,
|
|
39750
|
+
"[[events_observer]]",
|
|
39751
|
+
`endpoint = "${endpoint}"`,
|
|
39752
|
+
'events_keys = ["*"]',
|
|
39753
|
+
`timeout_ms = ${timeoutMs}`,
|
|
39754
|
+
`disable_retries = ${disableRetries}`,
|
|
39755
|
+
""
|
|
39756
|
+
].join(`
|
|
39757
|
+
`);
|
|
39758
|
+
}
|
|
39759
|
+
function defaultObserverEndpoint(network) {
|
|
39760
|
+
if (network === "devnet")
|
|
39761
|
+
return "127.0.0.1:3700";
|
|
39762
|
+
return "indexer:3700";
|
|
39763
|
+
}
|
|
39764
|
+
|
|
39765
|
+
// src/commands/observer.ts
|
|
39766
|
+
init_output();
|
|
39767
|
+
function registerObserverCommand(program2) {
|
|
39768
|
+
program2.command("observer").description("Print the Stacks events_observer stanza for this node mode").option("--mode <mode>", "indexer (retry) or signer-shared (no retry)", "indexer").option("--endpoint <host:port>", "Observer callback the node will POST to").option("--recovery <source>", "Required for signer-shared: journal or archive").option("--network <network>", "mainnet, testnet, or devnet", process.env.STACKS_NETWORK ?? "mainnet").action((opts) => {
|
|
39769
|
+
const mode = parseObserverMode(opts.mode);
|
|
39770
|
+
const network = parseInstanceNetwork(opts.network);
|
|
39771
|
+
const endpoint = opts.endpoint ?? defaultObserverEndpoint(network);
|
|
39772
|
+
const recovery = opts.recovery ? parseRecoverySource(opts.recovery) : undefined;
|
|
39773
|
+
if (mode === "signer-shared") {
|
|
39774
|
+
note("Signer-shared nodes skip observer retries. Pair with a journal or archive for completeness.");
|
|
39775
|
+
}
|
|
39776
|
+
writeData(renderObserverStanza({ mode, endpoint, network, recovery }));
|
|
39777
|
+
});
|
|
39778
|
+
}
|
|
39499
39779
|
// src/cli.ts
|
|
39500
39780
|
var { version } = package_default;
|
|
39501
|
-
program.name("secondlayer").alias("sl").description("
|
|
39781
|
+
program.name("secondlayer").alias("sl").description("Secondlayer CLI — self-hosted Stacks data runtime").version(version).option("--network <network>", "Override network (local, testnet, mainnet)").option("--api-key <key>", "API credential (overrides SL_API_KEY)").option("--api-url <url>", "API endpoint (overrides SL_API_URL)").showSuggestionAfterError(true).showHelpAfterError("(run `sl --help` to see available commands)");
|
|
39502
39782
|
program.hook("preAction", (thisCommand) => {
|
|
39503
39783
|
const { network, apiKey, apiUrl } = thisCommand.opts();
|
|
39504
39784
|
if (network)
|
|
@@ -39510,16 +39790,15 @@ program.hook("preAction", (thisCommand) => {
|
|
|
39510
39790
|
});
|
|
39511
39791
|
program.addHelpText("after", `
|
|
39512
39792
|
Quickstart:
|
|
39513
|
-
$ sl
|
|
39793
|
+
$ sl init --network mainnet
|
|
39794
|
+
$ sl bootstrap --against <manifest>
|
|
39514
39795
|
$ sl subgraphs create my-watcher --template sip-010-balances
|
|
39515
39796
|
$ sl subgraphs deploy subgraphs/my-watcher.ts
|
|
39516
|
-
$ sl subgraphs status my-watcher
|
|
39517
39797
|
`);
|
|
39518
39798
|
program.commandsGroup("Getting started:");
|
|
39519
|
-
program
|
|
39520
|
-
|
|
39521
|
-
|
|
39522
|
-
});
|
|
39799
|
+
registerInitCommand(program);
|
|
39800
|
+
registerBootstrapCommand(program);
|
|
39801
|
+
registerObserverCommand(program);
|
|
39523
39802
|
registerLoginCommand(program);
|
|
39524
39803
|
registerLogoutCommand(program);
|
|
39525
39804
|
registerWhoamiCommand(program);
|
|
@@ -39552,7 +39831,6 @@ registerStatusCommand(program);
|
|
|
39552
39831
|
registerDoctorCommand(program);
|
|
39553
39832
|
registerConfigCommand(program);
|
|
39554
39833
|
registerContextCommand(program);
|
|
39555
|
-
registerBootstrapCommand(program);
|
|
39556
39834
|
registerVerifyCommand(program);
|
|
39557
39835
|
registerRepairCommand(program);
|
|
39558
39836
|
program.commandsGroup("Account:");
|
|
@@ -39560,5 +39838,5 @@ registerAccountCommand(program);
|
|
|
39560
39838
|
registerKeysCommand(program);
|
|
39561
39839
|
program.parse();
|
|
39562
39840
|
|
|
39563
|
-
//# debugId=
|
|
39841
|
+
//# debugId=8F8395688EA7983464756E2164756E21
|
|
39564
39842
|
//# sourceMappingURL=cli.js.map
|