@secondlayer/cli 12.0.0 → 12.1.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 +2 -2
- package/dist/cli.js +106 -31
- package/dist/cli.js.map +7 -6
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -181,8 +181,8 @@ const supply = await token.vars.totalSupply.get()
|
|
|
181
181
|
## Frozen commands
|
|
182
182
|
|
|
183
183
|
Local-dev periphery: still functional, hidden from `secondlayer --help`, no further
|
|
184
|
-
investment — may be removed in a future major.
|
|
185
|
-
|
|
184
|
+
investment — may be removed in a future major. `secondlayer subgraphs
|
|
185
|
+
create/deploy` against your own instance is the supported path.
|
|
186
186
|
|
|
187
187
|
- `secondlayer local up/down/start/stop/restart/status/logs` — local stack + dev services
|
|
188
188
|
- `secondlayer local node ...` — local Stacks node management
|
package/dist/cli.js
CHANGED
|
@@ -34320,14 +34320,14 @@ init_esm();
|
|
|
34320
34320
|
// package.json
|
|
34321
34321
|
var package_default = {
|
|
34322
34322
|
name: "@secondlayer/cli",
|
|
34323
|
-
version: "12.
|
|
34323
|
+
version: "12.1.0",
|
|
34324
|
+
description: "Run a Stacks index on your own hardware — set it up, restore verified history, and keep it current.",
|
|
34324
34325
|
repository: {
|
|
34325
34326
|
type: "git",
|
|
34326
34327
|
url: "git+https://github.com/ryanwaits/secondlayer.git",
|
|
34327
34328
|
directory: "packages/cli"
|
|
34328
34329
|
},
|
|
34329
34330
|
homepage: "https://github.com/ryanwaits/secondlayer/tree/main/packages/cli#readme",
|
|
34330
|
-
description: "CLI for subgraphs and blockchain indexing on Stacks",
|
|
34331
34331
|
type: "module",
|
|
34332
34332
|
bin: {
|
|
34333
34333
|
secondlayer: "./dist/cli.js",
|
|
@@ -34363,10 +34363,10 @@ var package_default = {
|
|
|
34363
34363
|
"@inquirer/prompts": "^8.2.0",
|
|
34364
34364
|
"@secondlayer/bundler": "^0.5.1",
|
|
34365
34365
|
"@secondlayer/scaffold": "^1.5.4",
|
|
34366
|
-
"@secondlayer/sdk": "^8.0.
|
|
34367
|
-
"@secondlayer/shared": "^
|
|
34366
|
+
"@secondlayer/sdk": "^8.0.1",
|
|
34367
|
+
"@secondlayer/shared": "^10.0.0",
|
|
34368
34368
|
"@secondlayer/stacks": "^4.0.0",
|
|
34369
|
-
"@secondlayer/subgraphs": "^4.0.
|
|
34369
|
+
"@secondlayer/subgraphs": "^4.0.2",
|
|
34370
34370
|
esbuild: "^0.27.4",
|
|
34371
34371
|
"smol-toml": "^1.6.1",
|
|
34372
34372
|
zod: "^4.3.6"
|
|
@@ -39006,19 +39006,91 @@ Exit codes:
|
|
|
39006
39006
|
function registerBootstrapCommand(program2) {
|
|
39007
39007
|
attachBootstrapCommand(program2.command("bootstrap").description("Restore chain history from a verified archive instead of syncing from genesis"));
|
|
39008
39008
|
}
|
|
39009
|
+
// src/commands/uninstall.ts
|
|
39010
|
+
init_output();
|
|
39011
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
39012
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
|
|
39013
|
+
import { join as join17, resolve as resolve13 } from "node:path";
|
|
39014
|
+
import { planUninstall, uninstallCommand } from "@secondlayer/shared/runtime";
|
|
39015
|
+
var UNINSTALL_EXIT = { OK: 0, FAILED: 1, REFUSED: 2 };
|
|
39016
|
+
var DEFAULT_COMPOSE = "docker/oss/docker-compose.yml";
|
|
39017
|
+
function bundleHasKeys(bundleDir) {
|
|
39018
|
+
const manifest = join17(bundleDir, "manifest.json");
|
|
39019
|
+
if (!existsSync8(manifest))
|
|
39020
|
+
return false;
|
|
39021
|
+
try {
|
|
39022
|
+
const parsed = JSON.parse(readFileSync7(manifest, "utf8"));
|
|
39023
|
+
return parsed.secrets != null;
|
|
39024
|
+
} catch {
|
|
39025
|
+
return false;
|
|
39026
|
+
}
|
|
39027
|
+
}
|
|
39028
|
+
function registerUninstallCommand(program2) {
|
|
39029
|
+
program2.command("uninstall").description("Stop the stack; the index, chainstate, and keys survive").option("--compose <file>", "compose file to tear down", DEFAULT_COMPOSE).option("--purge", "also destroy the volumes (wipes the index)").option("--yes", "confirm a purge").option("--backup <dir>", "a bundle proving the keys exist elsewhere (required to purge)").option("--apply", "actually run it (default is a dry run)").option("--json", "Output as JSON").action(async (opts) => {
|
|
39030
|
+
const dataDir = process.env.DATA_DIR ?? "./data";
|
|
39031
|
+
const secretsPresent = !!process.env.SECONDLAYER_SECRETS_KEY || existsSync8(resolve13(process.cwd(), ".env.local"));
|
|
39032
|
+
const keysBackedUp = opts.backup ? bundleHasKeys(resolve13(opts.backup)) : false;
|
|
39033
|
+
const decision = planUninstall({
|
|
39034
|
+
purge: opts.purge === true,
|
|
39035
|
+
confirmed: opts.yes === true,
|
|
39036
|
+
keysBackedUp,
|
|
39037
|
+
secretsPresent,
|
|
39038
|
+
dataDir
|
|
39039
|
+
});
|
|
39040
|
+
if (!decision.ok) {
|
|
39041
|
+
printError(decision.reason);
|
|
39042
|
+
process.exit(UNINSTALL_EXIT.REFUSED);
|
|
39043
|
+
}
|
|
39044
|
+
const cmd = uninstallCommand(decision.plan, opts.compose);
|
|
39045
|
+
if (!opts.apply) {
|
|
39046
|
+
output({
|
|
39047
|
+
json: opts.json,
|
|
39048
|
+
data: { dryRun: true, plan: decision.plan, command: cmd },
|
|
39049
|
+
human: () => {
|
|
39050
|
+
note("dry run — nothing removed. Pass --apply to proceed.");
|
|
39051
|
+
note(`would run: docker ${cmd.join(" ")}`);
|
|
39052
|
+
for (const p2 of decision.plan.preserves) {
|
|
39053
|
+
note(`preserved · ${p2.what}: ${p2.detail}`);
|
|
39054
|
+
}
|
|
39055
|
+
for (const d2 of decision.plan.destroys) {
|
|
39056
|
+
warn(`DESTROYED · volume ${d2}`);
|
|
39057
|
+
}
|
|
39058
|
+
}
|
|
39059
|
+
});
|
|
39060
|
+
return;
|
|
39061
|
+
}
|
|
39062
|
+
const res = spawnSync3("docker", cmd, { stdio: "inherit" });
|
|
39063
|
+
if ((res.status ?? 1) !== 0) {
|
|
39064
|
+
printError(`docker ${cmd.join(" ")} exited ${res.status}`);
|
|
39065
|
+
process.exit(UNINSTALL_EXIT.FAILED);
|
|
39066
|
+
}
|
|
39067
|
+
output({
|
|
39068
|
+
json: opts.json,
|
|
39069
|
+
data: { removed: true, plan: decision.plan },
|
|
39070
|
+
human: () => {
|
|
39071
|
+
success("stack removed");
|
|
39072
|
+
for (const w2 of decision.warnings)
|
|
39073
|
+
warn(w2);
|
|
39074
|
+
for (const p2 of decision.plan.preserves) {
|
|
39075
|
+
note(`preserved · ${p2.what}: ${p2.detail}`);
|
|
39076
|
+
}
|
|
39077
|
+
}
|
|
39078
|
+
});
|
|
39079
|
+
});
|
|
39080
|
+
}
|
|
39009
39081
|
// src/commands/backup.ts
|
|
39010
39082
|
init_output();
|
|
39011
39083
|
import { createHash as createHash4 } from "node:crypto";
|
|
39012
39084
|
import {
|
|
39013
39085
|
chmodSync,
|
|
39014
|
-
existsSync as
|
|
39086
|
+
existsSync as existsSync9,
|
|
39015
39087
|
mkdirSync as mkdirSync6,
|
|
39016
|
-
readFileSync as
|
|
39088
|
+
readFileSync as readFileSync8,
|
|
39017
39089
|
statSync as statSync3,
|
|
39018
39090
|
writeFileSync as writeFileSync7
|
|
39019
39091
|
} from "node:fs";
|
|
39020
|
-
import { join as
|
|
39021
|
-
import { getDb as getDb5, sql as sql4 } from "@secondlayer/shared/db";
|
|
39092
|
+
import { join as join18, resolve as resolve14 } from "node:path";
|
|
39093
|
+
import { closeDb, getDb as getDb5, sql as sql4 } from "@secondlayer/shared/db";
|
|
39022
39094
|
import {
|
|
39023
39095
|
decryptBundle,
|
|
39024
39096
|
encryptBundle,
|
|
@@ -39044,7 +39116,7 @@ function readSecretsKey() {
|
|
|
39044
39116
|
return key ? key : undefined;
|
|
39045
39117
|
}
|
|
39046
39118
|
function sha256File(path11) {
|
|
39047
|
-
return createHash4("sha256").update(
|
|
39119
|
+
return createHash4("sha256").update(readFileSync8(path11)).digest("hex");
|
|
39048
39120
|
}
|
|
39049
39121
|
async function readScope() {
|
|
39050
39122
|
const db = getDb5();
|
|
@@ -39076,6 +39148,7 @@ function attachBackupCommand(cmd) {
|
|
|
39076
39148
|
return cmd.requiredOption("--out <dir>", "directory to write the bundle into").option("--passphrase <passphrase>", "encrypt bundled secrets (or set SECONDLAYER_BACKUP_PASSPHRASE)").option("--no-secrets", "omit keys; the bundle restores data only").option("--json", "Output as JSON").action(async (opts) => {
|
|
39077
39149
|
try {
|
|
39078
39150
|
await runBackup(opts);
|
|
39151
|
+
await closeDb();
|
|
39079
39152
|
} catch (error2) {
|
|
39080
39153
|
printError(error2 instanceof Error ? error2.message : String(error2));
|
|
39081
39154
|
process.exit(BACKUP_EXIT.FAILED);
|
|
@@ -39104,10 +39177,10 @@ async function runBackup(opts) {
|
|
|
39104
39177
|
printError(plan.reason);
|
|
39105
39178
|
process.exit(BACKUP_EXIT.REFUSED);
|
|
39106
39179
|
}
|
|
39107
|
-
const outDir =
|
|
39180
|
+
const outDir = resolve14(opts.out);
|
|
39108
39181
|
mkdirSync6(outDir, { recursive: true });
|
|
39109
39182
|
console.error("dumping database…");
|
|
39110
|
-
const dumpPath =
|
|
39183
|
+
const dumpPath = join18(outDir, DB_FILE);
|
|
39111
39184
|
const dump = await runProcess(["pg_dump", "-Fc", "--no-owner", "--no-acl", "-f", dumpPath, databaseUrl], "pg_dump");
|
|
39112
39185
|
if (!dump.ok) {
|
|
39113
39186
|
printError(dump.stderr);
|
|
@@ -39126,7 +39199,7 @@ async function runBackup(opts) {
|
|
|
39126
39199
|
const lines2 = SECRET_ENV_KEYS.filter((k2) => process.env[k2]).map((k2) => `${k2}=${process.env[k2]}`);
|
|
39127
39200
|
const sealed = encryptBundle(lines2.join(`
|
|
39128
39201
|
`), passphrase);
|
|
39129
|
-
const secretsPath =
|
|
39202
|
+
const secretsPath = join18(outDir, SECRETS_FILE);
|
|
39130
39203
|
writeFileSync7(secretsPath, sealed);
|
|
39131
39204
|
chmodSync(secretsPath, 384);
|
|
39132
39205
|
manifest.secrets = {
|
|
@@ -39135,7 +39208,7 @@ async function runBackup(opts) {
|
|
|
39135
39208
|
canary: sealKeyCanary(secretsKey)
|
|
39136
39209
|
};
|
|
39137
39210
|
}
|
|
39138
|
-
writeFileSync7(
|
|
39211
|
+
writeFileSync7(join18(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
|
|
39139
39212
|
`);
|
|
39140
39213
|
output({
|
|
39141
39214
|
json: opts.json,
|
|
@@ -39155,6 +39228,7 @@ function attachRestoreCommand(cmd) {
|
|
|
39155
39228
|
return cmd.requiredOption("--from <dir>", "bundle directory to restore from").option("--passphrase <passphrase>", "decrypt bundled secrets (or set SECONDLAYER_BACKUP_PASSPHRASE)").option("--apply", "actually restore (default is a dry run)").option("--force", "restore over a database that already holds chain data").option("--json", "Output as JSON").action(async (opts) => {
|
|
39156
39229
|
try {
|
|
39157
39230
|
await runRestore(opts);
|
|
39231
|
+
await closeDb();
|
|
39158
39232
|
} catch (error2) {
|
|
39159
39233
|
printError(error2 instanceof Error ? error2.message : String(error2));
|
|
39160
39234
|
process.exit(BACKUP_EXIT.FAILED);
|
|
@@ -39163,14 +39237,14 @@ function attachRestoreCommand(cmd) {
|
|
|
39163
39237
|
}
|
|
39164
39238
|
async function runRestore(opts) {
|
|
39165
39239
|
const databaseUrl = requireDatabaseUrl();
|
|
39166
|
-
const dir =
|
|
39167
|
-
const manifestPath =
|
|
39168
|
-
if (!
|
|
39240
|
+
const dir = resolve14(opts.from);
|
|
39241
|
+
const manifestPath = join18(dir, MANIFEST_FILE);
|
|
39242
|
+
if (!existsSync9(manifestPath)) {
|
|
39169
39243
|
printError(`no ${MANIFEST_FILE} in ${dir}`);
|
|
39170
39244
|
process.exit(BACKUP_EXIT.REFUSED);
|
|
39171
39245
|
}
|
|
39172
|
-
const manifest = JSON.parse(
|
|
39173
|
-
const dumpPath =
|
|
39246
|
+
const manifest = JSON.parse(readFileSync8(manifestPath, "utf8"));
|
|
39247
|
+
const dumpPath = join18(dir, manifest.db?.file ?? DB_FILE);
|
|
39174
39248
|
if (manifest.db && sha256File(dumpPath) !== manifest.db.sha256) {
|
|
39175
39249
|
printError("the database dump does not match the digest in the manifest; this bundle is corrupt or truncated");
|
|
39176
39250
|
process.exit(BACKUP_EXIT.REFUSED);
|
|
@@ -39184,7 +39258,7 @@ async function runRestore(opts) {
|
|
|
39184
39258
|
process.exit(BACKUP_EXIT.REFUSED);
|
|
39185
39259
|
}
|
|
39186
39260
|
try {
|
|
39187
|
-
const env2 = decryptBundle(
|
|
39261
|
+
const env2 = decryptBundle(readFileSync8(join18(dir, manifest.secrets.file)), passphrase);
|
|
39188
39262
|
bundleKey = env2.split(`
|
|
39189
39263
|
`).find((l2) => l2.startsWith("SECONDLAYER_SECRETS_KEY="))?.slice("SECONDLAYER_SECRETS_KEY=".length).trim();
|
|
39190
39264
|
} catch {
|
|
@@ -39263,8 +39337,8 @@ function registerRestoreCommand(program2) {
|
|
|
39263
39337
|
}
|
|
39264
39338
|
// src/lib/instance-init.ts
|
|
39265
39339
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
39266
|
-
import { existsSync as
|
|
39267
|
-
import { resolve as
|
|
39340
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "node:fs";
|
|
39341
|
+
import { resolve as resolve15 } from "node:path";
|
|
39268
39342
|
import { generateEd25519KeyPair } from "@secondlayer/shared/crypto/ed25519";
|
|
39269
39343
|
import { INSTANCE_NETWORKS } from "@secondlayer/shared/db/queries/instance";
|
|
39270
39344
|
var INSTANCE_ENV_FILE = ".env.local";
|
|
@@ -39298,9 +39372,9 @@ function unescapeEnvValue(raw) {
|
|
|
39298
39372
|
`).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
39299
39373
|
}
|
|
39300
39374
|
function readEnvValue(envPath, key) {
|
|
39301
|
-
if (!
|
|
39375
|
+
if (!existsSync10(envPath))
|
|
39302
39376
|
return null;
|
|
39303
|
-
const match =
|
|
39377
|
+
const match = readFileSync9(envPath, "utf8").match(new RegExp(`^${key}=(.*)$`, "m"));
|
|
39304
39378
|
return match ? unescapeEnvValue(match[1]) : null;
|
|
39305
39379
|
}
|
|
39306
39380
|
function buildInstanceEnv(input4) {
|
|
@@ -39343,12 +39417,12 @@ function renderInstanceEnv(env2) {
|
|
|
39343
39417
|
`);
|
|
39344
39418
|
}
|
|
39345
39419
|
function writeInstanceEnv(cwd, env2, file = INSTANCE_ENV_FILE) {
|
|
39346
|
-
const path11 =
|
|
39420
|
+
const path11 = resolve15(cwd, file);
|
|
39347
39421
|
writeFileSync8(path11, renderInstanceEnv(env2), { mode: 384 });
|
|
39348
39422
|
return path11;
|
|
39349
39423
|
}
|
|
39350
39424
|
function loadExistingInstanceEnv(cwd, file = INSTANCE_ENV_FILE) {
|
|
39351
|
-
const path11 =
|
|
39425
|
+
const path11 = resolve15(cwd, file);
|
|
39352
39426
|
return {
|
|
39353
39427
|
INSTANCE_TOKEN: readEnvValue(path11, "INSTANCE_TOKEN"),
|
|
39354
39428
|
SECONDLAYER_SECRETS_KEY: readEnvValue(path11, "SECONDLAYER_SECRETS_KEY"),
|
|
@@ -39502,7 +39576,7 @@ function registerStartCommand(program2) {
|
|
|
39502
39576
|
}
|
|
39503
39577
|
// src/cli.ts
|
|
39504
39578
|
var { version } = package_default;
|
|
39505
|
-
program.name("secondlayer").alias("sl").description("Secondlayer CLI —
|
|
39579
|
+
program.name("secondlayer").alias("sl").description("Secondlayer CLI — run a Stacks index on your own hardware").version(version).option("--network <network>", "Override network (local, testnet, mainnet)").option("--api-key <key>", "API credential (overrides SL_API_KEY)").option("--api-url <url>", "API endpoint (overrides SL_API_URL)").showSuggestionAfterError(true).showHelpAfterError("(run `secondlayer --help` to see available commands)");
|
|
39506
39580
|
program.hook("preAction", (thisCommand) => {
|
|
39507
39581
|
const { network, apiKey, apiUrl } = thisCommand.opts();
|
|
39508
39582
|
if (network)
|
|
@@ -39516,7 +39590,7 @@ program.addHelpText("after", `
|
|
|
39516
39590
|
Quickstart:
|
|
39517
39591
|
$ secondlayer init --network mainnet
|
|
39518
39592
|
$ secondlayer bootstrap --against <manifest>
|
|
39519
|
-
$ secondlayer subgraphs create my-watcher --
|
|
39593
|
+
$ secondlayer subgraphs create my-watcher --from-contract SP....my-contract
|
|
39520
39594
|
$ secondlayer subgraphs deploy subgraphs/my-watcher.ts
|
|
39521
39595
|
`);
|
|
39522
39596
|
program.commandsGroup("Getting started:");
|
|
@@ -39528,7 +39602,7 @@ registerConsoleCommand(program);
|
|
|
39528
39602
|
registerLoginCommand(program);
|
|
39529
39603
|
registerLogoutCommand(program);
|
|
39530
39604
|
registerWhoamiCommand(program);
|
|
39531
|
-
program.commandsGroup("
|
|
39605
|
+
program.commandsGroup("Your data:");
|
|
39532
39606
|
registerSubgraphsCommand(program);
|
|
39533
39607
|
registerSubscriptionsCommand(program);
|
|
39534
39608
|
registerStreamsCommand(program);
|
|
@@ -39560,9 +39634,10 @@ registerVerifyCommand(program);
|
|
|
39560
39634
|
registerRepairCommand(program);
|
|
39561
39635
|
registerBackupCommand(program);
|
|
39562
39636
|
registerRestoreCommand(program);
|
|
39563
|
-
program
|
|
39637
|
+
registerUninstallCommand(program);
|
|
39638
|
+
program.commandsGroup("Archive:");
|
|
39564
39639
|
registerCreditsCommand(program);
|
|
39565
39640
|
program.parse();
|
|
39566
39641
|
|
|
39567
|
-
//# debugId=
|
|
39642
|
+
//# debugId=135579D3DFB23FDC64756E2164756E21
|
|
39568
39643
|
//# sourceMappingURL=cli.js.map
|