@secondlayer/cli 14.1.1 → 14.1.2
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/dist/cli.js +21 -224
- package/dist/cli.js.map +4 -4
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -34963,14 +34963,13 @@ var init_node_impl = __esm(() => {
|
|
|
34963
34963
|
// src/lib/archive-reference.ts
|
|
34964
34964
|
import { createHash as createHash4 } from "node:crypto";
|
|
34965
34965
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
34966
|
-
import {
|
|
34966
|
+
import { join as join13, resolve as resolve13, sep as sep2 } from "node:path";
|
|
34967
34967
|
import { parseRetryAfter } from "@secondlayer/sdk";
|
|
34968
|
-
import {
|
|
34969
|
-
|
|
34970
|
-
|
|
34971
|
-
|
|
34972
|
-
|
|
34973
|
-
}
|
|
34968
|
+
import {
|
|
34969
|
+
checkSignature,
|
|
34970
|
+
loadReference,
|
|
34971
|
+
resolveArchivePublicKey
|
|
34972
|
+
} from "@secondlayer/shared/archive/reference";
|
|
34974
34973
|
function assertPathWithinRoot(path11, root) {
|
|
34975
34974
|
const segments = path11.split("/");
|
|
34976
34975
|
const leaves = path11.startsWith("/") || path11.includes("\\") || /^[a-z][a-z0-9+.-]*:/i.test(path11) || /[%?#]/.test(path11) || segments.some((part) => part === "" || part === "." || part === "..");
|
|
@@ -34985,109 +34984,6 @@ function assertPathWithinRoot(path11, root) {
|
|
|
34985
34984
|
}
|
|
34986
34985
|
}
|
|
34987
34986
|
}
|
|
34988
|
-
async function fetchJson(url) {
|
|
34989
|
-
const response2 = await fetch(url, { signal: AbortSignal.timeout(30000) });
|
|
34990
|
-
if (!response2.ok) {
|
|
34991
|
-
throw new Error(`could not fetch manifest (${response2.status} ${response2.statusText})`);
|
|
34992
|
-
}
|
|
34993
|
-
return response2.json();
|
|
34994
|
-
}
|
|
34995
|
-
async function loadReference(source, options3 = {}) {
|
|
34996
|
-
const isRemote = /^https?:\/\//.test(source);
|
|
34997
|
-
let manifest;
|
|
34998
|
-
let root;
|
|
34999
|
-
if (isRemote) {
|
|
35000
|
-
manifest = await fetchJson(source);
|
|
35001
|
-
const url = new URL(source);
|
|
35002
|
-
url.pathname = url.pathname.replace(/\/snapshots\/[^/]+$/, "").replace(/\/[^/]+\.json$/, "");
|
|
35003
|
-
root = url.toString();
|
|
35004
|
-
} else {
|
|
35005
|
-
const path11 = resolve13(source);
|
|
35006
|
-
manifest = JSON.parse(await readFile5(path11, "utf8"));
|
|
35007
|
-
root = isLatestPointer(manifest) ? dirname16(path11) : dirname16(dirname16(path11));
|
|
35008
|
-
}
|
|
35009
|
-
if (!isLatestPointer(manifest)) {
|
|
35010
|
-
return { manifest, origin: source, root, isRemote };
|
|
35011
|
-
}
|
|
35012
|
-
const pointer = manifest;
|
|
35013
|
-
const snapshotPath = pointer.snapshot_path;
|
|
35014
|
-
if (!SNAPSHOT_PATH_PATTERN.test(snapshotPath)) {
|
|
35015
|
-
throw new Error(`pointer names an invalid snapshot path: ${snapshotPath} (expected snapshots/<sha256>.json)`);
|
|
35016
|
-
}
|
|
35017
|
-
if (typeof pointer.snapshot_digest !== "string" || !/^[0-9a-f]{64}$/.test(pointer.snapshot_digest)) {
|
|
35018
|
-
throw new Error("pointer carries no snapshot_digest, so it cannot prove which snapshot is current");
|
|
35019
|
-
}
|
|
35020
|
-
if (pointer.signature !== undefined) {
|
|
35021
|
-
if (!options3.publicKeyPem) {
|
|
35022
|
-
throw new Error("pointer is signed but no public key is available to verify it");
|
|
35023
|
-
}
|
|
35024
|
-
let verified = false;
|
|
35025
|
-
try {
|
|
35026
|
-
verified = verifyStreamsBulkManifestSignature(pointer, options3.publicKeyPem);
|
|
35027
|
-
} catch {
|
|
35028
|
-
verified = false;
|
|
35029
|
-
}
|
|
35030
|
-
if (!verified) {
|
|
35031
|
-
throw new Error("pointer signature did not verify against the archive key");
|
|
35032
|
-
}
|
|
35033
|
-
}
|
|
35034
|
-
const resolved = isRemote ? await fetchJson(`${root.replace(/\/$/, "")}/${snapshotPath}`) : JSON.parse(await readFile5(join13(root, snapshotPath), "utf8"));
|
|
35035
|
-
const digest = createHash4("sha256").update(JSON.stringify((({ signature: _s, key_id: _k, ...rest }) => rest)(resolved))).digest("hex");
|
|
35036
|
-
if (digest !== pointer.snapshot_digest) {
|
|
35037
|
-
throw new Error(`pointer/snapshot mismatch: latest.json names ${pointer.snapshot_digest}, resolved manifest hashes to ${digest}`);
|
|
35038
|
-
}
|
|
35039
|
-
return { manifest: resolved, origin: source, root, isRemote };
|
|
35040
|
-
}
|
|
35041
|
-
function checkSignature(manifest, publicKeyPem, insecure) {
|
|
35042
|
-
if (insecure)
|
|
35043
|
-
return { verified: false, reason: "signature check skipped" };
|
|
35044
|
-
if (!manifest.signature) {
|
|
35045
|
-
return { verified: false, reason: "manifest carries no signature" };
|
|
35046
|
-
}
|
|
35047
|
-
if (!publicKeyPem) {
|
|
35048
|
-
return {
|
|
35049
|
-
verified: false,
|
|
35050
|
-
reason: "no public key available to verify with"
|
|
35051
|
-
};
|
|
35052
|
-
}
|
|
35053
|
-
try {
|
|
35054
|
-
const verified = verifyStreamsBulkManifestSignature(manifest, publicKeyPem);
|
|
35055
|
-
return {
|
|
35056
|
-
verified,
|
|
35057
|
-
reason: verified ? undefined : "signature did not verify"
|
|
35058
|
-
};
|
|
35059
|
-
} catch (err) {
|
|
35060
|
-
return {
|
|
35061
|
-
verified: false,
|
|
35062
|
-
reason: err instanceof Error ? err.message : "signature check failed"
|
|
35063
|
-
};
|
|
35064
|
-
}
|
|
35065
|
-
}
|
|
35066
|
-
async function fetchHostedKey(url) {
|
|
35067
|
-
if (!url.startsWith("https://"))
|
|
35068
|
-
return;
|
|
35069
|
-
try {
|
|
35070
|
-
const response2 = await fetch(url, { signal: AbortSignal.timeout(1e4) });
|
|
35071
|
-
if (!response2.ok)
|
|
35072
|
-
return;
|
|
35073
|
-
const body = await response2.json();
|
|
35074
|
-
return typeof body.public_key_pem === "string" ? body.public_key_pem : undefined;
|
|
35075
|
-
} catch {
|
|
35076
|
-
return;
|
|
35077
|
-
}
|
|
35078
|
-
}
|
|
35079
|
-
async function resolveArchivePublicKey(input4) {
|
|
35080
|
-
if (input4.explicitPem)
|
|
35081
|
-
return input4.explicitPem;
|
|
35082
|
-
if (input4.envPem)
|
|
35083
|
-
return input4.envPem;
|
|
35084
|
-
if (input4.allowHostedApi) {
|
|
35085
|
-
const hosted = await fetchHostedKey(input4.hostedKeyUrl ?? HOSTED_SIGNING_KEY_URL);
|
|
35086
|
-
if (hosted)
|
|
35087
|
-
return hosted;
|
|
35088
|
-
}
|
|
35089
|
-
return ARCHIVE_ROOT_PUBLIC_KEY_PEM;
|
|
35090
|
-
}
|
|
35091
34987
|
function isTransientCode(code) {
|
|
35092
34988
|
return typeof code === "string" && (TRANSIENT_ERROR_CODES.has(code) || code.startsWith("UND_ERR_"));
|
|
35093
34989
|
}
|
|
@@ -35178,9 +35074,8 @@ async function fetchVerifiedPartition(reference, partition, gate, fetchOpts) {
|
|
|
35178
35074
|
}
|
|
35179
35075
|
return bytes;
|
|
35180
35076
|
}
|
|
35181
|
-
var
|
|
35077
|
+
var ArchiveFetchError, TRANSIENT_ERROR_CODES, PARTITION_FETCH_RETRIES = 3, PARTITION_FETCH_TIMEOUT_MS = 300000, RETRY_BASE_MS = 1000, RETRY_AFTER_CAP_MS = 60000;
|
|
35182
35078
|
var init_archive_reference = __esm(() => {
|
|
35183
|
-
SNAPSHOT_PATH_PATTERN = /^snapshots\/[0-9a-f]{64}\.json$/;
|
|
35184
35079
|
ArchiveFetchError = class ArchiveFetchError extends Error {
|
|
35185
35080
|
transient;
|
|
35186
35081
|
attempts;
|
|
@@ -35207,7 +35102,7 @@ var init_archive_reference = __esm(() => {
|
|
|
35207
35102
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
35208
35103
|
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
35209
35104
|
import { resolve as resolve14 } from "node:path";
|
|
35210
|
-
import { ARCHIVE_ROOT_PUBLIC_KEY_PEM
|
|
35105
|
+
import { ARCHIVE_ROOT_PUBLIC_KEY_PEM } from "@secondlayer/shared/archive/root-key";
|
|
35211
35106
|
import { generateEd25519KeyPair } from "@secondlayer/shared/crypto/ed25519";
|
|
35212
35107
|
import { INSTANCE_NETWORKS } from "@secondlayer/shared/db/queries/instance";
|
|
35213
35108
|
function parseInstanceNetwork(value) {
|
|
@@ -35261,7 +35156,7 @@ function buildInstanceEnv(input4) {
|
|
|
35261
35156
|
const secrets = input4.existing?.SECONDLAYER_SECRETS_KEY || generateSecretsKey();
|
|
35262
35157
|
const signing = input4.existing?.STREAMS_SIGNING_PRIVATE_KEY || input4.existing?.SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY || generateSigningPrivateKey();
|
|
35263
35158
|
const apiUrl = input4.apiUrl ?? "http://127.0.0.1:3800";
|
|
35264
|
-
const archiveKey = input4.existing?.ARCHIVE_SIGNING_PUBLIC_KEY || input4.archivePublicKeyPem ||
|
|
35159
|
+
const archiveKey = input4.existing?.ARCHIVE_SIGNING_PUBLIC_KEY || input4.archivePublicKeyPem || ARCHIVE_ROOT_PUBLIC_KEY_PEM;
|
|
35265
35160
|
return {
|
|
35266
35161
|
INSTANCE_MODE: "oss",
|
|
35267
35162
|
STACKS_NETWORK: input4.network,
|
|
@@ -36792,7 +36687,7 @@ var {
|
|
|
36792
36687
|
// package.json
|
|
36793
36688
|
var package_default = {
|
|
36794
36689
|
name: "@secondlayer/cli",
|
|
36795
|
-
version: "14.1.
|
|
36690
|
+
version: "14.1.2",
|
|
36796
36691
|
description: "Run a Stacks index on your own hardware — set it up, restore verified history, and keep it current.",
|
|
36797
36692
|
repository: {
|
|
36798
36693
|
type: "git",
|
|
@@ -36837,8 +36732,8 @@ var package_default = {
|
|
|
36837
36732
|
"@opentui/react": "^0.5.3",
|
|
36838
36733
|
"@secondlayer/bundler": "^0.5.1",
|
|
36839
36734
|
"@secondlayer/scaffold": "^1.5.5",
|
|
36840
|
-
"@secondlayer/sdk": "^10.
|
|
36841
|
-
"@secondlayer/shared": "^11.
|
|
36735
|
+
"@secondlayer/sdk": "^10.2.0",
|
|
36736
|
+
"@secondlayer/shared": "^11.3.0",
|
|
36842
36737
|
"@secondlayer/stacks": "^5.1.0",
|
|
36843
36738
|
"@secondlayer/subgraphs": "^4.1.5",
|
|
36844
36739
|
esbuild: "^0.27.4",
|
|
@@ -37456,112 +37351,14 @@ import {
|
|
|
37456
37351
|
} from "@secondlayer/sdk";
|
|
37457
37352
|
|
|
37458
37353
|
// src/lib/instance-diagnosis.ts
|
|
37459
|
-
|
|
37460
|
-
|
|
37461
|
-
|
|
37462
|
-
|
|
37463
|
-
|
|
37464
|
-
|
|
37465
|
-
|
|
37466
|
-
|
|
37467
|
-
return false;
|
|
37468
|
-
return (status.chainTip ?? null) === null;
|
|
37469
|
-
}
|
|
37470
|
-
function emptyIndexIssue(status) {
|
|
37471
|
-
const decoders = status.index?.decoders ?? [];
|
|
37472
|
-
const stalled = decoders.filter((d2) => d2.status !== "ok").length;
|
|
37473
|
-
const decoderClause = stalled > 0 ? ` and ${stalled} decoder${stalled === 1 ? "" : "s"} report unavailable because there is nothing to decode` : "";
|
|
37474
|
-
return {
|
|
37475
|
-
title: "No blocks indexed yet — this instance holds 0 blocks.",
|
|
37476
|
-
detail: `Streams has no tip${decoderClause}, which is what makes the overall status DEGRADED. This is the expected state for a fresh instance: nothing is broken, but no read returns data until history lands.`,
|
|
37477
|
-
nextSteps: [BOOTSTRAP_STEP, OBSERVER_STEP, SELF_HOST_DOCS_STEP]
|
|
37478
|
-
};
|
|
37479
|
-
}
|
|
37480
|
-
var SERVICE_STEPS = [
|
|
37481
|
-
"See the raw per-service report: secondlayer status --json",
|
|
37482
|
-
"Check the containers from docker/oss: docker compose ps, then docker compose logs --tail=50 secondlayer"
|
|
37483
|
-
];
|
|
37484
|
-
function diagnoseInstanceStatus(status) {
|
|
37485
|
-
const overall = String(status.status ?? "unknown");
|
|
37486
|
-
if (hasNoChainData(status)) {
|
|
37487
|
-
return { state: "empty-index", overall, issues: [emptyIndexIssue(status)] };
|
|
37488
|
-
}
|
|
37489
|
-
const issues = [];
|
|
37490
|
-
for (const service of status.services ?? []) {
|
|
37491
|
-
if (service.status === "ok")
|
|
37492
|
-
continue;
|
|
37493
|
-
issues.push({
|
|
37494
|
-
title: `Service ${service.name} reports ${service.status}.`,
|
|
37495
|
-
nextSteps: SERVICE_STEPS
|
|
37496
|
-
});
|
|
37497
|
-
}
|
|
37498
|
-
const integrity = status.chainIntegrity;
|
|
37499
|
-
if (integrity && integrity.ok === false) {
|
|
37500
|
-
issues.push({
|
|
37501
|
-
title: "Chain-data integrity check failed.",
|
|
37502
|
-
detail: integrity.reason ?? "The tip the instance serves is not backed by the history it implies.",
|
|
37503
|
-
nextSteps: [
|
|
37504
|
-
"Verify against the archive: secondlayer verify all --against <manifest>",
|
|
37505
|
-
"Repair what diverged: secondlayer repair --against <manifest> --apply"
|
|
37506
|
-
]
|
|
37507
|
-
});
|
|
37508
|
-
}
|
|
37509
|
-
if (issues.length === 0 && overall !== "healthy") {
|
|
37510
|
-
issues.push({
|
|
37511
|
-
title: `The API reports status=${overall} but names no failing service.`,
|
|
37512
|
-
detail: "Every service in the report is ok, so the degradation is in a surface this command does not summarize.",
|
|
37513
|
-
nextSteps: SERVICE_STEPS
|
|
37514
|
-
});
|
|
37515
|
-
}
|
|
37516
|
-
return {
|
|
37517
|
-
state: issues.length > 0 ? "degraded" : "healthy",
|
|
37518
|
-
overall,
|
|
37519
|
-
issues
|
|
37520
|
-
};
|
|
37521
|
-
}
|
|
37522
|
-
var CONTEXT_FIELDS = [
|
|
37523
|
-
"account",
|
|
37524
|
-
"streamsTip",
|
|
37525
|
-
"indexTip",
|
|
37526
|
-
"subgraphs",
|
|
37527
|
-
"subscriptions",
|
|
37528
|
-
"activeOperations"
|
|
37529
|
-
];
|
|
37530
|
-
function explainContextNulls(snapshot, probe) {
|
|
37531
|
-
const nullFields = CONTEXT_FIELDS.filter((field) => (snapshot[field] ?? null) === null);
|
|
37532
|
-
if (nullFields.length === 0) {
|
|
37533
|
-
return { summary: "Every field resolved.", nulls: {} };
|
|
37534
|
-
}
|
|
37535
|
-
if (!probe.status) {
|
|
37536
|
-
const reason = `No instance answered at ${probe.apiUrl} (${probe.statusError ?? "unreachable"}), so this read never reached an API.`;
|
|
37537
|
-
return {
|
|
37538
|
-
summary: `${reason} Start it from docker/oss with: docker compose up -d — or point SL_API_URL at the instance you meant.`,
|
|
37539
|
-
nulls: Object.fromEntries(nullFields.map((field) => [field, reason]))
|
|
37540
|
-
};
|
|
37541
|
-
}
|
|
37542
|
-
const emptyIndex = hasNoChainData(probe.status);
|
|
37543
|
-
const blocks = probe.status.chainIntegrity?.maxHeight ?? null;
|
|
37544
|
-
const accountReason = probe.selfHosted ? "Expected: a self-hosted instance has no account system. Accounts exist only on the metered archive deployment at api.secondlayer.tools, so null here is normal and not a failure." : "Not authenticated — run `secondlayer login`, or export INSTANCE_TOKEN.";
|
|
37545
|
-
const listReason = unresolvedReadReason(probe, blocks);
|
|
37546
|
-
const chainReason = emptyIndex ? `No blocks indexed yet — this instance holds 0 blocks, so there is no tip to report. ${BOOTSTRAP_STEP}` : listReason;
|
|
37547
|
-
const nulls = {};
|
|
37548
|
-
for (const field of nullFields) {
|
|
37549
|
-
if (field === "account")
|
|
37550
|
-
nulls[field] = accountReason;
|
|
37551
|
-
else if (field === "streamsTip" || field === "indexTip")
|
|
37552
|
-
nulls[field] = chainReason;
|
|
37553
|
-
else if (field === "activeOperations")
|
|
37554
|
-
nulls[field] = (snapshot.subgraphs ?? null) === null ? "Not probed: in-flight operations are read per subgraph, and the subgraph list did not resolve." : listReason;
|
|
37555
|
-
else
|
|
37556
|
-
nulls[field] = listReason;
|
|
37557
|
-
}
|
|
37558
|
-
const summary = emptyIndex ? `Instance at ${probe.apiUrl} is reachable but holds 0 blocks — the chain fields are null because nothing has been indexed yet. ${BOOTSTRAP_STEP}` : nullFields.length === 1 && nullFields[0] === "account" ? `Instance at ${probe.apiUrl} is reachable and indexed to block ${blocks ?? "unknown"}; only \`account\` is null, which is normal when self-hosting.` : `Instance at ${probe.apiUrl} is reachable, but ${nullFields.length} field${nullFields.length === 1 ? "" : "s"} did not resolve — see \`nulls\` below for each one.`;
|
|
37559
|
-
return { summary, nulls };
|
|
37560
|
-
}
|
|
37561
|
-
function unresolvedReadReason(probe, blocks) {
|
|
37562
|
-
const where = `The instance at ${probe.apiUrl} is reachable${blocks !== null ? ` and indexed to block ${blocks}` : ""}, but this read returned nothing.`;
|
|
37563
|
-
return probe.hasCredential ? `${where} Check the surface directly: secondlayer status --json` : `${where} No credential resolved: export INSTANCE_TOKEN — an instance published past loopback requires it on every /v1 read.`;
|
|
37564
|
-
}
|
|
37354
|
+
import {
|
|
37355
|
+
BOOTSTRAP_STEP,
|
|
37356
|
+
OBSERVER_STEP,
|
|
37357
|
+
SELF_HOST_DOCS_STEP,
|
|
37358
|
+
diagnoseInstanceStatus,
|
|
37359
|
+
explainContextNulls,
|
|
37360
|
+
hasNoChainData
|
|
37361
|
+
} from "@secondlayer/shared/archive/instance-diagnosis";
|
|
37565
37362
|
|
|
37566
37363
|
// src/commands/context.ts
|
|
37567
37364
|
init_output();
|
|
@@ -42769,5 +42566,5 @@ program.commandsGroup("Archive:");
|
|
|
42769
42566
|
registerCreditsCommand(program);
|
|
42770
42567
|
program.parse();
|
|
42771
42568
|
|
|
42772
|
-
//# debugId=
|
|
42569
|
+
//# debugId=54476D3169B18AA964756E2164756E21
|
|
42773
42570
|
//# sourceMappingURL=cli.js.map
|