@secondlayer/cli 6.0.0 → 7.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 +38 -36
- package/dist/cli.js +829 -855
- package/dist/cli.js.map +21 -21
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2522,7 +2522,7 @@ function resolveApiUrl() {
|
|
|
2522
2522
|
return process.env.SL_API_URL ?? process.env.SL_PLATFORM_API_URL ?? DEFAULT_API_URL;
|
|
2523
2523
|
}
|
|
2524
2524
|
function resolveEnvKey() {
|
|
2525
|
-
return process.env.SL_API_KEY
|
|
2525
|
+
return process.env.SL_API_KEY;
|
|
2526
2526
|
}
|
|
2527
2527
|
async function resolveAuth() {
|
|
2528
2528
|
const apiUrl = resolveApiUrl();
|
|
@@ -4946,88 +4946,6 @@ var init_src = __esm(() => {
|
|
|
4946
4946
|
src_default = Postgres;
|
|
4947
4947
|
});
|
|
4948
4948
|
|
|
4949
|
-
// src/lib/dev-state.ts
|
|
4950
|
-
var exports_dev_state = {};
|
|
4951
|
-
__export(exports_dev_state, {
|
|
4952
|
-
saveDevState: () => saveDevState,
|
|
4953
|
-
loadDevState: () => loadDevState,
|
|
4954
|
-
isProcessRunning: () => isProcessRunning,
|
|
4955
|
-
isDevRunning: () => isDevRunning,
|
|
4956
|
-
getRunningServices: () => getRunningServices,
|
|
4957
|
-
getLogsDir: () => getLogsDir,
|
|
4958
|
-
getLogFile: () => getLogFile,
|
|
4959
|
-
ensureDirs: () => ensureDirs,
|
|
4960
|
-
clearLogs: () => clearLogs,
|
|
4961
|
-
clearDevState: () => clearDevState
|
|
4962
|
-
});
|
|
4963
|
-
import { homedir as homedir4 } from "node:os";
|
|
4964
|
-
import { join as join5 } from "node:path";
|
|
4965
|
-
function getLogsDir() {
|
|
4966
|
-
return LOGS_DIR;
|
|
4967
|
-
}
|
|
4968
|
-
function getLogFile(service) {
|
|
4969
|
-
return join5(LOGS_DIR, `${service}.log`);
|
|
4970
|
-
}
|
|
4971
|
-
async function ensureDirs() {
|
|
4972
|
-
await Bun.$`mkdir -p ${STATE_DIR}`.quiet();
|
|
4973
|
-
await Bun.$`mkdir -p ${LOGS_DIR}`.quiet();
|
|
4974
|
-
}
|
|
4975
|
-
async function loadDevState() {
|
|
4976
|
-
const file = Bun.file(DEV_STATE_PATH);
|
|
4977
|
-
if (!await file.exists()) {
|
|
4978
|
-
return null;
|
|
4979
|
-
}
|
|
4980
|
-
try {
|
|
4981
|
-
return await file.json();
|
|
4982
|
-
} catch {
|
|
4983
|
-
return null;
|
|
4984
|
-
}
|
|
4985
|
-
}
|
|
4986
|
-
async function saveDevState(state) {
|
|
4987
|
-
await ensureDirs();
|
|
4988
|
-
await Bun.write(DEV_STATE_PATH, `${JSON.stringify(state, null, 2)}
|
|
4989
|
-
`);
|
|
4990
|
-
}
|
|
4991
|
-
async function clearDevState() {
|
|
4992
|
-
const file = Bun.file(DEV_STATE_PATH);
|
|
4993
|
-
if (await file.exists()) {
|
|
4994
|
-
await Bun.$`rm ${DEV_STATE_PATH}`.quiet();
|
|
4995
|
-
}
|
|
4996
|
-
}
|
|
4997
|
-
async function clearLogs() {
|
|
4998
|
-
await Bun.$`rm -f ${LOGS_DIR}/*.log`.quiet().nothrow();
|
|
4999
|
-
}
|
|
5000
|
-
function isProcessRunning(pid) {
|
|
5001
|
-
try {
|
|
5002
|
-
process.kill(pid, 0);
|
|
5003
|
-
return true;
|
|
5004
|
-
} catch {
|
|
5005
|
-
return false;
|
|
5006
|
-
}
|
|
5007
|
-
}
|
|
5008
|
-
async function getRunningServices() {
|
|
5009
|
-
const state = await loadDevState();
|
|
5010
|
-
if (!state)
|
|
5011
|
-
return {};
|
|
5012
|
-
const running = {};
|
|
5013
|
-
for (const [name, service] of Object.entries(state.services)) {
|
|
5014
|
-
if (isProcessRunning(service.pid)) {
|
|
5015
|
-
running[name] = service;
|
|
5016
|
-
}
|
|
5017
|
-
}
|
|
5018
|
-
return running;
|
|
5019
|
-
}
|
|
5020
|
-
async function isDevRunning() {
|
|
5021
|
-
const running = await getRunningServices();
|
|
5022
|
-
return Object.keys(running).length > 0;
|
|
5023
|
-
}
|
|
5024
|
-
var STATE_DIR, DEV_STATE_PATH, LOGS_DIR;
|
|
5025
|
-
var init_dev_state = __esm(() => {
|
|
5026
|
-
STATE_DIR = join5(homedir4(), ".secondlayer");
|
|
5027
|
-
DEV_STATE_PATH = join5(STATE_DIR, "dev.json");
|
|
5028
|
-
LOGS_DIR = join5(STATE_DIR, "logs");
|
|
5029
|
-
});
|
|
5030
|
-
|
|
5031
4949
|
// src/utils/abi-compat.ts
|
|
5032
4950
|
function normalizeAccess(access) {
|
|
5033
4951
|
if (access === "read_only")
|
|
@@ -12990,6 +12908,88 @@ function inferNetwork(address) {
|
|
|
12990
12908
|
return;
|
|
12991
12909
|
}
|
|
12992
12910
|
|
|
12911
|
+
// src/lib/dev-state.ts
|
|
12912
|
+
var exports_dev_state = {};
|
|
12913
|
+
__export(exports_dev_state, {
|
|
12914
|
+
saveDevState: () => saveDevState,
|
|
12915
|
+
loadDevState: () => loadDevState,
|
|
12916
|
+
isProcessRunning: () => isProcessRunning,
|
|
12917
|
+
isDevRunning: () => isDevRunning,
|
|
12918
|
+
getRunningServices: () => getRunningServices,
|
|
12919
|
+
getLogsDir: () => getLogsDir,
|
|
12920
|
+
getLogFile: () => getLogFile,
|
|
12921
|
+
ensureDirs: () => ensureDirs,
|
|
12922
|
+
clearLogs: () => clearLogs,
|
|
12923
|
+
clearDevState: () => clearDevState
|
|
12924
|
+
});
|
|
12925
|
+
import { homedir as homedir4 } from "node:os";
|
|
12926
|
+
import { join as join8 } from "node:path";
|
|
12927
|
+
function getLogsDir() {
|
|
12928
|
+
return LOGS_DIR;
|
|
12929
|
+
}
|
|
12930
|
+
function getLogFile(service) {
|
|
12931
|
+
return join8(LOGS_DIR, `${service}.log`);
|
|
12932
|
+
}
|
|
12933
|
+
async function ensureDirs() {
|
|
12934
|
+
await Bun.$`mkdir -p ${STATE_DIR}`.quiet();
|
|
12935
|
+
await Bun.$`mkdir -p ${LOGS_DIR}`.quiet();
|
|
12936
|
+
}
|
|
12937
|
+
async function loadDevState() {
|
|
12938
|
+
const file = Bun.file(DEV_STATE_PATH);
|
|
12939
|
+
if (!await file.exists()) {
|
|
12940
|
+
return null;
|
|
12941
|
+
}
|
|
12942
|
+
try {
|
|
12943
|
+
return await file.json();
|
|
12944
|
+
} catch {
|
|
12945
|
+
return null;
|
|
12946
|
+
}
|
|
12947
|
+
}
|
|
12948
|
+
async function saveDevState(state) {
|
|
12949
|
+
await ensureDirs();
|
|
12950
|
+
await Bun.write(DEV_STATE_PATH, `${JSON.stringify(state, null, 2)}
|
|
12951
|
+
`);
|
|
12952
|
+
}
|
|
12953
|
+
async function clearDevState() {
|
|
12954
|
+
const file = Bun.file(DEV_STATE_PATH);
|
|
12955
|
+
if (await file.exists()) {
|
|
12956
|
+
await Bun.$`rm ${DEV_STATE_PATH}`.quiet();
|
|
12957
|
+
}
|
|
12958
|
+
}
|
|
12959
|
+
async function clearLogs() {
|
|
12960
|
+
await Bun.$`rm -f ${LOGS_DIR}/*.log`.quiet().nothrow();
|
|
12961
|
+
}
|
|
12962
|
+
function isProcessRunning(pid) {
|
|
12963
|
+
try {
|
|
12964
|
+
process.kill(pid, 0);
|
|
12965
|
+
return true;
|
|
12966
|
+
} catch {
|
|
12967
|
+
return false;
|
|
12968
|
+
}
|
|
12969
|
+
}
|
|
12970
|
+
async function getRunningServices() {
|
|
12971
|
+
const state = await loadDevState();
|
|
12972
|
+
if (!state)
|
|
12973
|
+
return {};
|
|
12974
|
+
const running = {};
|
|
12975
|
+
for (const [name, service] of Object.entries(state.services)) {
|
|
12976
|
+
if (isProcessRunning(service.pid)) {
|
|
12977
|
+
running[name] = service;
|
|
12978
|
+
}
|
|
12979
|
+
}
|
|
12980
|
+
return running;
|
|
12981
|
+
}
|
|
12982
|
+
async function isDevRunning() {
|
|
12983
|
+
const running = await getRunningServices();
|
|
12984
|
+
return Object.keys(running).length > 0;
|
|
12985
|
+
}
|
|
12986
|
+
var STATE_DIR, DEV_STATE_PATH, LOGS_DIR;
|
|
12987
|
+
var init_dev_state = __esm(() => {
|
|
12988
|
+
STATE_DIR = join8(homedir4(), ".secondlayer");
|
|
12989
|
+
DEV_STATE_PATH = join8(STATE_DIR, "dev.json");
|
|
12990
|
+
LOGS_DIR = join8(STATE_DIR, "logs");
|
|
12991
|
+
});
|
|
12992
|
+
|
|
12993
12993
|
// src/lib/network.ts
|
|
12994
12994
|
function getChainId(network) {
|
|
12995
12995
|
return CHAIN_IDS[network].decimal.toString();
|
|
@@ -32354,7 +32354,7 @@ var {
|
|
|
32354
32354
|
// package.json
|
|
32355
32355
|
var package_default = {
|
|
32356
32356
|
name: "@secondlayer/cli",
|
|
32357
|
-
version: "
|
|
32357
|
+
version: "7.0.0",
|
|
32358
32358
|
repository: {
|
|
32359
32359
|
type: "git",
|
|
32360
32360
|
url: "git+https://github.com/ryanwaits/secondlayer.git",
|
|
@@ -32605,10 +32605,6 @@ async function runBillingStatus(json) {
|
|
|
32605
32605
|
function addBillingCommand(parent) {
|
|
32606
32606
|
parent.command("billing").description("Show your plan, Stripe subscription, trial, and discounts").option("--json", "Output as JSON").action((options) => runBillingStatus(options.json));
|
|
32607
32607
|
}
|
|
32608
|
-
function registerBillingCommand(program2) {
|
|
32609
|
-
const billing = program2.command("billing").description("Deprecated: use `account billing`");
|
|
32610
|
-
billing.command("status").description("Show your current plan, Stripe subscription, trial, and discounts").option("--json", "Output as JSON").action((options) => runBillingStatus(options.json));
|
|
32611
|
-
}
|
|
32612
32608
|
function renderBillingStatus(res) {
|
|
32613
32609
|
if (!res.subscription) {
|
|
32614
32610
|
console.log(formatKeyValue([
|
|
@@ -32700,13 +32696,6 @@ function registerAccountCommand(program2) {
|
|
|
32700
32696
|
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("--json", "Output as JSON").action(withErrorHandling(async (options) => applyProfileUpdate(options), {
|
|
32701
32697
|
action: "update profile"
|
|
32702
32698
|
}));
|
|
32703
|
-
account.command("profile").description("Deprecated: use `account get` / `account update`").option("--name <name>", "Set display name").option("--bio <bio>", "Set bio").option("--slug <slug>", "Set public URL slug").option("--json", "Output as JSON").action(withErrorHandling(async (options) => {
|
|
32704
|
-
if (options.name || options.bio || options.slug) {
|
|
32705
|
-
await applyProfileUpdate(options);
|
|
32706
|
-
} else {
|
|
32707
|
-
await showProfile(options.json);
|
|
32708
|
-
}
|
|
32709
|
-
}, { action: "manage profile" }));
|
|
32710
32699
|
}
|
|
32711
32700
|
// src/commands/config.ts
|
|
32712
32701
|
init_config();
|
|
@@ -32952,7 +32941,7 @@ async function detectStacksNodes() {
|
|
|
32952
32941
|
init_output();
|
|
32953
32942
|
function registerConfigCommand(program2) {
|
|
32954
32943
|
const config = program2.command("config").description("Manage CLI configuration");
|
|
32955
|
-
config.command("get").
|
|
32944
|
+
config.command("get").description("Show current configuration").option("--json", "Output as JSON").action(async (options) => {
|
|
32956
32945
|
try {
|
|
32957
32946
|
const cfg = await loadConfig();
|
|
32958
32947
|
if (options.json) {
|
|
@@ -33001,7 +32990,7 @@ Examples:
|
|
|
33001
32990
|
process.exit(1);
|
|
33002
32991
|
}
|
|
33003
32992
|
});
|
|
33004
|
-
config.command("delete").
|
|
32993
|
+
config.command("delete").description("Delete all configuration (remove the config file)").option("-y, --yes", "Skip confirmation").action(async (opts) => {
|
|
33005
32994
|
try {
|
|
33006
32995
|
if (!opts.yes && !await confirmDestructive("Delete the config file?")) {
|
|
33007
32996
|
return;
|
|
@@ -33106,20 +33095,97 @@ async function validateDatabaseConnection(url) {
|
|
|
33106
33095
|
console.log(dim("The URL was saved but connection failed. Check your database settings."));
|
|
33107
33096
|
}
|
|
33108
33097
|
}
|
|
33109
|
-
// src/commands/
|
|
33110
|
-
|
|
33111
|
-
|
|
33112
|
-
|
|
33113
|
-
|
|
33114
|
-
|
|
33115
|
-
|
|
33116
|
-
|
|
33117
|
-
|
|
33118
|
-
|
|
33119
|
-
|
|
33120
|
-
|
|
33121
|
-
|
|
33122
|
-
|
|
33098
|
+
// src/commands/status.ts
|
|
33099
|
+
init_config();
|
|
33100
|
+
init_output();
|
|
33101
|
+
function registerStatusCommand(program2) {
|
|
33102
|
+
program2.command("status").description("Show system status").option("--json", "Output as JSON").action(async (options) => {
|
|
33103
|
+
const config = await loadConfig();
|
|
33104
|
+
try {
|
|
33105
|
+
const status = await httpPlatform("/status");
|
|
33106
|
+
output({
|
|
33107
|
+
json: options.json,
|
|
33108
|
+
data: status,
|
|
33109
|
+
human: () => printStatus(status)
|
|
33110
|
+
});
|
|
33111
|
+
} catch (err) {
|
|
33112
|
+
if (err instanceof CliHttpError && err.code === "SESSION_EXPIRED") {
|
|
33113
|
+
console.error("Not logged in. Run: sl login");
|
|
33114
|
+
process.exit(1);
|
|
33115
|
+
}
|
|
33116
|
+
console.log("");
|
|
33117
|
+
console.log(blue("System Status"));
|
|
33118
|
+
console.log(` ${red("NOT RUNNING")}`);
|
|
33119
|
+
console.log("");
|
|
33120
|
+
if (config.network === "local") {
|
|
33121
|
+
console.log(dim(" API service is not running."));
|
|
33122
|
+
console.log(dim(" Start with: sl local start"));
|
|
33123
|
+
} else {
|
|
33124
|
+
console.log(dim(" Can't reach the platform API."));
|
|
33125
|
+
console.log(dim(" Check your connection or try again."));
|
|
33126
|
+
}
|
|
33127
|
+
console.log("");
|
|
33128
|
+
process.exit(1);
|
|
33129
|
+
}
|
|
33130
|
+
});
|
|
33131
|
+
}
|
|
33132
|
+
function printStatus(status) {
|
|
33133
|
+
console.log("");
|
|
33134
|
+
const statusColor = status.status === "healthy" ? green : red;
|
|
33135
|
+
console.log(blue("System Status"));
|
|
33136
|
+
console.log(` ${statusColor(status.status.toUpperCase())}${status.network ? dim(` (${status.network})`) : ""}`);
|
|
33137
|
+
console.log("");
|
|
33138
|
+
console.log(blue("Database"));
|
|
33139
|
+
const dbColor = status.database.status === "ok" ? green : red;
|
|
33140
|
+
console.log(` Status: ${dbColor(status.database.status)}`);
|
|
33141
|
+
console.log("");
|
|
33142
|
+
console.log(blue("Index Progress"));
|
|
33143
|
+
if (status.indexProgress.length === 0) {
|
|
33144
|
+
console.log(dim(" No data indexed yet"));
|
|
33145
|
+
} else {
|
|
33146
|
+
for (const p of status.indexProgress) {
|
|
33147
|
+
const behind = p.highestSeenBlock - p.lastIndexedBlock;
|
|
33148
|
+
const behindStr = behind > 0 ? yellow(` (${behind} behind)`) : green(" (synced)");
|
|
33149
|
+
console.log(` ${p.network}: block ${p.lastIndexedBlock}${behindStr}`);
|
|
33150
|
+
if (status.chainTip && status.chainTip > 0) {
|
|
33151
|
+
const pct = Math.min(p.lastIndexedBlock / status.chainTip * 100, 100);
|
|
33152
|
+
const barWidth = 30;
|
|
33153
|
+
const filled = Math.round(pct / 100 * barWidth);
|
|
33154
|
+
const empty = barWidth - filled;
|
|
33155
|
+
const bar = `${"█".repeat(filled)}${"░".repeat(empty)}`;
|
|
33156
|
+
const pctStr = `${pct.toFixed(1)}%`;
|
|
33157
|
+
const color = pct >= 99.9 ? green : pct >= 50 ? yellow : red;
|
|
33158
|
+
console.log(` ${dim("chain:")} ${color(bar)} ${color(pctStr)} ${dim(`(tip: ${status.chainTip.toLocaleString()})`)}`);
|
|
33159
|
+
}
|
|
33160
|
+
const contiguous = p.lastContiguousBlock ?? 0;
|
|
33161
|
+
if (status.integrity === "complete") {
|
|
33162
|
+
console.log(` ${dim("contiguous:")} ${contiguous} ${green("(complete)")}`);
|
|
33163
|
+
} else {
|
|
33164
|
+
const gapCount = status.gaps?.length ?? 0;
|
|
33165
|
+
const missing = status.totalMissingBlocks ?? 0;
|
|
33166
|
+
console.log(` ${dim("contiguous:")} ${contiguous} ${yellow(`(${gapCount} gaps, ${missing} missing blocks)`)}`);
|
|
33167
|
+
if (status.gaps) {
|
|
33168
|
+
for (const gap of status.gaps.slice(0, 5)) {
|
|
33169
|
+
const range = gap.gapStart === gap.gapEnd ? `${gap.gapStart}` : `${gap.gapStart}-${gap.gapEnd}`;
|
|
33170
|
+
console.log(` ${dim("gap:")} ${range} ${dim(`(${gap.size} block${gap.size > 1 ? "s" : ""})`)}`);
|
|
33171
|
+
}
|
|
33172
|
+
}
|
|
33173
|
+
}
|
|
33174
|
+
}
|
|
33175
|
+
}
|
|
33176
|
+
console.log("");
|
|
33177
|
+
if (status.activeSubgraphs !== undefined) {
|
|
33178
|
+
console.log(blue("Activity"));
|
|
33179
|
+
console.log(formatKeyValue([
|
|
33180
|
+
[" Active Subgraphs", status.activeSubgraphs.toString()]
|
|
33181
|
+
]));
|
|
33182
|
+
console.log("");
|
|
33183
|
+
}
|
|
33184
|
+
console.log(dim(`Last updated: ${status.timestamp}`));
|
|
33185
|
+
}
|
|
33186
|
+
// src/commands/subscriptions.ts
|
|
33187
|
+
import { confirm } from "@inquirer/prompts";
|
|
33188
|
+
import { sign } from "@secondlayer/shared/crypto/standard-webhooks";
|
|
33123
33189
|
|
|
33124
33190
|
// src/lib/filter-params.ts
|
|
33125
33191
|
var FILTER_OPERATORS = ["eq", "neq", "gt", "gte", "lt", "lte"];
|
|
@@ -33187,9 +33253,8 @@ function parseSubscriptionFilter(args) {
|
|
|
33187
33253
|
return Object.keys(filters).length > 0 ? filters : undefined;
|
|
33188
33254
|
}
|
|
33189
33255
|
|
|
33190
|
-
// src/commands/
|
|
33256
|
+
// src/commands/subscriptions.ts
|
|
33191
33257
|
init_output();
|
|
33192
|
-
init_resolve_auth();
|
|
33193
33258
|
|
|
33194
33259
|
// src/lib/subscription-validation.ts
|
|
33195
33260
|
import {
|
|
@@ -33209,6 +33274,23 @@ async function validateSubscriptionTargetFromApi(client, input) {
|
|
|
33209
33274
|
}
|
|
33210
33275
|
}
|
|
33211
33276
|
|
|
33277
|
+
// src/commands/create.ts
|
|
33278
|
+
import {
|
|
33279
|
+
copyFileSync,
|
|
33280
|
+
existsSync,
|
|
33281
|
+
mkdirSync,
|
|
33282
|
+
readFileSync,
|
|
33283
|
+
readdirSync,
|
|
33284
|
+
statSync,
|
|
33285
|
+
writeFileSync
|
|
33286
|
+
} from "node:fs";
|
|
33287
|
+
import { dirname as dirname2, join as join4, relative, resolve } from "node:path";
|
|
33288
|
+
import { fileURLToPath } from "node:url";
|
|
33289
|
+
import { input, select as select2 } from "@inquirer/prompts";
|
|
33290
|
+
import { SecondLayer as SecondLayer2 } from "@secondlayer/sdk";
|
|
33291
|
+
init_output();
|
|
33292
|
+
init_resolve_auth();
|
|
33293
|
+
|
|
33212
33294
|
// src/utils/urls.ts
|
|
33213
33295
|
function deriveBaseUrl(apiUrl) {
|
|
33214
33296
|
const override = process.env.SL_DASHBOARD_URL?.trim();
|
|
@@ -33453,105 +33535,8 @@ function addSubscriptionsCreateCommand(subscriptions) {
|
|
|
33453
33535
|
examplePrefix: "sl subscriptions create"
|
|
33454
33536
|
});
|
|
33455
33537
|
}
|
|
33456
|
-
|
|
33457
|
-
const create = program2.command("create").description("Deprecated: use `sl subscriptions create`");
|
|
33458
|
-
addSubscriptionScaffold(create, "subscription <name>", {
|
|
33459
|
-
description: "Deprecated: use `sl subscriptions create`",
|
|
33460
|
-
examplePrefix: "sl create subscription"
|
|
33461
|
-
});
|
|
33462
|
-
}
|
|
33463
|
-
// src/commands/status.ts
|
|
33464
|
-
init_config();
|
|
33465
|
-
init_output();
|
|
33466
|
-
function registerStatusCommand(program2) {
|
|
33467
|
-
program2.command("status").description("Show system status").option("--json", "Output as JSON").action(async (options) => {
|
|
33468
|
-
const config = await loadConfig();
|
|
33469
|
-
try {
|
|
33470
|
-
const status = await httpPlatform("/status");
|
|
33471
|
-
output({
|
|
33472
|
-
json: options.json,
|
|
33473
|
-
data: status,
|
|
33474
|
-
human: () => printStatus(status)
|
|
33475
|
-
});
|
|
33476
|
-
} catch (err) {
|
|
33477
|
-
if (err instanceof CliHttpError && err.code === "SESSION_EXPIRED") {
|
|
33478
|
-
console.error("Not logged in. Run: sl login");
|
|
33479
|
-
process.exit(1);
|
|
33480
|
-
}
|
|
33481
|
-
console.log("");
|
|
33482
|
-
console.log(blue("System Status"));
|
|
33483
|
-
console.log(` ${red("NOT RUNNING")}`);
|
|
33484
|
-
console.log("");
|
|
33485
|
-
if (config.network === "local") {
|
|
33486
|
-
console.log(dim(" API service is not running."));
|
|
33487
|
-
console.log(dim(" Start with: sl local start"));
|
|
33488
|
-
} else {
|
|
33489
|
-
console.log(dim(" Can't reach the platform API."));
|
|
33490
|
-
console.log(dim(" Check your connection or try again."));
|
|
33491
|
-
}
|
|
33492
|
-
console.log("");
|
|
33493
|
-
process.exit(1);
|
|
33494
|
-
}
|
|
33495
|
-
});
|
|
33496
|
-
}
|
|
33497
|
-
function printStatus(status) {
|
|
33498
|
-
console.log("");
|
|
33499
|
-
const statusColor = status.status === "healthy" ? green : red;
|
|
33500
|
-
console.log(blue("System Status"));
|
|
33501
|
-
console.log(` ${statusColor(status.status.toUpperCase())}${status.network ? dim(` (${status.network})`) : ""}`);
|
|
33502
|
-
console.log("");
|
|
33503
|
-
console.log(blue("Database"));
|
|
33504
|
-
const dbColor = status.database.status === "ok" ? green : red;
|
|
33505
|
-
console.log(` Status: ${dbColor(status.database.status)}`);
|
|
33506
|
-
console.log("");
|
|
33507
|
-
console.log(blue("Index Progress"));
|
|
33508
|
-
if (status.indexProgress.length === 0) {
|
|
33509
|
-
console.log(dim(" No data indexed yet"));
|
|
33510
|
-
} else {
|
|
33511
|
-
for (const p of status.indexProgress) {
|
|
33512
|
-
const behind = p.highestSeenBlock - p.lastIndexedBlock;
|
|
33513
|
-
const behindStr = behind > 0 ? yellow(` (${behind} behind)`) : green(" (synced)");
|
|
33514
|
-
console.log(` ${p.network}: block ${p.lastIndexedBlock}${behindStr}`);
|
|
33515
|
-
if (status.chainTip && status.chainTip > 0) {
|
|
33516
|
-
const pct = Math.min(p.lastIndexedBlock / status.chainTip * 100, 100);
|
|
33517
|
-
const barWidth = 30;
|
|
33518
|
-
const filled = Math.round(pct / 100 * barWidth);
|
|
33519
|
-
const empty = barWidth - filled;
|
|
33520
|
-
const bar = `${"█".repeat(filled)}${"░".repeat(empty)}`;
|
|
33521
|
-
const pctStr = `${pct.toFixed(1)}%`;
|
|
33522
|
-
const color = pct >= 99.9 ? green : pct >= 50 ? yellow : red;
|
|
33523
|
-
console.log(` ${dim("chain:")} ${color(bar)} ${color(pctStr)} ${dim(`(tip: ${status.chainTip.toLocaleString()})`)}`);
|
|
33524
|
-
}
|
|
33525
|
-
const contiguous = p.lastContiguousBlock ?? 0;
|
|
33526
|
-
if (status.integrity === "complete") {
|
|
33527
|
-
console.log(` ${dim("contiguous:")} ${contiguous} ${green("(complete)")}`);
|
|
33528
|
-
} else {
|
|
33529
|
-
const gapCount = status.gaps?.length ?? 0;
|
|
33530
|
-
const missing = status.totalMissingBlocks ?? 0;
|
|
33531
|
-
console.log(` ${dim("contiguous:")} ${contiguous} ${yellow(`(${gapCount} gaps, ${missing} missing blocks)`)}`);
|
|
33532
|
-
if (status.gaps) {
|
|
33533
|
-
for (const gap of status.gaps.slice(0, 5)) {
|
|
33534
|
-
const range = gap.gapStart === gap.gapEnd ? `${gap.gapStart}` : `${gap.gapStart}-${gap.gapEnd}`;
|
|
33535
|
-
console.log(` ${dim("gap:")} ${range} ${dim(`(${gap.size} block${gap.size > 1 ? "s" : ""})`)}`);
|
|
33536
|
-
}
|
|
33537
|
-
}
|
|
33538
|
-
}
|
|
33539
|
-
}
|
|
33540
|
-
}
|
|
33541
|
-
console.log("");
|
|
33542
|
-
if (status.activeSubgraphs !== undefined) {
|
|
33543
|
-
console.log(blue("Activity"));
|
|
33544
|
-
console.log(formatKeyValue([
|
|
33545
|
-
[" Active Subgraphs", status.activeSubgraphs.toString()]
|
|
33546
|
-
]));
|
|
33547
|
-
console.log("");
|
|
33548
|
-
}
|
|
33549
|
-
console.log(dim(`Last updated: ${status.timestamp}`));
|
|
33550
|
-
}
|
|
33538
|
+
|
|
33551
33539
|
// src/commands/subscriptions.ts
|
|
33552
|
-
import { confirm } from "@inquirer/prompts";
|
|
33553
|
-
import { sign } from "@secondlayer/shared/crypto/standard-webhooks";
|
|
33554
|
-
init_output();
|
|
33555
33540
|
function commonOptions(cmd) {
|
|
33556
33541
|
return cmd.option("--service-key <key>", "SL_SERVICE_KEY override").option("--base-url <url>", "SL_API_URL override");
|
|
33557
33542
|
}
|
|
@@ -34177,375 +34162,10 @@ POST result:`));
|
|
|
34177
34162
|
}
|
|
34178
34163
|
});
|
|
34179
34164
|
}
|
|
34180
|
-
// src/commands/db.ts
|
|
34181
|
-
init_config();
|
|
34182
|
-
init_dev_state();
|
|
34183
|
-
init_output();
|
|
34184
|
-
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
34185
|
-
import { getDb, sql } from "@secondlayer/shared/db";
|
|
34186
|
-
import {
|
|
34187
|
-
countMissingBlocks,
|
|
34188
|
-
findGaps
|
|
34189
|
-
} from "@secondlayer/shared/db/queries/integrity";
|
|
34190
|
-
import { StacksNodeClient } from "@secondlayer/shared/node";
|
|
34191
|
-
var DEV_DATABASE_URL = "postgres://postgres:postgres@localhost:5432/secondlayer_dev";
|
|
34192
|
-
function registerDbCommand(program2) {
|
|
34193
|
-
addDbCommand(program2, { deprecated: true });
|
|
34194
|
-
}
|
|
34195
|
-
function addDbCommand(parent, opts) {
|
|
34196
|
-
const dbCmd = parent.command("db").description(opts?.deprecated ? "Deprecated: use `sl local db`" : "Inspect indexer database tables").addHelpText("after", `
|
|
34197
|
-
Subcommands:
|
|
34198
|
-
blocks Show recent blocks
|
|
34199
|
-
txs Show recent transactions
|
|
34200
|
-
events Show recent events
|
|
34201
|
-
gaps Show gaps in indexed block data
|
|
34202
|
-
truncate Truncate all indexed data
|
|
34203
|
-
resync Reset database and restart indexer
|
|
34204
|
-
|
|
34205
|
-
Run \`sl db\` with no subcommand to print an overview.`).hook("preAction", async () => {
|
|
34206
|
-
await requireLocalNetwork();
|
|
34207
|
-
}).action(async () => {
|
|
34208
|
-
await showOverview(10);
|
|
34209
|
-
});
|
|
34210
|
-
dbCmd.command("blocks").description("Show recent blocks").option("--limit <n>", "Number of rows", "10").option("--json", "Output as JSON").action(async function() {
|
|
34211
|
-
const opts2 = this.opts();
|
|
34212
|
-
await showBlocks(Number.parseInt(opts2.limit), opts2.json);
|
|
34213
|
-
});
|
|
34214
|
-
dbCmd.command("txs").description("Show recent transactions").option("--limit <n>", "Number of rows", "10").option("--json", "Output as JSON").action(async function() {
|
|
34215
|
-
const opts2 = this.opts();
|
|
34216
|
-
await showTransactions(Number.parseInt(opts2.limit), opts2.json);
|
|
34217
|
-
});
|
|
34218
|
-
dbCmd.command("events").description("Show recent events").option("--limit <n>", "Number of rows", "10").option("--json", "Output as JSON").action(async function() {
|
|
34219
|
-
const opts2 = this.opts();
|
|
34220
|
-
await showEvents(Number.parseInt(opts2.limit), opts2.json);
|
|
34221
|
-
});
|
|
34222
|
-
dbCmd.command("gaps").description("Show gaps in indexed block data").option("--limit <n>", "Number of gaps to show", "50").option("--json", "Output as JSON").action(async function() {
|
|
34223
|
-
const opts2 = this.opts();
|
|
34224
|
-
await showGaps(Number.parseInt(opts2.limit), opts2.json);
|
|
34225
|
-
});
|
|
34226
|
-
dbCmd.command("truncate").alias("reset").description("Truncate all indexed data (blocks, txs, events)").option("-y, --yes", "Skip confirmation prompt").action(async function() {
|
|
34227
|
-
const opts2 = this.opts();
|
|
34228
|
-
await resetDatabase(opts2.yes);
|
|
34229
|
-
});
|
|
34230
|
-
dbCmd.command("resync").description("Reset database and restart indexer for fresh sync").option("-y, --yes", "Skip confirmation prompt").option("--backfill", "After reset, fetch all blocks from node").action(async function() {
|
|
34231
|
-
const opts2 = this.opts();
|
|
34232
|
-
await resyncDatabase(opts2.yes, opts2.backfill);
|
|
34233
|
-
});
|
|
34234
|
-
}
|
|
34235
|
-
function ensureDb() {
|
|
34236
|
-
if (!process.env.DATABASE_URL) {
|
|
34237
|
-
process.env.DATABASE_URL = DEV_DATABASE_URL;
|
|
34238
|
-
}
|
|
34239
|
-
return getDb();
|
|
34240
|
-
}
|
|
34241
|
-
async function showOverview(_limit) {
|
|
34242
|
-
try {
|
|
34243
|
-
const db = ensureDb();
|
|
34244
|
-
const blockCountResult = await db.selectFrom("blocks").select(sql`count(*)`.as("count")).executeTakeFirst();
|
|
34245
|
-
const txCountResult = await db.selectFrom("transactions").select(sql`count(*)`.as("count")).executeTakeFirst();
|
|
34246
|
-
const eventCountResult = await db.selectFrom("events").select(sql`count(*)`.as("count")).executeTakeFirst();
|
|
34247
|
-
const blockCount = blockCountResult?.count ?? 0;
|
|
34248
|
-
const txCount = txCountResult?.count ?? 0;
|
|
34249
|
-
const eventCount = eventCountResult?.count ?? 0;
|
|
34250
|
-
const latest = await db.selectFrom("blocks").select(["height", "hash", "timestamp"]).orderBy("height", "desc").limit(1).executeTakeFirst();
|
|
34251
|
-
console.log("");
|
|
34252
|
-
console.log(blue("Database Overview"));
|
|
34253
|
-
console.log("");
|
|
34254
|
-
console.log(` Blocks: ${green(blockCount.toString())}`);
|
|
34255
|
-
console.log(` Transactions: ${green(txCount.toString())}`);
|
|
34256
|
-
console.log(` Events: ${green(eventCount.toString())}`);
|
|
34257
|
-
console.log("");
|
|
34258
|
-
if (latest) {
|
|
34259
|
-
const time = new Date(latest.timestamp * 1000).toISOString();
|
|
34260
|
-
console.log(blue("Latest Block"));
|
|
34261
|
-
console.log(` Height: ${latest.height}`);
|
|
34262
|
-
console.log(` Hash: ${dim(latest.hash.slice(0, 20))}...`);
|
|
34263
|
-
console.log(` Time: ${time}`);
|
|
34264
|
-
console.log("");
|
|
34265
|
-
}
|
|
34266
|
-
console.log(dim(`Use 'sl db blocks|txs|events' to see records`));
|
|
34267
|
-
console.log("");
|
|
34268
|
-
process.exit(0);
|
|
34269
|
-
} catch (err) {
|
|
34270
|
-
error(`Failed to query database: ${err}`);
|
|
34271
|
-
console.log(dim(`
|
|
34272
|
-
Make sure PostgreSQL is running.`));
|
|
34273
|
-
console.log(dim("Run 'sl local start' to start all services."));
|
|
34274
|
-
process.exit(1);
|
|
34275
|
-
}
|
|
34276
|
-
}
|
|
34277
|
-
async function showBlocks(limit, json) {
|
|
34278
|
-
try {
|
|
34279
|
-
const db = ensureDb();
|
|
34280
|
-
const rows = await db.selectFrom("blocks").select(["height", "hash", "timestamp", "canonical"]).orderBy("height", "desc").limit(limit).execute();
|
|
34281
|
-
if (json) {
|
|
34282
|
-
console.log(JSON.stringify(rows, null, 2));
|
|
34283
|
-
process.exit(0);
|
|
34284
|
-
}
|
|
34285
|
-
console.log("");
|
|
34286
|
-
console.log(blue(`Recent Blocks (${rows.length})`));
|
|
34287
|
-
console.log("");
|
|
34288
|
-
console.log(dim(" HEIGHT".padEnd(10)) + dim("HASH".padEnd(24)) + dim("TIME"));
|
|
34289
|
-
for (const row of rows) {
|
|
34290
|
-
const time = new Date(row.timestamp * 1000).toLocaleTimeString();
|
|
34291
|
-
const canonicalMark = row.canonical ? "" : yellow(" (reorg)");
|
|
34292
|
-
console.log(` ${row.height.toString().padEnd(10)}${`${row.hash.slice(0, 20)}... `.padEnd(24)}${time}${canonicalMark}`);
|
|
34293
|
-
}
|
|
34294
|
-
console.log("");
|
|
34295
|
-
process.exit(0);
|
|
34296
|
-
} catch (err) {
|
|
34297
|
-
error(`Failed to query blocks: ${err}`);
|
|
34298
|
-
process.exit(1);
|
|
34299
|
-
}
|
|
34300
|
-
}
|
|
34301
|
-
async function showTransactions(limit, json) {
|
|
34302
|
-
try {
|
|
34303
|
-
const db = ensureDb();
|
|
34304
|
-
const rows = await db.selectFrom("transactions").select([
|
|
34305
|
-
"tx_id",
|
|
34306
|
-
"block_height",
|
|
34307
|
-
"type",
|
|
34308
|
-
"sender",
|
|
34309
|
-
"status",
|
|
34310
|
-
"contract_id",
|
|
34311
|
-
"function_name"
|
|
34312
|
-
]).orderBy("block_height", "desc").limit(limit).execute();
|
|
34313
|
-
if (json) {
|
|
34314
|
-
console.log(JSON.stringify(rows, null, 2));
|
|
34315
|
-
process.exit(0);
|
|
34316
|
-
}
|
|
34317
|
-
console.log("");
|
|
34318
|
-
console.log(blue(`Recent Transactions (${rows.length})`));
|
|
34319
|
-
console.log("");
|
|
34320
|
-
console.log(dim(" BLOCK".padEnd(8)) + dim("TYPE".padEnd(18)) + dim("SENDER".padEnd(20)) + dim("CONTRACT/FUNCTION"));
|
|
34321
|
-
for (const row of rows) {
|
|
34322
|
-
const statusColor = row.status === "success" ? green : yellow;
|
|
34323
|
-
const contractInfo = row.contract_id ? `${row.contract_id.split(".")[1] || row.contract_id}${row.function_name ? `::${row.function_name}` : ""}` : "-";
|
|
34324
|
-
console.log(` ${statusColor(row.block_height.toString().padEnd(8))}${row.type.padEnd(18)}${`${row.sender.slice(0, 18)}... `.padEnd(20)}${contractInfo}`);
|
|
34325
|
-
}
|
|
34326
|
-
console.log("");
|
|
34327
|
-
process.exit(0);
|
|
34328
|
-
} catch (err) {
|
|
34329
|
-
error(`Failed to query transactions: ${err}`);
|
|
34330
|
-
process.exit(1);
|
|
34331
|
-
}
|
|
34332
|
-
}
|
|
34333
|
-
async function showEvents(limit, json) {
|
|
34334
|
-
try {
|
|
34335
|
-
const db = ensureDb();
|
|
34336
|
-
const rows = await db.selectFrom("events").select(["block_height", "tx_id", "event_index", "type", "data"]).orderBy("block_height", "desc").limit(limit).execute();
|
|
34337
|
-
if (json) {
|
|
34338
|
-
console.log(JSON.stringify(rows, null, 2));
|
|
34339
|
-
process.exit(0);
|
|
34340
|
-
}
|
|
34341
|
-
console.log("");
|
|
34342
|
-
console.log(blue(`Recent Events (${rows.length})`));
|
|
34343
|
-
console.log("");
|
|
34344
|
-
if (rows.length === 0) {
|
|
34345
|
-
console.log(dim(" No events found"));
|
|
34346
|
-
console.log("");
|
|
34347
|
-
process.exit(0);
|
|
34348
|
-
}
|
|
34349
|
-
console.log(dim(" BLOCK".padEnd(8)) + dim("INDEX".padEnd(8)) + dim("TYPE".padEnd(24)) + dim("TX"));
|
|
34350
|
-
for (const row of rows) {
|
|
34351
|
-
console.log(` ${row.block_height.toString().padEnd(8)}` + `${row.event_index.toString().padEnd(8)}` + `${row.type.padEnd(24)}` + `${row.tx_id.slice(0, 16)}...`);
|
|
34352
|
-
}
|
|
34353
|
-
console.log("");
|
|
34354
|
-
process.exit(0);
|
|
34355
|
-
} catch (err) {
|
|
34356
|
-
error(`Failed to query events: ${err}`);
|
|
34357
|
-
process.exit(1);
|
|
34358
|
-
}
|
|
34359
|
-
}
|
|
34360
|
-
async function showGaps(limit, json) {
|
|
34361
|
-
try {
|
|
34362
|
-
const db = ensureDb();
|
|
34363
|
-
const gaps = await findGaps(db, limit);
|
|
34364
|
-
const missing = await countMissingBlocks(db);
|
|
34365
|
-
if (json) {
|
|
34366
|
-
console.log(JSON.stringify({ gaps, totalMissingBlocks: missing }, null, 2));
|
|
34367
|
-
process.exit(0);
|
|
34368
|
-
}
|
|
34369
|
-
console.log("");
|
|
34370
|
-
if (gaps.length === 0) {
|
|
34371
|
-
console.log(green("No gaps detected — block data is contiguous"));
|
|
34372
|
-
console.log("");
|
|
34373
|
-
process.exit(0);
|
|
34374
|
-
}
|
|
34375
|
-
console.log(blue(`Block Gaps (${gaps.length})`));
|
|
34376
|
-
console.log("");
|
|
34377
|
-
console.log(dim(" GAP_START".padEnd(14)) + dim("GAP_END".padEnd(14)) + dim("SIZE"));
|
|
34378
|
-
for (const gap of gaps) {
|
|
34379
|
-
console.log(` ${gap.gapStart.toString().padEnd(14)}` + `${gap.gapEnd.toString().padEnd(14)}` + `${gap.size}`);
|
|
34380
|
-
}
|
|
34381
|
-
console.log("");
|
|
34382
|
-
console.log(`${yellow(gaps.length.toString())} gaps, ${red(missing.toString())} missing blocks`);
|
|
34383
|
-
console.log("");
|
|
34384
|
-
process.exit(0);
|
|
34385
|
-
} catch (err) {
|
|
34386
|
-
error(`Failed to query gaps: ${err}`);
|
|
34387
|
-
process.exit(1);
|
|
34388
|
-
}
|
|
34389
|
-
}
|
|
34390
|
-
async function resetDatabase(skipConfirm) {
|
|
34391
|
-
try {
|
|
34392
|
-
const db = ensureDb();
|
|
34393
|
-
const blockCount = (await db.selectFrom("blocks").select(sql`count(*)`.as("count")).executeTakeFirst())?.count ?? 0;
|
|
34394
|
-
const txCount = (await db.selectFrom("transactions").select(sql`count(*)`.as("count")).executeTakeFirst())?.count ?? 0;
|
|
34395
|
-
const eventCount = (await db.selectFrom("events").select(sql`count(*)`.as("count")).executeTakeFirst())?.count ?? 0;
|
|
34396
|
-
console.log("");
|
|
34397
|
-
console.log(yellow("This will delete all indexed blockchain data:"));
|
|
34398
|
-
console.log("");
|
|
34399
|
-
console.log(` ${red(blockCount.toString())} blocks`);
|
|
34400
|
-
console.log(` ${red(txCount.toString())} transactions`);
|
|
34401
|
-
console.log(` ${red(eventCount.toString())} events`);
|
|
34402
|
-
console.log(` ${dim("+ index_progress")}`);
|
|
34403
|
-
console.log("");
|
|
34404
|
-
console.log(dim("Note: Subgraph configurations will be preserved."));
|
|
34405
|
-
console.log("");
|
|
34406
|
-
if (!skipConfirm) {
|
|
34407
|
-
const confirmed = await confirm2({
|
|
34408
|
-
message: "Are you sure you want to reset the database?",
|
|
34409
|
-
default: false
|
|
34410
|
-
});
|
|
34411
|
-
if (!confirmed) {
|
|
34412
|
-
info("Cancelled");
|
|
34413
|
-
process.exit(0);
|
|
34414
|
-
}
|
|
34415
|
-
}
|
|
34416
|
-
info("Truncating tables...");
|
|
34417
|
-
await sql`TRUNCATE TABLE events, transactions, blocks, index_progress RESTART IDENTITY CASCADE`.execute(db);
|
|
34418
|
-
console.log("");
|
|
34419
|
-
success("Database reset complete");
|
|
34420
|
-
console.log("");
|
|
34421
|
-
console.log(dim("Run 'sl local restart' to restart the indexer for fresh sync."));
|
|
34422
|
-
console.log("");
|
|
34423
|
-
process.exit(0);
|
|
34424
|
-
} catch (err) {
|
|
34425
|
-
error(`Failed to reset database: ${err}`);
|
|
34426
|
-
process.exit(1);
|
|
34427
|
-
}
|
|
34428
|
-
}
|
|
34429
|
-
async function resyncDatabase(skipConfirm, backfill) {
|
|
34430
|
-
try {
|
|
34431
|
-
const db = ensureDb();
|
|
34432
|
-
const blockCount = (await db.selectFrom("blocks").select(sql`count(*)`.as("count")).executeTakeFirst())?.count ?? 0;
|
|
34433
|
-
console.log("");
|
|
34434
|
-
console.log(yellow("This will:"));
|
|
34435
|
-
console.log(` 1. Delete all indexed data (${red(blockCount.toString())} blocks)`);
|
|
34436
|
-
console.log(" 2. Restart the indexer for fresh sync");
|
|
34437
|
-
if (backfill) {
|
|
34438
|
-
console.log(" 3. Fetch all blocks from node (backfill)");
|
|
34439
|
-
}
|
|
34440
|
-
console.log("");
|
|
34441
|
-
console.log(dim("Note: Subgraph configurations will be preserved."));
|
|
34442
|
-
console.log("");
|
|
34443
|
-
if (!skipConfirm) {
|
|
34444
|
-
const confirmed = await confirm2({
|
|
34445
|
-
message: "Are you sure you want to resync?",
|
|
34446
|
-
default: false
|
|
34447
|
-
});
|
|
34448
|
-
if (!confirmed) {
|
|
34449
|
-
info("Cancelled");
|
|
34450
|
-
process.exit(0);
|
|
34451
|
-
}
|
|
34452
|
-
}
|
|
34453
|
-
info("Truncating tables...");
|
|
34454
|
-
await sql`TRUNCATE TABLE events, transactions, blocks, index_progress RESTART IDENTITY CASCADE`.execute(db);
|
|
34455
|
-
console.log(green(" ✓ Database reset"));
|
|
34456
|
-
const state = await loadDevState();
|
|
34457
|
-
if (state?.services?.indexer && isProcessRunning(state.services.indexer.pid)) {
|
|
34458
|
-
info("Restarting indexer...");
|
|
34459
|
-
try {
|
|
34460
|
-
process.kill(state.services.indexer.pid, "SIGTERM");
|
|
34461
|
-
await Bun.sleep(1000);
|
|
34462
|
-
} catch {}
|
|
34463
|
-
if (isProcessRunning(state.services.indexer.pid)) {
|
|
34464
|
-
try {
|
|
34465
|
-
process.kill(state.services.indexer.pid, "SIGKILL");
|
|
34466
|
-
} catch {}
|
|
34467
|
-
}
|
|
34468
|
-
console.log(green(" ✓ Indexer stopped"));
|
|
34469
|
-
console.log("");
|
|
34470
|
-
info("Run 'sl local restart' to start fresh sync");
|
|
34471
|
-
} else {
|
|
34472
|
-
console.log("");
|
|
34473
|
-
success("Database reset complete");
|
|
34474
|
-
console.log("");
|
|
34475
|
-
if (!backfill) {
|
|
34476
|
-
console.log(dim("Indexer not running. Start with 'sl local start' to begin sync."));
|
|
34477
|
-
}
|
|
34478
|
-
}
|
|
34479
|
-
if (backfill) {
|
|
34480
|
-
console.log("");
|
|
34481
|
-
info("Starting backfill from node...");
|
|
34482
|
-
const nodeClient = new StacksNodeClient;
|
|
34483
|
-
const healthy = await nodeClient.isHealthy();
|
|
34484
|
-
if (!healthy) {
|
|
34485
|
-
warn(`Cannot reach Stacks node at ${nodeClient.getRpcUrl()}`);
|
|
34486
|
-
console.log(dim("Run 'sl sync --from 1 --to <tip>' manually once the node is available."));
|
|
34487
|
-
console.log("");
|
|
34488
|
-
process.exit(0);
|
|
34489
|
-
}
|
|
34490
|
-
const nodeInfo = await nodeClient.getInfo();
|
|
34491
|
-
const tip = nodeInfo.stacks_tip_height;
|
|
34492
|
-
info(`Node tip: block ${tip}. Fetching blocks 1 to ${tip}...`);
|
|
34493
|
-
console.log(dim(`This may take a while. Run 'sl sync --from 1 --to ${tip}' if interrupted.`));
|
|
34494
|
-
console.log("");
|
|
34495
|
-
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
|
|
34496
|
-
const config = await loadConfig2();
|
|
34497
|
-
const indexerUrl = process.env.INDEXER_URL || `http://localhost:${config.ports.indexer}`;
|
|
34498
|
-
const concurrency = 5;
|
|
34499
|
-
let fetched = 0;
|
|
34500
|
-
let errors = 0;
|
|
34501
|
-
for (let i = 1;i <= tip; i += concurrency) {
|
|
34502
|
-
const batch = [];
|
|
34503
|
-
for (let j = i;j < i + concurrency && j <= tip; j++) {
|
|
34504
|
-
batch.push(j);
|
|
34505
|
-
}
|
|
34506
|
-
const results = await Promise.allSettled(batch.map(async (height) => {
|
|
34507
|
-
const block = await nodeClient.getBlock(height);
|
|
34508
|
-
if (!block)
|
|
34509
|
-
throw new Error(`Block ${height} not found`);
|
|
34510
|
-
const res = await fetch(`${indexerUrl}/new_block`, {
|
|
34511
|
-
method: "POST",
|
|
34512
|
-
headers: {
|
|
34513
|
-
"Content-Type": "application/json",
|
|
34514
|
-
"X-Source": "backfill"
|
|
34515
|
-
},
|
|
34516
|
-
body: JSON.stringify(block)
|
|
34517
|
-
});
|
|
34518
|
-
if (!res.ok)
|
|
34519
|
-
throw new Error(`Indexer rejected block ${height}: ${res.status}`);
|
|
34520
|
-
}));
|
|
34521
|
-
for (const r of results) {
|
|
34522
|
-
if (r.status === "fulfilled")
|
|
34523
|
-
fetched++;
|
|
34524
|
-
else
|
|
34525
|
-
errors++;
|
|
34526
|
-
}
|
|
34527
|
-
const pct = Math.round((fetched + errors) / tip * 100);
|
|
34528
|
-
process.stdout.write(`\r ${green(`${fetched}`)} fetched, ${errors > 0 ? red(`${errors} errors`) : "0 errors"} (${pct}%)`);
|
|
34529
|
-
}
|
|
34530
|
-
console.log("");
|
|
34531
|
-
console.log("");
|
|
34532
|
-
if (errors === 0) {
|
|
34533
|
-
success(`Backfill complete: ${fetched} blocks indexed`);
|
|
34534
|
-
} else {
|
|
34535
|
-
warn(`Backfill complete: ${fetched} indexed, ${errors} errors`);
|
|
34536
|
-
}
|
|
34537
|
-
}
|
|
34538
|
-
console.log("");
|
|
34539
|
-
process.exit(0);
|
|
34540
|
-
} catch (err) {
|
|
34541
|
-
error(`Failed to resync: ${err}`);
|
|
34542
|
-
process.exit(1);
|
|
34543
|
-
}
|
|
34544
|
-
}
|
|
34545
34165
|
// src/commands/devnet.ts
|
|
34546
34166
|
import { spawnSync } from "node:child_process";
|
|
34547
34167
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
34548
|
-
import { dirname as dirname4, join as
|
|
34168
|
+
import { dirname as dirname4, join as join6 } from "node:path";
|
|
34549
34169
|
|
|
34550
34170
|
// src/lib/devnet-compose.ts
|
|
34551
34171
|
var DEFAULT_IMAGE_OWNER = "ryanwaits";
|
|
@@ -34688,14 +34308,14 @@ volumes:
|
|
|
34688
34308
|
|
|
34689
34309
|
// src/lib/devnet-config.ts
|
|
34690
34310
|
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
34691
|
-
import { dirname as dirname3, join as
|
|
34311
|
+
import { dirname as dirname3, join as join5, parse as parsePath } from "node:path";
|
|
34692
34312
|
import { parse as parseToml } from "smol-toml";
|
|
34693
34313
|
var DEFAULT_INDEXER_OBSERVER = "host.docker.internal:3700";
|
|
34694
34314
|
function findClarinetProject(startDir) {
|
|
34695
34315
|
let dir = startDir;
|
|
34696
34316
|
const { root } = parsePath(dir);
|
|
34697
34317
|
while (true) {
|
|
34698
|
-
if (existsSync2(
|
|
34318
|
+
if (existsSync2(join5(dir, "Clarinet.toml")))
|
|
34699
34319
|
return dir;
|
|
34700
34320
|
if (dir === root)
|
|
34701
34321
|
return null;
|
|
@@ -34742,7 +34362,7 @@ stacks_node_events_observers = [${entry}]
|
|
|
34742
34362
|
|
|
34743
34363
|
// src/commands/devnet.ts
|
|
34744
34364
|
init_output();
|
|
34745
|
-
var COMPOSE_REL =
|
|
34365
|
+
var COMPOSE_REL = join6(".secondlayer", "docker-compose.yml");
|
|
34746
34366
|
function ensureDocker() {
|
|
34747
34367
|
const probe = spawnSync("docker", ["info"], { stdio: "ignore" });
|
|
34748
34368
|
if (probe.error || probe.status !== 0) {
|
|
@@ -34780,13 +34400,13 @@ function registerDevnetCommand(program2) {
|
|
|
34780
34400
|
devnet.command("status").description("Snapshot of the local stack: ingest, subgraphs, and recent activity").option("-w, --watch", "Refresh every 2s until Ctrl-C").option("--limit <n>", "Recent activity rows to show", "12").action(async (options) => {
|
|
34781
34401
|
await status(options);
|
|
34782
34402
|
});
|
|
34783
|
-
devnet.command("logs [service]").description("Tail stack logs — all services, or one of: indexer, api, subgraph-processor, postgres").option("--project <dir>", "Clarinet project directory").option("-f, --follow", "Follow log output").option("-n, --lines <n>", "Lines to show from the end of each log").
|
|
34403
|
+
devnet.command("logs [service]").description("Tail stack logs — all services, or one of: indexer, api, subgraph-processor, postgres").option("--project <dir>", "Clarinet project directory").option("-f, --follow", "Follow log output").option("-n, --lines <n>", "Lines to show from the end of each log").action(async (service, options) => {
|
|
34784
34404
|
await logs(service, options);
|
|
34785
34405
|
});
|
|
34786
34406
|
}
|
|
34787
34407
|
function resolveProject(explicit) {
|
|
34788
34408
|
if (explicit) {
|
|
34789
|
-
if (!existsSync3(
|
|
34409
|
+
if (!existsSync3(join6(explicit, "Clarinet.toml"))) {
|
|
34790
34410
|
error(`No Clarinet.toml in ${explicit}`);
|
|
34791
34411
|
process.exit(1);
|
|
34792
34412
|
}
|
|
@@ -34802,7 +34422,7 @@ function resolveProject(explicit) {
|
|
|
34802
34422
|
}
|
|
34803
34423
|
async function connect(options) {
|
|
34804
34424
|
const project = resolveProject(options.project);
|
|
34805
|
-
const devnetToml =
|
|
34425
|
+
const devnetToml = join6(project, "settings", "Devnet.toml");
|
|
34806
34426
|
mkdirSync2(dirname4(devnetToml), { recursive: true });
|
|
34807
34427
|
const result = ensureEventObserver(devnetToml, DEFAULT_INDEXER_OBSERVER);
|
|
34808
34428
|
console.log(`${green("✓")} found Clarinet.toml`);
|
|
@@ -34812,7 +34432,7 @@ async function connect(options) {
|
|
|
34812
34432
|
console.log(`${green("✓")} ${result.status === "created" ? "created" : "patched"} settings/Devnet.toml`);
|
|
34813
34433
|
console.log(dim(` + stacks_node_events_observers = ["${DEFAULT_INDEXER_OBSERVER}"]`));
|
|
34814
34434
|
}
|
|
34815
|
-
const composePath =
|
|
34435
|
+
const composePath = join6(project, COMPOSE_REL);
|
|
34816
34436
|
mkdirSync2(dirname4(composePath), { recursive: true });
|
|
34817
34437
|
writeFileSync3(composePath, buildDevnetCompose({ owner: options.owner, imageTag: options.imageTag }));
|
|
34818
34438
|
console.log(`${green("✓")} wrote ${COMPOSE_REL}`);
|
|
@@ -34836,13 +34456,13 @@ ${bold("Secondlayer stack up")}`);
|
|
|
34836
34456
|
console.log(`
|
|
34837
34457
|
${bold("Next:")}`);
|
|
34838
34458
|
console.log(` ${yellow("clarinet devnet start")} ${dim("# auto-deploys + streams to the indexer")}`);
|
|
34839
|
-
console.log(` ${yellow("SL_API_URL=http://localhost:3800
|
|
34459
|
+
console.log(` ${yellow("SL_API_URL=http://localhost:3800 SL_API_KEY=dummy sl subgraphs deploy ./subgraph.ts")}`);
|
|
34840
34460
|
console.log(dim(`
|
|
34841
34461
|
Stop with: sl devnet down`));
|
|
34842
34462
|
}
|
|
34843
34463
|
async function down(options) {
|
|
34844
34464
|
const project = resolveProject(options.project);
|
|
34845
|
-
const composePath =
|
|
34465
|
+
const composePath = join6(project, COMPOSE_REL);
|
|
34846
34466
|
if (!existsSync3(composePath)) {
|
|
34847
34467
|
error(`No ${COMPOSE_REL} in ${project} — nothing to stop. Run \`sl devnet connect\` first.`);
|
|
34848
34468
|
process.exit(1);
|
|
@@ -34857,7 +34477,7 @@ async function down(options) {
|
|
|
34857
34477
|
}
|
|
34858
34478
|
async function logs(service, options) {
|
|
34859
34479
|
const project = resolveProject(options.project);
|
|
34860
|
-
const composePath =
|
|
34480
|
+
const composePath = join6(project, COMPOSE_REL);
|
|
34861
34481
|
if (!existsSync3(composePath)) {
|
|
34862
34482
|
error(`No ${COMPOSE_REL} in ${project} — run \`sl devnet connect\` first.`);
|
|
34863
34483
|
process.exit(1);
|
|
@@ -34867,7 +34487,7 @@ async function logs(service, options) {
|
|
|
34867
34487
|
process.exit(1);
|
|
34868
34488
|
}
|
|
34869
34489
|
ensureDocker();
|
|
34870
|
-
const args = ["logs", "--tail", options.lines ??
|
|
34490
|
+
const args = ["logs", "--tail", options.lines ?? "200"];
|
|
34871
34491
|
if (options.follow)
|
|
34872
34492
|
args.push("-f");
|
|
34873
34493
|
if (service)
|
|
@@ -34984,9 +34604,9 @@ import {
|
|
|
34984
34604
|
watch,
|
|
34985
34605
|
writeFileSync as writeFileSync4
|
|
34986
34606
|
} from "node:fs";
|
|
34987
|
-
import { dirname as dirname5, join as
|
|
34607
|
+
import { dirname as dirname5, join as join7, resolve as resolve2 } from "node:path";
|
|
34988
34608
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
34989
|
-
import { confirm as
|
|
34609
|
+
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
34990
34610
|
|
|
34991
34611
|
// src/generators/subgraph-scaffold.ts
|
|
34992
34612
|
init_format();
|
|
@@ -35249,7 +34869,7 @@ init_fs();
|
|
|
35249
34869
|
init_output();
|
|
35250
34870
|
|
|
35251
34871
|
// src/commands/login.ts
|
|
35252
|
-
import { confirm as
|
|
34872
|
+
import { confirm as confirm2, input as input2 } from "@inquirer/prompts";
|
|
35253
34873
|
init_output();
|
|
35254
34874
|
init_session();
|
|
35255
34875
|
function sessionExpiry() {
|
|
@@ -35293,7 +34913,7 @@ async function runLoginFlow(options = {}) {
|
|
|
35293
34913
|
return;
|
|
35294
34914
|
}
|
|
35295
34915
|
try {
|
|
35296
|
-
const proceed = await
|
|
34916
|
+
const proceed = await confirm2({
|
|
35297
34917
|
message: "Log in as a different user?",
|
|
35298
34918
|
default: false
|
|
35299
34919
|
});
|
|
@@ -35764,7 +35384,7 @@ async function loadSubgraphWithDepCheck(absPath) {
|
|
|
35764
35384
|
if (!missingSdk)
|
|
35765
35385
|
throw err;
|
|
35766
35386
|
warn("Missing dependency: @secondlayer/subgraphs");
|
|
35767
|
-
const install = await
|
|
35387
|
+
const install = await confirm3({
|
|
35768
35388
|
message: "Install with `bun add @secondlayer/subgraphs`?",
|
|
35769
35389
|
default: true
|
|
35770
35390
|
});
|
|
@@ -35833,6 +35453,35 @@ async function writeOrPrintSubgraphSpec(spec, format, output2) {
|
|
|
35833
35453
|
await writeTextFile(outPath, text);
|
|
35834
35454
|
success(`Created ${outPath}`);
|
|
35835
35455
|
}
|
|
35456
|
+
async function specFromLocalFile(absPath, format, specOptions) {
|
|
35457
|
+
const { readFile: readFile3 } = await import("node:fs/promises");
|
|
35458
|
+
const { bundleSubgraphCode } = await import("@secondlayer/bundler");
|
|
35459
|
+
const { generateSubgraphSQL } = await import("@secondlayer/subgraphs");
|
|
35460
|
+
const {
|
|
35461
|
+
generateSubgraphAgentSchema,
|
|
35462
|
+
generateSubgraphMarkdown,
|
|
35463
|
+
generateSubgraphOpenApi
|
|
35464
|
+
} = await import("@secondlayer/shared/subgraphs/spec");
|
|
35465
|
+
const source = await readFile3(absPath, "utf8");
|
|
35466
|
+
const bundled = await bundleSubgraphCode(source);
|
|
35467
|
+
const { hash } = generateSubgraphSQL({
|
|
35468
|
+
name: bundled.name,
|
|
35469
|
+
version: bundled.version,
|
|
35470
|
+
description: bundled.description,
|
|
35471
|
+
sources: bundled.sources,
|
|
35472
|
+
schema: bundled.schema,
|
|
35473
|
+
handlers: {}
|
|
35474
|
+
});
|
|
35475
|
+
const detail = createLocalSubgraphDetail({
|
|
35476
|
+
name: bundled.name,
|
|
35477
|
+
version: bundled.version,
|
|
35478
|
+
description: bundled.description,
|
|
35479
|
+
sources: bundled.sources,
|
|
35480
|
+
schema: bundled.schema,
|
|
35481
|
+
schemaHash: hash
|
|
35482
|
+
});
|
|
35483
|
+
return format === "openapi" ? generateSubgraphOpenApi(detail, specOptions) : format === "agent" ? generateSubgraphAgentSchema(detail, specOptions) : generateSubgraphMarkdown(detail, specOptions);
|
|
35484
|
+
}
|
|
35836
35485
|
function createLocalSubgraphDetail(input3) {
|
|
35837
35486
|
const tables = {};
|
|
35838
35487
|
for (const [tableName, rawTable] of Object.entries(input3.schema)) {
|
|
@@ -35907,7 +35556,7 @@ function readCliSubgraphsDependency() {
|
|
|
35907
35556
|
return "^1.3.2";
|
|
35908
35557
|
}
|
|
35909
35558
|
function ensureScaffoldPackageJson(dir) {
|
|
35910
|
-
const packagePath =
|
|
35559
|
+
const packagePath = join7(dir, "package.json");
|
|
35911
35560
|
const subgraphsDep = readCliSubgraphsDependency();
|
|
35912
35561
|
if (!existsSync4(packagePath)) {
|
|
35913
35562
|
writeFileSync4(packagePath, `${JSON.stringify({
|
|
@@ -36025,7 +35674,7 @@ function formatSubgraphSync(sync) {
|
|
|
36025
35674
|
}
|
|
36026
35675
|
function registerSubgraphsCommand(program2) {
|
|
36027
35676
|
const subgraphs = program2.command("subgraphs").description("Manage materialized subgraphs");
|
|
36028
|
-
subgraphs.command("create <name>").
|
|
35677
|
+
subgraphs.command("create <name>").description("Create a new subgraph definition file").option("--template <slug>", `Foundation Dataset starter (one of: ${SUBGRAPH_TEMPLATE_SLUGS.join(", ")})`).addHelpText("after", `
|
|
36029
35678
|
Examples:
|
|
36030
35679
|
$ sl subgraphs new my-graph
|
|
36031
35680
|
$ sl subgraphs new token-balances --template sip-010-balances`).action(async (name, opts) => {
|
|
@@ -36070,9 +35719,9 @@ ${SUBGRAPH_TEMPLATE_SLUGS.map((s) => ` ${s.padEnd(20)} ${SUBGRAPH_TEMPLATE_DESC
|
|
|
36070
35719
|
const def = mod.default ?? mod;
|
|
36071
35720
|
const { validateSubgraphDefinition } = await import("@secondlayer/subgraphs/validate");
|
|
36072
35721
|
const { deploySchema } = await import("@secondlayer/subgraphs");
|
|
36073
|
-
const { getDb
|
|
35722
|
+
const { getDb } = await import("@secondlayer/shared/db");
|
|
36074
35723
|
validateSubgraphDefinition(def);
|
|
36075
|
-
const db =
|
|
35724
|
+
const db = getDb();
|
|
36076
35725
|
const result = await deploySchema(db, def, absPath, {
|
|
36077
35726
|
forceReindex: false
|
|
36078
35727
|
});
|
|
@@ -36114,14 +35763,14 @@ Stopped watching.`);
|
|
|
36114
35763
|
});
|
|
36115
35764
|
await new Promise(() => {});
|
|
36116
35765
|
});
|
|
36117
|
-
subgraphs.command("deploy <file>").description("Deploy a subgraph definition file").option("--start-block <n>", "Override the subgraph definition startBlock for this deploy").option("--dry-run", "Validate and preview deploy without writing changes").option("
|
|
35766
|
+
subgraphs.command("deploy <file>").description("Deploy a subgraph definition file").option("--start-block <n>", "Override the subgraph definition startBlock for this deploy").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)").action(async (file, options2) => {
|
|
36118
35767
|
try {
|
|
36119
35768
|
const absPath = resolve2(file);
|
|
36120
35769
|
const config = await loadConfig();
|
|
36121
35770
|
if (config.network !== "local") {
|
|
36122
35771
|
await requireAuth();
|
|
36123
35772
|
}
|
|
36124
|
-
const dryRun = options2.dryRun
|
|
35773
|
+
const dryRun = options2.dryRun;
|
|
36125
35774
|
const startBlock = parseStartBlockOption(options2.startBlock);
|
|
36126
35775
|
if (startBlock !== undefined) {
|
|
36127
35776
|
warn(`--start-block ${startBlock} overrides the definition's startBlock for this deploy.`);
|
|
@@ -36204,7 +35853,7 @@ Stopped watching.`);
|
|
|
36204
35853
|
info(` + columns: ${t}.${cols.join(", ")}`);
|
|
36205
35854
|
}
|
|
36206
35855
|
}
|
|
36207
|
-
const confirmed = options2.yes ||
|
|
35856
|
+
const confirmed = options2.yes || await confirm3({
|
|
36208
35857
|
message: "⚠ This will drop all data and reindex from scratch. Continue?"
|
|
36209
35858
|
});
|
|
36210
35859
|
if (!confirmed) {
|
|
@@ -36238,8 +35887,8 @@ Stopped watching.`);
|
|
|
36238
35887
|
return;
|
|
36239
35888
|
}
|
|
36240
35889
|
const { deploySchema } = await import("@secondlayer/subgraphs");
|
|
36241
|
-
const { getDb
|
|
36242
|
-
const db =
|
|
35890
|
+
const { getDb, closeDb } = await import("@secondlayer/shared/db");
|
|
35891
|
+
const db = getDb();
|
|
36243
35892
|
const result = await deploySchema(db, effectiveDef, absPath, {
|
|
36244
35893
|
version: undefined,
|
|
36245
35894
|
forceReindex: startBlock !== undefined
|
|
@@ -36355,64 +36004,24 @@ Synced — exiting watch.`));
|
|
|
36355
36004
|
handleApiError(err, "get subgraph status");
|
|
36356
36005
|
}
|
|
36357
36006
|
});
|
|
36358
|
-
subgraphs.command("spec <
|
|
36007
|
+
subgraphs.command("spec <nameOrFile>").description("Output API documentation for a deployed subgraph or a local subgraph file").option("--format <format>", "Output format: openapi, agent, markdown", "openapi").option("-o, --output <path>", "Write output to a file").option("--server <url>", "Override the server URL in generated docs").action(async (nameOrFile, options2) => {
|
|
36359
36008
|
try {
|
|
36360
36009
|
const format = parseSubgraphSpecFormat(options2.format);
|
|
36361
36010
|
const specOptions = options2.server ? { serverUrl: options2.server } : undefined;
|
|
36362
|
-
const
|
|
36363
|
-
|
|
36364
|
-
} catch (err) {
|
|
36365
|
-
if (err instanceof Error && err.message.startsWith("--format")) {
|
|
36366
|
-
error(err.message);
|
|
36367
|
-
process.exit(1);
|
|
36368
|
-
}
|
|
36369
|
-
handleApiError(err, "generate subgraph spec");
|
|
36370
|
-
}
|
|
36371
|
-
});
|
|
36372
|
-
subgraphs.command("inspect <file>").description("Output API documentation for a local subgraph file").option("--format <format>", "Output format: openapi, agent, markdown", "agent").option("-o, --output <path>", "Write output to a file").option("--server <url>", "Override the server URL in generated docs").action(async (file, options2) => {
|
|
36373
|
-
try {
|
|
36374
|
-
const format = parseSubgraphSpecFormat(options2.format);
|
|
36375
|
-
const absPath = resolve2(file);
|
|
36376
|
-
if (!existsSync4(absPath)) {
|
|
36011
|
+
const absPath = resolve2(nameOrFile);
|
|
36012
|
+
if (nameOrFile.endsWith(".ts") && !existsSync4(absPath)) {
|
|
36377
36013
|
error(`File not found: ${absPath}`);
|
|
36378
36014
|
process.exit(1);
|
|
36379
36015
|
}
|
|
36380
|
-
const
|
|
36381
|
-
const
|
|
36382
|
-
const { generateSubgraphSQL } = await import("@secondlayer/subgraphs");
|
|
36383
|
-
const {
|
|
36384
|
-
generateSubgraphAgentSchema,
|
|
36385
|
-
generateSubgraphMarkdown,
|
|
36386
|
-
generateSubgraphOpenApi
|
|
36387
|
-
} = await import("@secondlayer/shared/subgraphs/spec");
|
|
36388
|
-
const source = await readFile3(absPath, "utf8");
|
|
36389
|
-
const bundled = await bundleSubgraphCode(source);
|
|
36390
|
-
const { hash } = generateSubgraphSQL({
|
|
36391
|
-
name: bundled.name,
|
|
36392
|
-
version: bundled.version,
|
|
36393
|
-
description: bundled.description,
|
|
36394
|
-
sources: bundled.sources,
|
|
36395
|
-
schema: bundled.schema,
|
|
36396
|
-
handlers: {}
|
|
36397
|
-
});
|
|
36398
|
-
const detail = createLocalSubgraphDetail({
|
|
36399
|
-
name: bundled.name,
|
|
36400
|
-
version: bundled.version,
|
|
36401
|
-
description: bundled.description,
|
|
36402
|
-
sources: bundled.sources,
|
|
36403
|
-
schema: bundled.schema,
|
|
36404
|
-
schemaHash: hash
|
|
36405
|
-
});
|
|
36406
|
-
const specOptions = options2.server ? { serverUrl: options2.server } : undefined;
|
|
36407
|
-
const spec = format === "openapi" ? generateSubgraphOpenApi(detail, specOptions) : format === "agent" ? generateSubgraphAgentSchema(detail, specOptions) : generateSubgraphMarkdown(detail, specOptions);
|
|
36016
|
+
const isLocalFile = nameOrFile.endsWith(".ts");
|
|
36017
|
+
const spec = isLocalFile ? await specFromLocalFile(absPath, format, specOptions) : format === "openapi" ? await getSubgraphOpenApi(nameOrFile, specOptions) : format === "agent" ? await getSubgraphAgentSchema(nameOrFile, specOptions) : await getSubgraphMarkdown(nameOrFile, specOptions);
|
|
36408
36018
|
await writeOrPrintSubgraphSpec(spec, format, options2.output);
|
|
36409
36019
|
} catch (err) {
|
|
36410
36020
|
if (err instanceof Error && err.message.startsWith("--format")) {
|
|
36411
36021
|
error(err.message);
|
|
36412
36022
|
process.exit(1);
|
|
36413
36023
|
}
|
|
36414
|
-
|
|
36415
|
-
process.exit(1);
|
|
36024
|
+
handleApiError(err, "generate subgraph spec");
|
|
36416
36025
|
}
|
|
36417
36026
|
});
|
|
36418
36027
|
subgraphs.command("codegen <file>").description("Generate an ORM schema (Prisma or Drizzle) for a subgraph's tables — point it at your BYO database").option("--target <orm>", "ORM target: prisma | drizzle", "prisma").option("--schema <name>", "Postgres schema name (defaults to subgraph_<name>)").option("--env <var>", "datasource url env var", "DATABASE_URL").option("--models-only", "Emit only Prisma models (compose via prismaSchemaFolder)").option("-o, --output <path>", "Write to a file (defaults to stdout)").action(async (file, options2) => {
|
|
@@ -36457,13 +36066,13 @@ Synced — exiting watch.`));
|
|
|
36457
36066
|
process.exit(1);
|
|
36458
36067
|
}
|
|
36459
36068
|
});
|
|
36460
|
-
subgraphs.command("reindex <name>").description("Reindex a subgraph from historical blocks (drops + reprocesses)").option("--from-block <n>", "Start block height").option("--to-block <n>", "End block height").option("
|
|
36069
|
+
subgraphs.command("reindex <name>").description("Reindex a subgraph from historical blocks (drops + reprocesses)").option("--from-block <n>", "Start block height").option("--to-block <n>", "End block height").option("-y, --yes", "Skip confirmation").addHelpText("after", `
|
|
36461
36070
|
Examples:
|
|
36462
36071
|
$ sl subgraphs reindex my-graph -y
|
|
36463
36072
|
$ sl subgraphs reindex my-graph --from-block 150000 --to-block 160000 -y`).action(async (name, options2) => {
|
|
36464
36073
|
try {
|
|
36465
|
-
const fromRaw = options2.fromBlock
|
|
36466
|
-
const toRaw = options2.toBlock
|
|
36074
|
+
const fromRaw = options2.fromBlock;
|
|
36075
|
+
const toRaw = options2.toBlock;
|
|
36467
36076
|
const fromBlock = fromRaw ? Number.parseInt(fromRaw, 10) : undefined;
|
|
36468
36077
|
const toBlock = toRaw ? Number.parseInt(toRaw, 10) : undefined;
|
|
36469
36078
|
if (!options2.yes) {
|
|
@@ -36471,11 +36080,11 @@ Examples:
|
|
|
36471
36080
|
error("Interactive prompt unavailable (stdin is not a TTY). Re-run with -y to skip confirmation.");
|
|
36472
36081
|
process.exit(1);
|
|
36473
36082
|
}
|
|
36474
|
-
const { confirm:
|
|
36083
|
+
const { confirm: confirm4 } = await import("@inquirer/prompts");
|
|
36475
36084
|
const range = fromBlock !== undefined && toBlock !== undefined ? ` for blocks [${fromBlock}, ${toBlock}]` : fromBlock !== undefined ? ` from block ${fromBlock}` : toBlock !== undefined ? ` up to block ${toBlock}` : "";
|
|
36476
36085
|
let ok = false;
|
|
36477
36086
|
try {
|
|
36478
|
-
ok = await
|
|
36087
|
+
ok = await confirm4({
|
|
36479
36088
|
message: `Reindex subgraph "${name}"${range}? Existing rows in this range will be dropped and reprocessed.`,
|
|
36480
36089
|
default: false
|
|
36481
36090
|
});
|
|
@@ -36503,12 +36112,12 @@ Examples:
|
|
|
36503
36112
|
handleApiError(err, "reindex subgraph");
|
|
36504
36113
|
}
|
|
36505
36114
|
});
|
|
36506
|
-
subgraphs.command("backfill <name>").description("Backfill a block range without dropping existing data").option("--from-block <n>", "Start block height").option("--to-block <n>", "End block height").
|
|
36115
|
+
subgraphs.command("backfill <name>").description("Backfill a block range without dropping existing data").option("--from-block <n>", "Start block height").option("--to-block <n>", "End block height").addHelpText("after", `
|
|
36507
36116
|
Examples:
|
|
36508
36117
|
$ sl subgraphs backfill my-graph --from-block 150000 --to-block 160000`).action(async (name, options2) => {
|
|
36509
36118
|
try {
|
|
36510
|
-
const fromRaw = options2.fromBlock
|
|
36511
|
-
const toRaw = options2.toBlock
|
|
36119
|
+
const fromRaw = options2.fromBlock;
|
|
36120
|
+
const toRaw = options2.toBlock;
|
|
36512
36121
|
if (!fromRaw || !toRaw) {
|
|
36513
36122
|
error("--from-block and --to-block are required");
|
|
36514
36123
|
process.exit(1);
|
|
@@ -36530,7 +36139,7 @@ Examples:
|
|
|
36530
36139
|
handleApiError(err, "backfill subgraph");
|
|
36531
36140
|
}
|
|
36532
36141
|
});
|
|
36533
|
-
subgraphs.command("cancel <name>").
|
|
36142
|
+
subgraphs.command("cancel <name>").description("Cancel a running reindex or backfill operation").action(async (name) => {
|
|
36534
36143
|
try {
|
|
36535
36144
|
info(`Stopping operation for subgraph "${name}"...`);
|
|
36536
36145
|
const result = await stopSubgraphApi(name);
|
|
@@ -36633,10 +36242,10 @@ ${rows.length} row(s)`));
|
|
|
36633
36242
|
error("Interactive prompt unavailable (stdin is not a TTY). Re-run with -y to skip confirmation.");
|
|
36634
36243
|
process.exit(1);
|
|
36635
36244
|
}
|
|
36636
|
-
const { confirm:
|
|
36245
|
+
const { confirm: confirm4 } = await import("@inquirer/prompts");
|
|
36637
36246
|
let ok = false;
|
|
36638
36247
|
try {
|
|
36639
|
-
ok = await
|
|
36248
|
+
ok = await confirm4({
|
|
36640
36249
|
message: `Delete subgraph "${name}" and all its data? This cannot be undone.`
|
|
36641
36250
|
});
|
|
36642
36251
|
} catch (promptErr) {
|
|
@@ -36754,185 +36363,6 @@ Examples:
|
|
|
36754
36363
|
}
|
|
36755
36364
|
});
|
|
36756
36365
|
}
|
|
36757
|
-
// src/commands/stack.ts
|
|
36758
|
-
init_config();
|
|
36759
|
-
init_dev_state();
|
|
36760
|
-
init_docker();
|
|
36761
|
-
init_network();
|
|
36762
|
-
init_node_manager();
|
|
36763
|
-
init_output();
|
|
36764
|
-
function registerStackCommand(program2) {
|
|
36765
|
-
const stack = program2.command("stack").description("Manage the full stack");
|
|
36766
|
-
stack.command("start").description("Start the full stack (node + dev services)").option("--no-node", "Skip starting the Stacks node").option("--no-dev", "Skip starting dev services").hook("preAction", async () => {
|
|
36767
|
-
await requireLocalNetwork();
|
|
36768
|
-
}).action(async (options2) => {
|
|
36769
|
-
await stackStart(options2);
|
|
36770
|
-
});
|
|
36771
|
-
stack.command("stop").description("Stop the full stack").option("--no-node", "Skip stopping the Stacks node").option("--no-dev", "Skip stopping dev services").option("--wait", "Wait for in-flight work to drain before stopping").hook("preAction", async () => {
|
|
36772
|
-
await requireLocalNetwork();
|
|
36773
|
-
}).action(async (options2) => {
|
|
36774
|
-
await stackStop(options2);
|
|
36775
|
-
});
|
|
36776
|
-
stack.command("restart").description("Restart the full stack").option("--no-node", "Skip restarting the Stacks node").option("--no-dev", "Skip restarting dev services").hook("preAction", async () => {
|
|
36777
|
-
await requireLocalNetwork();
|
|
36778
|
-
}).action(async (options2) => {
|
|
36779
|
-
await stackStop({ ...options2, wait: false });
|
|
36780
|
-
await stackStart(options2);
|
|
36781
|
-
});
|
|
36782
|
-
}
|
|
36783
|
-
async function stackStart(options2) {
|
|
36784
|
-
const config = await loadConfig();
|
|
36785
|
-
try {
|
|
36786
|
-
await requireDocker();
|
|
36787
|
-
} catch (err) {
|
|
36788
|
-
if (err instanceof DockerNotAvailableError) {
|
|
36789
|
-
error(err.message);
|
|
36790
|
-
process.exit(1);
|
|
36791
|
-
}
|
|
36792
|
-
throw err;
|
|
36793
|
-
}
|
|
36794
|
-
const network = process.env.STACKS_NETWORK ?? config.node?.network ?? "mainnet";
|
|
36795
|
-
const validation = await validateNetworkConsistency(config);
|
|
36796
|
-
if (!validation.valid) {
|
|
36797
|
-
error("Network mismatch detected:");
|
|
36798
|
-
for (const issue of validation.issues) {
|
|
36799
|
-
console.log(red(` - ${issue}`));
|
|
36800
|
-
}
|
|
36801
|
-
console.log("");
|
|
36802
|
-
console.log(dim("Fix your config or stop mismatched containers first."));
|
|
36803
|
-
process.exit(1);
|
|
36804
|
-
}
|
|
36805
|
-
console.log("");
|
|
36806
|
-
console.log(blue("Starting Secondlayer Stack"));
|
|
36807
|
-
console.log(dim(` Network: ${network}`));
|
|
36808
|
-
console.log("");
|
|
36809
|
-
if (options2.node) {
|
|
36810
|
-
const nodePath = config.node?.installPath;
|
|
36811
|
-
if (!nodePath) {
|
|
36812
|
-
warn("No node configured, skipping. Run 'sl local node setup' to configure.");
|
|
36813
|
-
} else if (await isNodeRunning()) {
|
|
36814
|
-
info("Node already running");
|
|
36815
|
-
} else {
|
|
36816
|
-
info("Starting Stacks node...");
|
|
36817
|
-
const started = await startNodeContainers(nodePath, network);
|
|
36818
|
-
if (!started) {
|
|
36819
|
-
error("Failed to start node containers");
|
|
36820
|
-
process.exit(1);
|
|
36821
|
-
}
|
|
36822
|
-
success("Node containers started");
|
|
36823
|
-
info("Waiting for node RPC...");
|
|
36824
|
-
let rpcReady = false;
|
|
36825
|
-
for (let i = 0;i < 60; i++) {
|
|
36826
|
-
try {
|
|
36827
|
-
const res = await fetch("http://localhost:20443/v2/info", {
|
|
36828
|
-
signal: AbortSignal.timeout(2000)
|
|
36829
|
-
});
|
|
36830
|
-
if (res.ok) {
|
|
36831
|
-
rpcReady = true;
|
|
36832
|
-
break;
|
|
36833
|
-
}
|
|
36834
|
-
} catch {}
|
|
36835
|
-
await Bun.sleep(1000);
|
|
36836
|
-
}
|
|
36837
|
-
if (rpcReady) {
|
|
36838
|
-
success("Node RPC responsive");
|
|
36839
|
-
const postValidation = await validateNetworkConsistency(config);
|
|
36840
|
-
if (!postValidation.valid) {
|
|
36841
|
-
warn("Post-start chain ID mismatch:");
|
|
36842
|
-
for (const issue of postValidation.issues) {
|
|
36843
|
-
console.log(yellow(` - ${issue}`));
|
|
36844
|
-
}
|
|
36845
|
-
}
|
|
36846
|
-
} else {
|
|
36847
|
-
warn("Node RPC not responsive after 60s (may still be starting)");
|
|
36848
|
-
}
|
|
36849
|
-
}
|
|
36850
|
-
console.log("");
|
|
36851
|
-
}
|
|
36852
|
-
if (options2.dev) {
|
|
36853
|
-
if (await isDevRunning()) {
|
|
36854
|
-
info("Dev services already running");
|
|
36855
|
-
} else {
|
|
36856
|
-
const args = [
|
|
36857
|
-
"bun",
|
|
36858
|
-
"run",
|
|
36859
|
-
`${import.meta.dir}/../../bin/secondlayer.ts`,
|
|
36860
|
-
"dev",
|
|
36861
|
-
"start"
|
|
36862
|
-
];
|
|
36863
|
-
if (config.node?.installPath) {
|
|
36864
|
-
args.push("--stacks-node");
|
|
36865
|
-
}
|
|
36866
|
-
info("Starting dev services...");
|
|
36867
|
-
const proc = Bun.spawn(args, {
|
|
36868
|
-
stdin: "inherit",
|
|
36869
|
-
stdout: "inherit",
|
|
36870
|
-
stderr: "inherit"
|
|
36871
|
-
});
|
|
36872
|
-
await proc.exited;
|
|
36873
|
-
}
|
|
36874
|
-
}
|
|
36875
|
-
console.log("");
|
|
36876
|
-
success("Stack started");
|
|
36877
|
-
console.log(dim("Run 'sl status' to check health"));
|
|
36878
|
-
console.log("");
|
|
36879
|
-
}
|
|
36880
|
-
async function stackStop(options2) {
|
|
36881
|
-
console.log("");
|
|
36882
|
-
info("Stopping Secondlayer Stack...");
|
|
36883
|
-
if (options2.dev) {
|
|
36884
|
-
if (await isDevRunning()) {
|
|
36885
|
-
info("Stopping dev services...");
|
|
36886
|
-
const proc = Bun.spawn([
|
|
36887
|
-
"bun",
|
|
36888
|
-
"run",
|
|
36889
|
-
`${import.meta.dir}/../../bin/secondlayer.ts`,
|
|
36890
|
-
"dev",
|
|
36891
|
-
"stop"
|
|
36892
|
-
], { stdin: "inherit", stdout: "inherit", stderr: "inherit" });
|
|
36893
|
-
await proc.exited;
|
|
36894
|
-
} else {
|
|
36895
|
-
info("Dev services not running");
|
|
36896
|
-
}
|
|
36897
|
-
}
|
|
36898
|
-
if (options2.node) {
|
|
36899
|
-
if (await isNodeRunning()) {
|
|
36900
|
-
info("Stopping node containers...");
|
|
36901
|
-
const stopped = await stopNodeContainers();
|
|
36902
|
-
if (stopped.length > 0) {
|
|
36903
|
-
success(`Stopped: ${stopped.join(", ")}`);
|
|
36904
|
-
}
|
|
36905
|
-
} else {
|
|
36906
|
-
info("Node not running");
|
|
36907
|
-
}
|
|
36908
|
-
}
|
|
36909
|
-
try {
|
|
36910
|
-
const result = await Bun.$`docker ps -a --format json --filter "name=secondlayer-dev" --filter "name=stacks"`.quiet().nothrow();
|
|
36911
|
-
if (result.exitCode === 0) {
|
|
36912
|
-
const lines = result.stdout.toString().trim().split(`
|
|
36913
|
-
`).filter(Boolean);
|
|
36914
|
-
const orphans = [];
|
|
36915
|
-
for (const line of lines) {
|
|
36916
|
-
try {
|
|
36917
|
-
const c = JSON.parse(line);
|
|
36918
|
-
const name = c.Names || c.Name;
|
|
36919
|
-
if (name && c.Status?.includes("Exited")) {
|
|
36920
|
-
orphans.push(name);
|
|
36921
|
-
}
|
|
36922
|
-
} catch {}
|
|
36923
|
-
}
|
|
36924
|
-
if (orphans.length > 0) {
|
|
36925
|
-
info(`Cleaning up ${orphans.length} orphaned container(s)...`);
|
|
36926
|
-
for (const name of orphans) {
|
|
36927
|
-
await Bun.$`docker rm ${name}`.quiet().nothrow();
|
|
36928
|
-
}
|
|
36929
|
-
}
|
|
36930
|
-
}
|
|
36931
|
-
} catch {}
|
|
36932
|
-
console.log("");
|
|
36933
|
-
success("Stack stopped");
|
|
36934
|
-
console.log("");
|
|
36935
|
-
}
|
|
36936
36366
|
// src/commands/streams.ts
|
|
36937
36367
|
init_output();
|
|
36938
36368
|
init_resolve_auth();
|
|
@@ -37005,7 +36435,7 @@ function registerStreamsCommand(program2) {
|
|
|
37005
36435
|
process.exit(1);
|
|
37006
36436
|
}
|
|
37007
36437
|
});
|
|
37008
|
-
streams.command("events").description("List events (cursor-paginated; one page per call)").option("--types <types>", `comma-separated event types (${VALID_TYPES.join(", ")})`).option("--contract-id <id>", "filter to a single contract identifier").option("--cursor <cursor>", "start cursor (block_height:event_index)").option("--from-block <n>", "filter to blocks >= n").option("--to-block <n>", "filter to blocks <= n").option("--
|
|
36438
|
+
streams.command("events").description("List events (cursor-paginated; one page per call)").option("--types <types>", `comma-separated event types (${VALID_TYPES.join(", ")})`).option("--contract-id <id>", "filter to a single contract identifier").option("--cursor <cursor>", "start cursor (block_height:event_index)").option("--from-block <n>", "filter to blocks >= n").option("--to-block <n>", "filter to blocks <= n").option("--limit <n>", "page size (1-1000, default 100)", "100").addHelpText("after", `
|
|
37009
36439
|
Examples:
|
|
37010
36440
|
$ sl streams events --types stx_transfer,print --limit 50
|
|
37011
36441
|
$ sl streams events --contract-id SP00....token --from-block 150000 --to-block 160000
|
|
@@ -37015,8 +36445,8 @@ Examples:
|
|
|
37015
36445
|
types: parseTypes(options2.types),
|
|
37016
36446
|
contractId: options2.contractId,
|
|
37017
36447
|
cursor: options2.cursor,
|
|
37018
|
-
fromHeight: parseHeight(options2.fromBlock
|
|
37019
|
-
toHeight: parseHeight(options2.toBlock
|
|
36448
|
+
fromHeight: parseHeight(options2.fromBlock, "--from-block"),
|
|
36449
|
+
toHeight: parseHeight(options2.toBlock, "--to-block"),
|
|
37020
36450
|
limit: parseLimit(options2.limit)
|
|
37021
36451
|
});
|
|
37022
36452
|
writeData(JSON.stringify(envelope, null, 2));
|
|
@@ -37490,6 +36920,532 @@ async function runLocalDoctor(jsonOutput) {
|
|
|
37490
36920
|
}
|
|
37491
36921
|
// src/commands/local.ts
|
|
37492
36922
|
init_config();
|
|
36923
|
+
|
|
36924
|
+
// src/commands/db.ts
|
|
36925
|
+
init_config();
|
|
36926
|
+
init_dev_state();
|
|
36927
|
+
init_output();
|
|
36928
|
+
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
36929
|
+
import { getDb, sql } from "@secondlayer/shared/db";
|
|
36930
|
+
import {
|
|
36931
|
+
countMissingBlocks,
|
|
36932
|
+
findGaps
|
|
36933
|
+
} from "@secondlayer/shared/db/queries/integrity";
|
|
36934
|
+
import { StacksNodeClient } from "@secondlayer/shared/node";
|
|
36935
|
+
var DEV_DATABASE_URL = "postgres://postgres:postgres@localhost:5432/secondlayer_dev";
|
|
36936
|
+
function addDbCommand(parent) {
|
|
36937
|
+
const dbCmd = parent.command("db").description("Inspect indexer database tables").addHelpText("after", `
|
|
36938
|
+
Subcommands:
|
|
36939
|
+
blocks Show recent blocks
|
|
36940
|
+
txs Show recent transactions
|
|
36941
|
+
events Show recent events
|
|
36942
|
+
gaps Show gaps in indexed block data
|
|
36943
|
+
truncate Truncate all indexed data
|
|
36944
|
+
resync Reset database and restart indexer
|
|
36945
|
+
|
|
36946
|
+
Run \`sl db\` with no subcommand to print an overview.`).hook("preAction", async () => {
|
|
36947
|
+
await requireLocalNetwork();
|
|
36948
|
+
}).action(async () => {
|
|
36949
|
+
await showOverview(10);
|
|
36950
|
+
});
|
|
36951
|
+
dbCmd.command("blocks").description("Show recent blocks").option("--limit <n>", "Number of rows", "10").option("--json", "Output as JSON").action(async function() {
|
|
36952
|
+
const opts = this.opts();
|
|
36953
|
+
await showBlocks(Number.parseInt(opts.limit), opts.json);
|
|
36954
|
+
});
|
|
36955
|
+
dbCmd.command("txs").description("Show recent transactions").option("--limit <n>", "Number of rows", "10").option("--json", "Output as JSON").action(async function() {
|
|
36956
|
+
const opts = this.opts();
|
|
36957
|
+
await showTransactions(Number.parseInt(opts.limit), opts.json);
|
|
36958
|
+
});
|
|
36959
|
+
dbCmd.command("events").description("Show recent events").option("--limit <n>", "Number of rows", "10").option("--json", "Output as JSON").action(async function() {
|
|
36960
|
+
const opts = this.opts();
|
|
36961
|
+
await showEvents(Number.parseInt(opts.limit), opts.json);
|
|
36962
|
+
});
|
|
36963
|
+
dbCmd.command("gaps").description("Show gaps in indexed block data").option("--limit <n>", "Number of gaps to show", "50").option("--json", "Output as JSON").action(async function() {
|
|
36964
|
+
const opts = this.opts();
|
|
36965
|
+
await showGaps(Number.parseInt(opts.limit), opts.json);
|
|
36966
|
+
});
|
|
36967
|
+
dbCmd.command("truncate").description("Truncate all indexed data (blocks, txs, events)").option("-y, --yes", "Skip confirmation prompt").action(async function() {
|
|
36968
|
+
const opts = this.opts();
|
|
36969
|
+
await resetDatabase(opts.yes);
|
|
36970
|
+
});
|
|
36971
|
+
dbCmd.command("resync").description("Reset database and restart indexer for fresh sync").option("-y, --yes", "Skip confirmation prompt").option("--backfill", "After reset, fetch all blocks from node").action(async function() {
|
|
36972
|
+
const opts = this.opts();
|
|
36973
|
+
await resyncDatabase(opts.yes, opts.backfill);
|
|
36974
|
+
});
|
|
36975
|
+
}
|
|
36976
|
+
function ensureDb() {
|
|
36977
|
+
if (!process.env.DATABASE_URL) {
|
|
36978
|
+
process.env.DATABASE_URL = DEV_DATABASE_URL;
|
|
36979
|
+
}
|
|
36980
|
+
return getDb();
|
|
36981
|
+
}
|
|
36982
|
+
async function showOverview(_limit) {
|
|
36983
|
+
try {
|
|
36984
|
+
const db = ensureDb();
|
|
36985
|
+
const blockCountResult = await db.selectFrom("blocks").select(sql`count(*)`.as("count")).executeTakeFirst();
|
|
36986
|
+
const txCountResult = await db.selectFrom("transactions").select(sql`count(*)`.as("count")).executeTakeFirst();
|
|
36987
|
+
const eventCountResult = await db.selectFrom("events").select(sql`count(*)`.as("count")).executeTakeFirst();
|
|
36988
|
+
const blockCount = blockCountResult?.count ?? 0;
|
|
36989
|
+
const txCount = txCountResult?.count ?? 0;
|
|
36990
|
+
const eventCount = eventCountResult?.count ?? 0;
|
|
36991
|
+
const latest = await db.selectFrom("blocks").select(["height", "hash", "timestamp"]).orderBy("height", "desc").limit(1).executeTakeFirst();
|
|
36992
|
+
console.log("");
|
|
36993
|
+
console.log(blue("Database Overview"));
|
|
36994
|
+
console.log("");
|
|
36995
|
+
console.log(` Blocks: ${green(blockCount.toString())}`);
|
|
36996
|
+
console.log(` Transactions: ${green(txCount.toString())}`);
|
|
36997
|
+
console.log(` Events: ${green(eventCount.toString())}`);
|
|
36998
|
+
console.log("");
|
|
36999
|
+
if (latest) {
|
|
37000
|
+
const time = new Date(latest.timestamp * 1000).toISOString();
|
|
37001
|
+
console.log(blue("Latest Block"));
|
|
37002
|
+
console.log(` Height: ${latest.height}`);
|
|
37003
|
+
console.log(` Hash: ${dim(latest.hash.slice(0, 20))}...`);
|
|
37004
|
+
console.log(` Time: ${time}`);
|
|
37005
|
+
console.log("");
|
|
37006
|
+
}
|
|
37007
|
+
console.log(dim(`Use 'sl db blocks|txs|events' to see records`));
|
|
37008
|
+
console.log("");
|
|
37009
|
+
process.exit(0);
|
|
37010
|
+
} catch (err) {
|
|
37011
|
+
error(`Failed to query database: ${err}`);
|
|
37012
|
+
console.log(dim(`
|
|
37013
|
+
Make sure PostgreSQL is running.`));
|
|
37014
|
+
console.log(dim("Run 'sl local start' to start all services."));
|
|
37015
|
+
process.exit(1);
|
|
37016
|
+
}
|
|
37017
|
+
}
|
|
37018
|
+
async function showBlocks(limit, json) {
|
|
37019
|
+
try {
|
|
37020
|
+
const db = ensureDb();
|
|
37021
|
+
const rows = await db.selectFrom("blocks").select(["height", "hash", "timestamp", "canonical"]).orderBy("height", "desc").limit(limit).execute();
|
|
37022
|
+
if (json) {
|
|
37023
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
37024
|
+
process.exit(0);
|
|
37025
|
+
}
|
|
37026
|
+
console.log("");
|
|
37027
|
+
console.log(blue(`Recent Blocks (${rows.length})`));
|
|
37028
|
+
console.log("");
|
|
37029
|
+
console.log(dim(" HEIGHT".padEnd(10)) + dim("HASH".padEnd(24)) + dim("TIME"));
|
|
37030
|
+
for (const row of rows) {
|
|
37031
|
+
const time = new Date(row.timestamp * 1000).toLocaleTimeString();
|
|
37032
|
+
const canonicalMark = row.canonical ? "" : yellow(" (reorg)");
|
|
37033
|
+
console.log(` ${row.height.toString().padEnd(10)}${`${row.hash.slice(0, 20)}... `.padEnd(24)}${time}${canonicalMark}`);
|
|
37034
|
+
}
|
|
37035
|
+
console.log("");
|
|
37036
|
+
process.exit(0);
|
|
37037
|
+
} catch (err) {
|
|
37038
|
+
error(`Failed to query blocks: ${err}`);
|
|
37039
|
+
process.exit(1);
|
|
37040
|
+
}
|
|
37041
|
+
}
|
|
37042
|
+
async function showTransactions(limit, json) {
|
|
37043
|
+
try {
|
|
37044
|
+
const db = ensureDb();
|
|
37045
|
+
const rows = await db.selectFrom("transactions").select([
|
|
37046
|
+
"tx_id",
|
|
37047
|
+
"block_height",
|
|
37048
|
+
"type",
|
|
37049
|
+
"sender",
|
|
37050
|
+
"status",
|
|
37051
|
+
"contract_id",
|
|
37052
|
+
"function_name"
|
|
37053
|
+
]).orderBy("block_height", "desc").limit(limit).execute();
|
|
37054
|
+
if (json) {
|
|
37055
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
37056
|
+
process.exit(0);
|
|
37057
|
+
}
|
|
37058
|
+
console.log("");
|
|
37059
|
+
console.log(blue(`Recent Transactions (${rows.length})`));
|
|
37060
|
+
console.log("");
|
|
37061
|
+
console.log(dim(" BLOCK".padEnd(8)) + dim("TYPE".padEnd(18)) + dim("SENDER".padEnd(20)) + dim("CONTRACT/FUNCTION"));
|
|
37062
|
+
for (const row of rows) {
|
|
37063
|
+
const statusColor = row.status === "success" ? green : yellow;
|
|
37064
|
+
const contractInfo = row.contract_id ? `${row.contract_id.split(".")[1] || row.contract_id}${row.function_name ? `::${row.function_name}` : ""}` : "-";
|
|
37065
|
+
console.log(` ${statusColor(row.block_height.toString().padEnd(8))}${row.type.padEnd(18)}${`${row.sender.slice(0, 18)}... `.padEnd(20)}${contractInfo}`);
|
|
37066
|
+
}
|
|
37067
|
+
console.log("");
|
|
37068
|
+
process.exit(0);
|
|
37069
|
+
} catch (err) {
|
|
37070
|
+
error(`Failed to query transactions: ${err}`);
|
|
37071
|
+
process.exit(1);
|
|
37072
|
+
}
|
|
37073
|
+
}
|
|
37074
|
+
async function showEvents(limit, json) {
|
|
37075
|
+
try {
|
|
37076
|
+
const db = ensureDb();
|
|
37077
|
+
const rows = await db.selectFrom("events").select(["block_height", "tx_id", "event_index", "type", "data"]).orderBy("block_height", "desc").limit(limit).execute();
|
|
37078
|
+
if (json) {
|
|
37079
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
37080
|
+
process.exit(0);
|
|
37081
|
+
}
|
|
37082
|
+
console.log("");
|
|
37083
|
+
console.log(blue(`Recent Events (${rows.length})`));
|
|
37084
|
+
console.log("");
|
|
37085
|
+
if (rows.length === 0) {
|
|
37086
|
+
console.log(dim(" No events found"));
|
|
37087
|
+
console.log("");
|
|
37088
|
+
process.exit(0);
|
|
37089
|
+
}
|
|
37090
|
+
console.log(dim(" BLOCK".padEnd(8)) + dim("INDEX".padEnd(8)) + dim("TYPE".padEnd(24)) + dim("TX"));
|
|
37091
|
+
for (const row of rows) {
|
|
37092
|
+
console.log(` ${row.block_height.toString().padEnd(8)}` + `${row.event_index.toString().padEnd(8)}` + `${row.type.padEnd(24)}` + `${row.tx_id.slice(0, 16)}...`);
|
|
37093
|
+
}
|
|
37094
|
+
console.log("");
|
|
37095
|
+
process.exit(0);
|
|
37096
|
+
} catch (err) {
|
|
37097
|
+
error(`Failed to query events: ${err}`);
|
|
37098
|
+
process.exit(1);
|
|
37099
|
+
}
|
|
37100
|
+
}
|
|
37101
|
+
async function showGaps(limit, json) {
|
|
37102
|
+
try {
|
|
37103
|
+
const db = ensureDb();
|
|
37104
|
+
const gaps = await findGaps(db, limit);
|
|
37105
|
+
const missing = await countMissingBlocks(db);
|
|
37106
|
+
if (json) {
|
|
37107
|
+
console.log(JSON.stringify({ gaps, totalMissingBlocks: missing }, null, 2));
|
|
37108
|
+
process.exit(0);
|
|
37109
|
+
}
|
|
37110
|
+
console.log("");
|
|
37111
|
+
if (gaps.length === 0) {
|
|
37112
|
+
console.log(green("No gaps detected — block data is contiguous"));
|
|
37113
|
+
console.log("");
|
|
37114
|
+
process.exit(0);
|
|
37115
|
+
}
|
|
37116
|
+
console.log(blue(`Block Gaps (${gaps.length})`));
|
|
37117
|
+
console.log("");
|
|
37118
|
+
console.log(dim(" GAP_START".padEnd(14)) + dim("GAP_END".padEnd(14)) + dim("SIZE"));
|
|
37119
|
+
for (const gap of gaps) {
|
|
37120
|
+
console.log(` ${gap.gapStart.toString().padEnd(14)}` + `${gap.gapEnd.toString().padEnd(14)}` + `${gap.size}`);
|
|
37121
|
+
}
|
|
37122
|
+
console.log("");
|
|
37123
|
+
console.log(`${yellow(gaps.length.toString())} gaps, ${red(missing.toString())} missing blocks`);
|
|
37124
|
+
console.log("");
|
|
37125
|
+
process.exit(0);
|
|
37126
|
+
} catch (err) {
|
|
37127
|
+
error(`Failed to query gaps: ${err}`);
|
|
37128
|
+
process.exit(1);
|
|
37129
|
+
}
|
|
37130
|
+
}
|
|
37131
|
+
async function resetDatabase(skipConfirm) {
|
|
37132
|
+
try {
|
|
37133
|
+
const db = ensureDb();
|
|
37134
|
+
const blockCount = (await db.selectFrom("blocks").select(sql`count(*)`.as("count")).executeTakeFirst())?.count ?? 0;
|
|
37135
|
+
const txCount = (await db.selectFrom("transactions").select(sql`count(*)`.as("count")).executeTakeFirst())?.count ?? 0;
|
|
37136
|
+
const eventCount = (await db.selectFrom("events").select(sql`count(*)`.as("count")).executeTakeFirst())?.count ?? 0;
|
|
37137
|
+
console.log("");
|
|
37138
|
+
console.log(yellow("This will delete all indexed blockchain data:"));
|
|
37139
|
+
console.log("");
|
|
37140
|
+
console.log(` ${red(blockCount.toString())} blocks`);
|
|
37141
|
+
console.log(` ${red(txCount.toString())} transactions`);
|
|
37142
|
+
console.log(` ${red(eventCount.toString())} events`);
|
|
37143
|
+
console.log(` ${dim("+ index_progress")}`);
|
|
37144
|
+
console.log("");
|
|
37145
|
+
console.log(dim("Note: Subgraph configurations will be preserved."));
|
|
37146
|
+
console.log("");
|
|
37147
|
+
if (!skipConfirm) {
|
|
37148
|
+
const confirmed = await confirm4({
|
|
37149
|
+
message: "Are you sure you want to reset the database?",
|
|
37150
|
+
default: false
|
|
37151
|
+
});
|
|
37152
|
+
if (!confirmed) {
|
|
37153
|
+
info("Cancelled");
|
|
37154
|
+
process.exit(0);
|
|
37155
|
+
}
|
|
37156
|
+
}
|
|
37157
|
+
info("Truncating tables...");
|
|
37158
|
+
await sql`TRUNCATE TABLE events, transactions, blocks, index_progress RESTART IDENTITY CASCADE`.execute(db);
|
|
37159
|
+
console.log("");
|
|
37160
|
+
success("Database reset complete");
|
|
37161
|
+
console.log("");
|
|
37162
|
+
console.log(dim("Run 'sl local restart' to restart the indexer for fresh sync."));
|
|
37163
|
+
console.log("");
|
|
37164
|
+
process.exit(0);
|
|
37165
|
+
} catch (err) {
|
|
37166
|
+
error(`Failed to reset database: ${err}`);
|
|
37167
|
+
process.exit(1);
|
|
37168
|
+
}
|
|
37169
|
+
}
|
|
37170
|
+
async function resyncDatabase(skipConfirm, backfill) {
|
|
37171
|
+
try {
|
|
37172
|
+
const db = ensureDb();
|
|
37173
|
+
const blockCount = (await db.selectFrom("blocks").select(sql`count(*)`.as("count")).executeTakeFirst())?.count ?? 0;
|
|
37174
|
+
console.log("");
|
|
37175
|
+
console.log(yellow("This will:"));
|
|
37176
|
+
console.log(` 1. Delete all indexed data (${red(blockCount.toString())} blocks)`);
|
|
37177
|
+
console.log(" 2. Restart the indexer for fresh sync");
|
|
37178
|
+
if (backfill) {
|
|
37179
|
+
console.log(" 3. Fetch all blocks from node (backfill)");
|
|
37180
|
+
}
|
|
37181
|
+
console.log("");
|
|
37182
|
+
console.log(dim("Note: Subgraph configurations will be preserved."));
|
|
37183
|
+
console.log("");
|
|
37184
|
+
if (!skipConfirm) {
|
|
37185
|
+
const confirmed = await confirm4({
|
|
37186
|
+
message: "Are you sure you want to resync?",
|
|
37187
|
+
default: false
|
|
37188
|
+
});
|
|
37189
|
+
if (!confirmed) {
|
|
37190
|
+
info("Cancelled");
|
|
37191
|
+
process.exit(0);
|
|
37192
|
+
}
|
|
37193
|
+
}
|
|
37194
|
+
info("Truncating tables...");
|
|
37195
|
+
await sql`TRUNCATE TABLE events, transactions, blocks, index_progress RESTART IDENTITY CASCADE`.execute(db);
|
|
37196
|
+
console.log(green(" ✓ Database reset"));
|
|
37197
|
+
const state = await loadDevState();
|
|
37198
|
+
if (state?.services?.indexer && isProcessRunning(state.services.indexer.pid)) {
|
|
37199
|
+
info("Restarting indexer...");
|
|
37200
|
+
try {
|
|
37201
|
+
process.kill(state.services.indexer.pid, "SIGTERM");
|
|
37202
|
+
await Bun.sleep(1000);
|
|
37203
|
+
} catch {}
|
|
37204
|
+
if (isProcessRunning(state.services.indexer.pid)) {
|
|
37205
|
+
try {
|
|
37206
|
+
process.kill(state.services.indexer.pid, "SIGKILL");
|
|
37207
|
+
} catch {}
|
|
37208
|
+
}
|
|
37209
|
+
console.log(green(" ✓ Indexer stopped"));
|
|
37210
|
+
console.log("");
|
|
37211
|
+
info("Run 'sl local restart' to start fresh sync");
|
|
37212
|
+
} else {
|
|
37213
|
+
console.log("");
|
|
37214
|
+
success("Database reset complete");
|
|
37215
|
+
console.log("");
|
|
37216
|
+
if (!backfill) {
|
|
37217
|
+
console.log(dim("Indexer not running. Start with 'sl local start' to begin sync."));
|
|
37218
|
+
}
|
|
37219
|
+
}
|
|
37220
|
+
if (backfill) {
|
|
37221
|
+
console.log("");
|
|
37222
|
+
info("Starting backfill from node...");
|
|
37223
|
+
const nodeClient = new StacksNodeClient;
|
|
37224
|
+
const healthy = await nodeClient.isHealthy();
|
|
37225
|
+
if (!healthy) {
|
|
37226
|
+
warn(`Cannot reach Stacks node at ${nodeClient.getRpcUrl()}`);
|
|
37227
|
+
console.log(dim("Run 'sl sync --from 1 --to <tip>' manually once the node is available."));
|
|
37228
|
+
console.log("");
|
|
37229
|
+
process.exit(0);
|
|
37230
|
+
}
|
|
37231
|
+
const nodeInfo = await nodeClient.getInfo();
|
|
37232
|
+
const tip = nodeInfo.stacks_tip_height;
|
|
37233
|
+
info(`Node tip: block ${tip}. Fetching blocks 1 to ${tip}...`);
|
|
37234
|
+
console.log(dim(`This may take a while. Run 'sl sync --from 1 --to ${tip}' if interrupted.`));
|
|
37235
|
+
console.log("");
|
|
37236
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
|
|
37237
|
+
const config = await loadConfig2();
|
|
37238
|
+
const indexerUrl = process.env.INDEXER_URL || `http://localhost:${config.ports.indexer}`;
|
|
37239
|
+
const concurrency = 5;
|
|
37240
|
+
let fetched = 0;
|
|
37241
|
+
let errors2 = 0;
|
|
37242
|
+
for (let i = 1;i <= tip; i += concurrency) {
|
|
37243
|
+
const batch = [];
|
|
37244
|
+
for (let j = i;j < i + concurrency && j <= tip; j++) {
|
|
37245
|
+
batch.push(j);
|
|
37246
|
+
}
|
|
37247
|
+
const results = await Promise.allSettled(batch.map(async (height) => {
|
|
37248
|
+
const block = await nodeClient.getBlock(height);
|
|
37249
|
+
if (!block)
|
|
37250
|
+
throw new Error(`Block ${height} not found`);
|
|
37251
|
+
const res = await fetch(`${indexerUrl}/new_block`, {
|
|
37252
|
+
method: "POST",
|
|
37253
|
+
headers: {
|
|
37254
|
+
"Content-Type": "application/json",
|
|
37255
|
+
"X-Source": "backfill"
|
|
37256
|
+
},
|
|
37257
|
+
body: JSON.stringify(block)
|
|
37258
|
+
});
|
|
37259
|
+
if (!res.ok)
|
|
37260
|
+
throw new Error(`Indexer rejected block ${height}: ${res.status}`);
|
|
37261
|
+
}));
|
|
37262
|
+
for (const r of results) {
|
|
37263
|
+
if (r.status === "fulfilled")
|
|
37264
|
+
fetched++;
|
|
37265
|
+
else
|
|
37266
|
+
errors2++;
|
|
37267
|
+
}
|
|
37268
|
+
const pct = Math.round((fetched + errors2) / tip * 100);
|
|
37269
|
+
process.stdout.write(`\r ${green(`${fetched}`)} fetched, ${errors2 > 0 ? red(`${errors2} errors`) : "0 errors"} (${pct}%)`);
|
|
37270
|
+
}
|
|
37271
|
+
console.log("");
|
|
37272
|
+
console.log("");
|
|
37273
|
+
if (errors2 === 0) {
|
|
37274
|
+
success(`Backfill complete: ${fetched} blocks indexed`);
|
|
37275
|
+
} else {
|
|
37276
|
+
warn(`Backfill complete: ${fetched} indexed, ${errors2} errors`);
|
|
37277
|
+
}
|
|
37278
|
+
}
|
|
37279
|
+
console.log("");
|
|
37280
|
+
process.exit(0);
|
|
37281
|
+
} catch (err) {
|
|
37282
|
+
error(`Failed to resync: ${err}`);
|
|
37283
|
+
process.exit(1);
|
|
37284
|
+
}
|
|
37285
|
+
}
|
|
37286
|
+
|
|
37287
|
+
// src/commands/stack.ts
|
|
37288
|
+
init_config();
|
|
37289
|
+
init_dev_state();
|
|
37290
|
+
init_docker();
|
|
37291
|
+
init_network();
|
|
37292
|
+
init_node_manager();
|
|
37293
|
+
init_output();
|
|
37294
|
+
async function stackStart(options2) {
|
|
37295
|
+
const config = await loadConfig();
|
|
37296
|
+
try {
|
|
37297
|
+
await requireDocker();
|
|
37298
|
+
} catch (err) {
|
|
37299
|
+
if (err instanceof DockerNotAvailableError) {
|
|
37300
|
+
error(err.message);
|
|
37301
|
+
process.exit(1);
|
|
37302
|
+
}
|
|
37303
|
+
throw err;
|
|
37304
|
+
}
|
|
37305
|
+
const network = process.env.STACKS_NETWORK ?? config.node?.network ?? "mainnet";
|
|
37306
|
+
const validation = await validateNetworkConsistency(config);
|
|
37307
|
+
if (!validation.valid) {
|
|
37308
|
+
error("Network mismatch detected:");
|
|
37309
|
+
for (const issue of validation.issues) {
|
|
37310
|
+
console.log(red(` - ${issue}`));
|
|
37311
|
+
}
|
|
37312
|
+
console.log("");
|
|
37313
|
+
console.log(dim("Fix your config or stop mismatched containers first."));
|
|
37314
|
+
process.exit(1);
|
|
37315
|
+
}
|
|
37316
|
+
console.log("");
|
|
37317
|
+
console.log(blue("Starting Secondlayer Stack"));
|
|
37318
|
+
console.log(dim(` Network: ${network}`));
|
|
37319
|
+
console.log("");
|
|
37320
|
+
if (options2.node) {
|
|
37321
|
+
const nodePath = config.node?.installPath;
|
|
37322
|
+
if (!nodePath) {
|
|
37323
|
+
warn("No node configured, skipping. Run 'sl local node setup' to configure.");
|
|
37324
|
+
} else if (await isNodeRunning()) {
|
|
37325
|
+
info("Node already running");
|
|
37326
|
+
} else {
|
|
37327
|
+
info("Starting Stacks node...");
|
|
37328
|
+
const started = await startNodeContainers(nodePath, network);
|
|
37329
|
+
if (!started) {
|
|
37330
|
+
error("Failed to start node containers");
|
|
37331
|
+
process.exit(1);
|
|
37332
|
+
}
|
|
37333
|
+
success("Node containers started");
|
|
37334
|
+
info("Waiting for node RPC...");
|
|
37335
|
+
let rpcReady = false;
|
|
37336
|
+
for (let i = 0;i < 60; i++) {
|
|
37337
|
+
try {
|
|
37338
|
+
const res = await fetch("http://localhost:20443/v2/info", {
|
|
37339
|
+
signal: AbortSignal.timeout(2000)
|
|
37340
|
+
});
|
|
37341
|
+
if (res.ok) {
|
|
37342
|
+
rpcReady = true;
|
|
37343
|
+
break;
|
|
37344
|
+
}
|
|
37345
|
+
} catch {}
|
|
37346
|
+
await Bun.sleep(1000);
|
|
37347
|
+
}
|
|
37348
|
+
if (rpcReady) {
|
|
37349
|
+
success("Node RPC responsive");
|
|
37350
|
+
const postValidation = await validateNetworkConsistency(config);
|
|
37351
|
+
if (!postValidation.valid) {
|
|
37352
|
+
warn("Post-start chain ID mismatch:");
|
|
37353
|
+
for (const issue of postValidation.issues) {
|
|
37354
|
+
console.log(yellow(` - ${issue}`));
|
|
37355
|
+
}
|
|
37356
|
+
}
|
|
37357
|
+
} else {
|
|
37358
|
+
warn("Node RPC not responsive after 60s (may still be starting)");
|
|
37359
|
+
}
|
|
37360
|
+
}
|
|
37361
|
+
console.log("");
|
|
37362
|
+
}
|
|
37363
|
+
if (options2.dev) {
|
|
37364
|
+
if (await isDevRunning()) {
|
|
37365
|
+
info("Dev services already running");
|
|
37366
|
+
} else {
|
|
37367
|
+
const args = [
|
|
37368
|
+
"bun",
|
|
37369
|
+
"run",
|
|
37370
|
+
`${import.meta.dir}/../../bin/secondlayer.ts`,
|
|
37371
|
+
"dev",
|
|
37372
|
+
"start"
|
|
37373
|
+
];
|
|
37374
|
+
if (config.node?.installPath) {
|
|
37375
|
+
args.push("--stacks-node");
|
|
37376
|
+
}
|
|
37377
|
+
info("Starting dev services...");
|
|
37378
|
+
const proc = Bun.spawn(args, {
|
|
37379
|
+
stdin: "inherit",
|
|
37380
|
+
stdout: "inherit",
|
|
37381
|
+
stderr: "inherit"
|
|
37382
|
+
});
|
|
37383
|
+
await proc.exited;
|
|
37384
|
+
}
|
|
37385
|
+
}
|
|
37386
|
+
console.log("");
|
|
37387
|
+
success("Stack started");
|
|
37388
|
+
console.log(dim("Run 'sl status' to check health"));
|
|
37389
|
+
console.log("");
|
|
37390
|
+
}
|
|
37391
|
+
async function stackStop(options2) {
|
|
37392
|
+
console.log("");
|
|
37393
|
+
info("Stopping Secondlayer Stack...");
|
|
37394
|
+
if (options2.dev) {
|
|
37395
|
+
if (await isDevRunning()) {
|
|
37396
|
+
info("Stopping dev services...");
|
|
37397
|
+
const proc = Bun.spawn([
|
|
37398
|
+
"bun",
|
|
37399
|
+
"run",
|
|
37400
|
+
`${import.meta.dir}/../../bin/secondlayer.ts`,
|
|
37401
|
+
"dev",
|
|
37402
|
+
"stop"
|
|
37403
|
+
], { stdin: "inherit", stdout: "inherit", stderr: "inherit" });
|
|
37404
|
+
await proc.exited;
|
|
37405
|
+
} else {
|
|
37406
|
+
info("Dev services not running");
|
|
37407
|
+
}
|
|
37408
|
+
}
|
|
37409
|
+
if (options2.node) {
|
|
37410
|
+
if (await isNodeRunning()) {
|
|
37411
|
+
info("Stopping node containers...");
|
|
37412
|
+
const stopped = await stopNodeContainers();
|
|
37413
|
+
if (stopped.length > 0) {
|
|
37414
|
+
success(`Stopped: ${stopped.join(", ")}`);
|
|
37415
|
+
}
|
|
37416
|
+
} else {
|
|
37417
|
+
info("Node not running");
|
|
37418
|
+
}
|
|
37419
|
+
}
|
|
37420
|
+
try {
|
|
37421
|
+
const result = await Bun.$`docker ps -a --format json --filter "name=secondlayer-dev" --filter "name=stacks"`.quiet().nothrow();
|
|
37422
|
+
if (result.exitCode === 0) {
|
|
37423
|
+
const lines = result.stdout.toString().trim().split(`
|
|
37424
|
+
`).filter(Boolean);
|
|
37425
|
+
const orphans = [];
|
|
37426
|
+
for (const line of lines) {
|
|
37427
|
+
try {
|
|
37428
|
+
const c = JSON.parse(line);
|
|
37429
|
+
const name = c.Names || c.Name;
|
|
37430
|
+
if (name && c.Status?.includes("Exited")) {
|
|
37431
|
+
orphans.push(name);
|
|
37432
|
+
}
|
|
37433
|
+
} catch {}
|
|
37434
|
+
}
|
|
37435
|
+
if (orphans.length > 0) {
|
|
37436
|
+
info(`Cleaning up ${orphans.length} orphaned container(s)...`);
|
|
37437
|
+
for (const name of orphans) {
|
|
37438
|
+
await Bun.$`docker rm ${name}`.quiet().nothrow();
|
|
37439
|
+
}
|
|
37440
|
+
}
|
|
37441
|
+
}
|
|
37442
|
+
} catch {}
|
|
37443
|
+
console.log("");
|
|
37444
|
+
success("Stack stopped");
|
|
37445
|
+
console.log("");
|
|
37446
|
+
}
|
|
37447
|
+
|
|
37448
|
+
// src/commands/local.ts
|
|
37493
37449
|
init_dev_state();
|
|
37494
37450
|
init_node_manager();
|
|
37495
37451
|
init_output();
|
|
@@ -37519,6 +37475,29 @@ function registerLocalCommand(program2) {
|
|
|
37519
37475
|
const { restartDev: restartDev2 } = await Promise.resolve().then(() => (init_dev_impl(), exports_dev_impl));
|
|
37520
37476
|
await restartDev2();
|
|
37521
37477
|
});
|
|
37478
|
+
local.command("up").description("Start the full local stack (Stacks node + dev services)").option("--devnet", "Run against a local Clarinet devnet instead").option("--no-node", "Skip starting the Stacks node").option("--no-dev", "Skip starting dev services").option("--project <dir>", "[--devnet] Clarinet project directory").option("--image-tag <tag>", "[--devnet] Published image tag to run").option("--owner <owner>", "[--devnet] ghcr image owner to pull from").option("--no-up", "[--devnet] Patch config + write compose without starting").action(async (options2) => {
|
|
37479
|
+
if (options2.devnet) {
|
|
37480
|
+
await connect({
|
|
37481
|
+
project: options2.project,
|
|
37482
|
+
imageTag: options2.imageTag ?? DEFAULT_IMAGE_TAG,
|
|
37483
|
+
owner: options2.owner,
|
|
37484
|
+
up: options2.up
|
|
37485
|
+
});
|
|
37486
|
+
} else {
|
|
37487
|
+
await stackStart({ node: options2.node, dev: options2.dev });
|
|
37488
|
+
}
|
|
37489
|
+
});
|
|
37490
|
+
local.command("down").description("Stop the full local stack").option("--devnet", "Stop a local Clarinet devnet instead").option("--no-node", "Skip stopping the Stacks node").option("--no-dev", "Skip stopping dev services").option("--wait", "Wait for in-flight work to drain before stopping").option("--project <dir>", "[--devnet] Clarinet project directory").option("--purge", "[--devnet] Also remove volumes (wipes the local index)").action(async (options2) => {
|
|
37491
|
+
if (options2.devnet) {
|
|
37492
|
+
await down({ project: options2.project, purge: options2.purge });
|
|
37493
|
+
} else {
|
|
37494
|
+
await stackStop({
|
|
37495
|
+
node: options2.node,
|
|
37496
|
+
dev: options2.dev,
|
|
37497
|
+
wait: options2.wait
|
|
37498
|
+
});
|
|
37499
|
+
}
|
|
37500
|
+
});
|
|
37522
37501
|
local.command("status").description("Show local environment status").action(async () => {
|
|
37523
37502
|
await showLocalStatus();
|
|
37524
37503
|
});
|
|
@@ -37535,13 +37514,13 @@ function registerLocalCommand(program2) {
|
|
|
37535
37514
|
const { startNode: startNode2 } = await Promise.resolve().then(() => (init_node_impl(), exports_node_impl));
|
|
37536
37515
|
await startNode2(options2.path, options2.withIndexer);
|
|
37537
37516
|
});
|
|
37538
|
-
node.command("stop").description("Stop the Stacks node").option("-p, --path <path>", "Path to stacks-blockchain-docker (overrides config)").option("-y, --yes", "Skip confirmation").option("--
|
|
37517
|
+
node.command("stop").description("Stop the Stacks node").option("-p, --path <path>", "Path to stacks-blockchain-docker (overrides config)").option("-y, --yes", "Skip confirmation").option("--wait", "Wait for in-flight work to drain first").action(async (options2) => {
|
|
37539
37518
|
const { stopNode: stopNode2 } = await Promise.resolve().then(() => (init_node_impl(), exports_node_impl));
|
|
37540
|
-
await stopNode2(options2.path, options2.yes
|
|
37519
|
+
await stopNode2(options2.path, options2.yes, options2.wait);
|
|
37541
37520
|
});
|
|
37542
|
-
node.command("restart").description("Restart the Stacks node (stop then start)").option("-p, --path <path>", "Path to stacks-blockchain-docker (overrides config)").option("-y, --yes", "Skip confirmation").option("--
|
|
37521
|
+
node.command("restart").description("Restart the Stacks node (stop then start)").option("-p, --path <path>", "Path to stacks-blockchain-docker (overrides config)").option("-y, --yes", "Skip confirmation").option("--wait", "Wait for in-flight work to drain before stopping").action(async (options2) => {
|
|
37543
37522
|
const { restartNode: restartNode2 } = await Promise.resolve().then(() => (init_node_impl(), exports_node_impl));
|
|
37544
|
-
await restartNode2(options2.path, options2.yes
|
|
37523
|
+
await restartNode2(options2.path, options2.yes, options2.wait);
|
|
37545
37524
|
});
|
|
37546
37525
|
node.command("status").description("Show Stacks node status").option("-p, --path <path>", "Path to stacks-blockchain-docker (overrides config)").option("--json", "Output as JSON").action(async (options2) => {
|
|
37547
37526
|
const { showStatus: showStatus2 } = await Promise.resolve().then(() => (init_node_impl(), exports_node_impl));
|
|
@@ -37966,7 +37945,7 @@ function registerProjectCommand(program2) {
|
|
|
37966
37945
|
info(dim(`Written to ${path2}`));
|
|
37967
37946
|
info(dim("Tip: add `.secondlayer/` to .gitignore — it's account-personal."));
|
|
37968
37947
|
});
|
|
37969
|
-
project.command("get").
|
|
37948
|
+
project.command("get").description("Show the active project for this directory").option("--json", "Output as JSON").action(async (options2) => {
|
|
37970
37949
|
const config = await loadConfig();
|
|
37971
37950
|
const active = await readActiveProject(process.cwd(), config.defaultProject);
|
|
37972
37951
|
output({
|
|
@@ -38023,7 +38002,7 @@ program.hook("preAction", (thisCommand) => {
|
|
|
38023
38002
|
program.addHelpText("after", `
|
|
38024
38003
|
Quickstart:
|
|
38025
38004
|
$ sl login # Authenticate (magic-link email)
|
|
38026
|
-
$ sl subgraphs
|
|
38005
|
+
$ sl subgraphs create my-watcher --template sip-010-balances
|
|
38027
38006
|
$ sl subgraphs deploy subgraphs/my-watcher.ts
|
|
38028
38007
|
$ sl subgraphs status my-watcher
|
|
38029
38008
|
`);
|
|
@@ -38037,14 +38016,13 @@ registerLogoutCommand(program);
|
|
|
38037
38016
|
registerWhoamiCommand(program);
|
|
38038
38017
|
program.commandsGroup("Data products:");
|
|
38039
38018
|
registerSubgraphsCommand(program);
|
|
38040
|
-
registerCreateCommand(program);
|
|
38041
38019
|
registerSubscriptionsCommand(program);
|
|
38042
38020
|
registerStreamsCommand(program);
|
|
38043
38021
|
registerDatasetsCommand(program);
|
|
38044
38022
|
program.commandsGroup("Project & codegen:");
|
|
38045
38023
|
registerProjectCommand(program);
|
|
38046
|
-
var configureGenerate = (cmd) => cmd.option("-c, --config <path>", "Path to config file").option("-o, --output <path>", "Output file path (required when using direct files)").option("
|
|
38047
|
-
options3.out = options3.output
|
|
38024
|
+
var configureGenerate = (cmd) => cmd.option("-c, --config <path>", "Path to config file").option("-o, --output <path>", "Output file path (required when using direct files)").option("-k, --api-key <key>", "Stacks node API key for direct RPC URLs").option("-w, --watch", "Watch for changes").action(async (files, options3) => {
|
|
38025
|
+
options3.out = options3.output;
|
|
38048
38026
|
const { generate: generate2 } = await Promise.resolve().then(() => (init_generate(), exports_generate));
|
|
38049
38027
|
await generate2(files, options3);
|
|
38050
38028
|
});
|
|
@@ -38054,20 +38032,16 @@ Examples:
|
|
|
38054
38032
|
$ sl contracts generate
|
|
38055
38033
|
$ sl contracts generate ./contracts/pool.clar -o ./src/generated.ts
|
|
38056
38034
|
$ sl contracts generate --config secondlayer.config.ts --watch`);
|
|
38057
|
-
configureGenerate(program.command("generate [files...]").aliases(["gen"]).description("Deprecated: use `sl contracts generate`"));
|
|
38058
38035
|
program.commandsGroup("Local development:");
|
|
38059
38036
|
registerLocalCommand(program);
|
|
38060
38037
|
registerDevnetCommand(program);
|
|
38061
|
-
registerStackCommand(program);
|
|
38062
|
-
registerDbCommand(program);
|
|
38063
38038
|
program.commandsGroup("Diagnostics:");
|
|
38064
38039
|
registerStatusCommand(program);
|
|
38065
38040
|
registerDoctorCommand(program);
|
|
38066
38041
|
registerConfigCommand(program);
|
|
38067
38042
|
program.commandsGroup("Account:");
|
|
38068
38043
|
registerAccountCommand(program);
|
|
38069
|
-
registerBillingCommand(program);
|
|
38070
38044
|
program.parse();
|
|
38071
38045
|
|
|
38072
|
-
//# debugId=
|
|
38046
|
+
//# debugId=E63D3732E427AF4164756E2164756E21
|
|
38073
38047
|
//# sourceMappingURL=cli.js.map
|