@secondlayer/cli 5.3.0 → 5.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +165 -20
- package/dist/cli.js.map +7 -6
- package/package.json +3 -3
- package/templates/subscriptions/cloudflare/.env.example +1 -0
- package/templates/subscriptions/inngest/.env.example +2 -0
- package/templates/subscriptions/inngest/package.json +1 -1
- package/templates/subscriptions/node/README.md +3 -1
- package/templates/subscriptions/trigger/.env.example +2 -0
package/dist/cli.js
CHANGED
|
@@ -32321,7 +32321,7 @@ var {
|
|
|
32321
32321
|
// package.json
|
|
32322
32322
|
var package_default = {
|
|
32323
32323
|
name: "@secondlayer/cli",
|
|
32324
|
-
version: "5.
|
|
32324
|
+
version: "5.4.1",
|
|
32325
32325
|
description: "CLI for subgraphs and blockchain indexing on Stacks",
|
|
32326
32326
|
type: "module",
|
|
32327
32327
|
bin: {
|
|
@@ -32365,9 +32365,9 @@ var package_default = {
|
|
|
32365
32365
|
"@inquirer/prompts": "^8.2.0",
|
|
32366
32366
|
"@secondlayer/bundler": "^0.3.5",
|
|
32367
32367
|
"@secondlayer/sdk": "^3.6.0",
|
|
32368
|
-
"@secondlayer/shared": "^6.4.
|
|
32368
|
+
"@secondlayer/shared": "^6.4.1",
|
|
32369
32369
|
"@secondlayer/stacks": "^2.2.0",
|
|
32370
|
-
"@secondlayer/subgraphs": "^2.0.
|
|
32370
|
+
"@secondlayer/subgraphs": "^2.0.2",
|
|
32371
32371
|
"@biomejs/js-api": "^0.7.0",
|
|
32372
32372
|
"@biomejs/wasm-nodejs": "^1.9.0",
|
|
32373
32373
|
esbuild: "^0.19.0",
|
|
@@ -33095,6 +33095,21 @@ async function validateSubscriptionTargetFromApi(client, input) {
|
|
|
33095
33095
|
}
|
|
33096
33096
|
}
|
|
33097
33097
|
|
|
33098
|
+
// src/utils/urls.ts
|
|
33099
|
+
function deriveBaseUrl(apiUrl) {
|
|
33100
|
+
const override = process.env.SL_DASHBOARD_URL?.trim();
|
|
33101
|
+
if (override)
|
|
33102
|
+
return override.replace(/\/$/, "");
|
|
33103
|
+
try {
|
|
33104
|
+
const url = new URL(apiUrl);
|
|
33105
|
+
url.hostname = url.hostname.replace(/^api\./, "");
|
|
33106
|
+
url.pathname = "/";
|
|
33107
|
+
return url.toString().replace(/\/$/, "");
|
|
33108
|
+
} catch {
|
|
33109
|
+
return apiUrl.replace(/\/$/, "");
|
|
33110
|
+
}
|
|
33111
|
+
}
|
|
33112
|
+
|
|
33098
33113
|
// src/commands/create.ts
|
|
33099
33114
|
var RUNTIMES = ["inngest", "trigger", "cloudflare", "node"];
|
|
33100
33115
|
var FORMAT_BY_RUNTIME = {
|
|
@@ -33158,6 +33173,10 @@ function buildSubscriptionAuthConfig(authToken) {
|
|
|
33158
33173
|
return { authType: "bearer", token };
|
|
33159
33174
|
}
|
|
33160
33175
|
async function createSubscription(name, opts) {
|
|
33176
|
+
if (opts.runtime && !RUNTIMES.includes(opts.runtime)) {
|
|
33177
|
+
error(`Unknown --runtime "${opts.runtime}". Valid: ${RUNTIMES.join(", ")}`);
|
|
33178
|
+
process.exit(1);
|
|
33179
|
+
}
|
|
33161
33180
|
const { runtime, subgraph, table, url } = await promptFor(name, opts);
|
|
33162
33181
|
let filter;
|
|
33163
33182
|
let authConfig;
|
|
@@ -33220,31 +33239,52 @@ async function createSubscription(name, opts) {
|
|
|
33220
33239
|
info("Template copied, but the subscription was not created. Provision it in the dashboard, or remove the template directory and rerun this command after fixing the API error.");
|
|
33221
33240
|
}
|
|
33222
33241
|
}
|
|
33242
|
+
let subscriptionId;
|
|
33243
|
+
let subscriptionStatus;
|
|
33244
|
+
if (sl) {
|
|
33245
|
+
try {
|
|
33246
|
+
const list = await sl.subscriptions.list();
|
|
33247
|
+
const rows = list.data ?? [];
|
|
33248
|
+
const created = rows.find((s) => s.name === name);
|
|
33249
|
+
subscriptionId = created?.id;
|
|
33250
|
+
subscriptionStatus = created?.status;
|
|
33251
|
+
} catch {}
|
|
33252
|
+
}
|
|
33223
33253
|
if (signingSecret) {
|
|
33224
33254
|
const envTarget = join4(targetDir, ".env");
|
|
33225
33255
|
const envExample = join4(targetDir, ".env.example");
|
|
33226
33256
|
if (existsSync(envExample) && !existsSync(envTarget)) {
|
|
33227
33257
|
copyFileSync(envExample, envTarget);
|
|
33228
33258
|
}
|
|
33229
|
-
if (existsSync(envTarget)) {
|
|
33259
|
+
if (!existsSync(envTarget)) {
|
|
33260
|
+
writeFileSync(envTarget, `SIGNING_SECRET=${signingSecret}
|
|
33261
|
+
`);
|
|
33262
|
+
} else {
|
|
33230
33263
|
const cur = readFileSync(envTarget, "utf8");
|
|
33231
|
-
|
|
33232
|
-
writeFileSync(envTarget, next.includes("SIGNING_SECRET=") ? next : `${cur}
|
|
33264
|
+
writeFileSync(envTarget, cur.match(/^SIGNING_SECRET=/m) ? cur.replace(/^SIGNING_SECRET=.*/m, `SIGNING_SECRET=${signingSecret}`) : `${cur}
|
|
33233
33265
|
SIGNING_SECRET=${signingSecret}
|
|
33234
33266
|
`);
|
|
33235
|
-
success(`Signing secret written to ${relative(process.cwd(), envTarget)}`);
|
|
33236
|
-
} else {
|
|
33237
|
-
info(`Signing secret (copy this — won't be shown again):
|
|
33238
|
-
${dim(" ")}${signingSecret}`);
|
|
33239
33267
|
}
|
|
33268
|
+
success(`Signing secret written to ${relative(process.cwd(), envTarget)}`);
|
|
33240
33269
|
}
|
|
33241
33270
|
console.log();
|
|
33242
33271
|
if (provisioningFailed) {
|
|
33243
33272
|
error("Subscription was not created.");
|
|
33244
33273
|
process.exit(1);
|
|
33245
33274
|
}
|
|
33275
|
+
let dashboardLine = "";
|
|
33276
|
+
try {
|
|
33277
|
+
const { apiUrl } = await resolveActiveTenant();
|
|
33278
|
+
const base = deriveBaseUrl(apiUrl);
|
|
33279
|
+
dashboardLine = subscriptionId ? `Dashboard: ${base}/platform/subgraphs/${subgraph}/subscriptions/${subscriptionId}
|
|
33280
|
+
` : `Dashboard: ${base}/platform/subgraphs/${subgraph}/subscriptions
|
|
33281
|
+
`;
|
|
33282
|
+
} catch {}
|
|
33283
|
+
const pausedLine = subscriptionStatus === "paused" ? `Subscription is paused. Resume:
|
|
33284
|
+
sl subscriptions resume ${name}
|
|
33285
|
+
` : "";
|
|
33246
33286
|
success(`Done. Next:
|
|
33247
|
-
cd ${name}
|
|
33287
|
+
${dashboardLine}${pausedLine}cd ${name}
|
|
33248
33288
|
bun install
|
|
33249
33289
|
bun run dev`);
|
|
33250
33290
|
}
|
|
@@ -33428,6 +33468,10 @@ async function resolveSubscriptionRef(client, ref) {
|
|
|
33428
33468
|
detail: await client.subscriptions.get(nameMatches[0].id)
|
|
33429
33469
|
};
|
|
33430
33470
|
}
|
|
33471
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
33472
|
+
if (!UUID_RE.test(ref)) {
|
|
33473
|
+
throw new Error(`Subscription "${ref}" not found.`);
|
|
33474
|
+
}
|
|
33431
33475
|
return {
|
|
33432
33476
|
id: ref,
|
|
33433
33477
|
detail: await client.subscriptions.get(ref)
|
|
@@ -33485,6 +33529,7 @@ function printSubscriptionDetail(sub) {
|
|
|
33485
33529
|
["Circuit Opened", sub.circuitOpenedAt ?? "none"],
|
|
33486
33530
|
["Last Error", sub.lastError ?? "none"],
|
|
33487
33531
|
["Max Retries", String(sub.maxRetries)],
|
|
33532
|
+
["Backoff", "30s → 2m → 10m → 1h → 6h → 24h → 72h"],
|
|
33488
33533
|
["Timeout", `${sub.timeoutMs}ms`],
|
|
33489
33534
|
["Concurrency", String(sub.concurrency)],
|
|
33490
33535
|
["Created", sub.createdAt],
|
|
@@ -33791,7 +33836,22 @@ ${data.length} subscription(s) total`));
|
|
|
33791
33836
|
commonOptions(subscriptions.command("delete <idOrName>").description("Delete a subscription").option("-y, --yes", "Skip confirmation").option("--json", "Output as JSON")).action(async (idOrName, options) => {
|
|
33792
33837
|
try {
|
|
33793
33838
|
const client = await getSubscriptionClient(options);
|
|
33794
|
-
|
|
33839
|
+
let resolved = null;
|
|
33840
|
+
try {
|
|
33841
|
+
resolved = await resolveSubscriptionRef(client, idOrName);
|
|
33842
|
+
} catch (err) {
|
|
33843
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33844
|
+
const status = err?.status;
|
|
33845
|
+
if (status === 404 || /not found/i.test(msg)) {
|
|
33846
|
+
if (options.json)
|
|
33847
|
+
printJson({ deleted: false, reason: "not_found" });
|
|
33848
|
+
else
|
|
33849
|
+
info(`Subscription "${idOrName}" not found (already deleted?)`);
|
|
33850
|
+
return;
|
|
33851
|
+
}
|
|
33852
|
+
throw err;
|
|
33853
|
+
}
|
|
33854
|
+
const { id, detail } = resolved;
|
|
33795
33855
|
const ok = await confirmOrExit(`Delete subscription "${detail.name}"? Pending outbox rows will be removed.`, options.yes);
|
|
33796
33856
|
if (!ok)
|
|
33797
33857
|
return;
|
|
@@ -34971,6 +35031,45 @@ function decodeBuffUtf8(value: unknown): string | null {
|
|
|
34971
35031
|
|
|
34972
35032
|
// src/commands/subgraphs.ts
|
|
34973
35033
|
init_api();
|
|
35034
|
+
init_resolve_tenant();
|
|
35035
|
+
async function loadSubgraphWithDepCheck(absPath) {
|
|
35036
|
+
try {
|
|
35037
|
+
return await import(absPath);
|
|
35038
|
+
} catch (err) {
|
|
35039
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
35040
|
+
const code = err?.code;
|
|
35041
|
+
const missingSdk = (code === "ERR_MODULE_NOT_FOUND" || msg.includes("ERR_MODULE_NOT_FOUND")) && msg.includes("@secondlayer/subgraphs");
|
|
35042
|
+
if (!missingSdk)
|
|
35043
|
+
throw err;
|
|
35044
|
+
warn("Missing dependency: @secondlayer/subgraphs");
|
|
35045
|
+
const install = await confirm3({
|
|
35046
|
+
message: "Install with `bun add @secondlayer/subgraphs`?",
|
|
35047
|
+
default: true
|
|
35048
|
+
});
|
|
35049
|
+
if (!install)
|
|
35050
|
+
throw err;
|
|
35051
|
+
await new Promise((res, rej) => {
|
|
35052
|
+
const child = spawn("bun", ["add", "@secondlayer/subgraphs"], {
|
|
35053
|
+
stdio: "inherit"
|
|
35054
|
+
});
|
|
35055
|
+
child.on("error", rej);
|
|
35056
|
+
child.on("exit", (c) => c === 0 ? res() : rej(new Error(`bun add exit ${c}`)));
|
|
35057
|
+
});
|
|
35058
|
+
return await import(absPath);
|
|
35059
|
+
}
|
|
35060
|
+
}
|
|
35061
|
+
async function typecheckHandler(absPath) {
|
|
35062
|
+
await new Promise((res, rej) => {
|
|
35063
|
+
const child = spawn("bunx", ["tsc", "--noEmit", "--allowJs", "--target", "es2022", absPath], { stdio: "inherit" });
|
|
35064
|
+
child.on("error", (err) => rej(new Error(`Failed to run tsc — install typescript (\`bun add -d typescript\`) or drop --strict. (${err.message})`)));
|
|
35065
|
+
child.on("exit", (code) => {
|
|
35066
|
+
if (code === 0)
|
|
35067
|
+
res();
|
|
35068
|
+
else
|
|
35069
|
+
rej(new Error(`Type-check failed (tsc exit ${code})`));
|
|
35070
|
+
});
|
|
35071
|
+
});
|
|
35072
|
+
}
|
|
34974
35073
|
function parseStartBlockOption(value) {
|
|
34975
35074
|
if (value === undefined)
|
|
34976
35075
|
return;
|
|
@@ -35290,7 +35389,7 @@ Stopped watching.`);
|
|
|
35290
35389
|
});
|
|
35291
35390
|
await new Promise(() => {});
|
|
35292
35391
|
});
|
|
35293
|
-
subgraphs.command("deploy <file>").description("Deploy a subgraph definition file").option("--version <semver>", "Explicit version (default: auto-increment patch)").option("--start-block <n>", "Override the subgraph definition startBlock for this deploy").option("--dry-run", "Validate and preview deploy without writing changes").option("--preview", "Alias for --dry-run").option("--force", "Skip confirmation prompt for reindex operations").action(async (file, options2) => {
|
|
35392
|
+
subgraphs.command("deploy <file>").description("Deploy a subgraph definition file").option("--version <semver>", "Explicit version (default: auto-increment patch)").option("--start-block <n>", "Override the subgraph definition startBlock for this deploy").option("--dry-run", "Validate and preview deploy without writing changes").option("--preview", "Alias for --dry-run").option("--force", "Skip confirmation prompt for reindex operations").option("--strict", "Run `tsc --noEmit` against the handler before deploy (slower; catches TS type errors)").action(async (file, options2) => {
|
|
35294
35393
|
try {
|
|
35295
35394
|
const absPath = resolve2(file);
|
|
35296
35395
|
const config = await loadConfig();
|
|
@@ -35302,8 +35401,12 @@ Stopped watching.`);
|
|
|
35302
35401
|
if (startBlock !== undefined) {
|
|
35303
35402
|
warn(`--start-block ${startBlock} overrides the definition's startBlock for this deploy.`);
|
|
35304
35403
|
}
|
|
35404
|
+
if (options2.strict) {
|
|
35405
|
+
info("Type-checking handler (tsc --noEmit)...");
|
|
35406
|
+
await typecheckHandler(absPath);
|
|
35407
|
+
}
|
|
35305
35408
|
info(`Loading subgraph from ${absPath}`);
|
|
35306
|
-
const mod = await
|
|
35409
|
+
const mod = await loadSubgraphWithDepCheck(absPath);
|
|
35307
35410
|
const def = mod.default ?? mod;
|
|
35308
35411
|
const effectiveDef = startBlock === undefined ? def : { ...def, startBlock };
|
|
35309
35412
|
const { validateSubgraphDefinition } = await import("@secondlayer/subgraphs/validate");
|
|
@@ -35338,10 +35441,23 @@ Stopped watching.`);
|
|
|
35338
35441
|
sourceCode: source,
|
|
35339
35442
|
...startBlock !== undefined ? { startBlock } : {}
|
|
35340
35443
|
});
|
|
35444
|
+
const printDeployFooter = async () => {
|
|
35445
|
+
try {
|
|
35446
|
+
const { apiUrl } = await resolveActiveTenant();
|
|
35447
|
+
const baseUrl = deriveBaseUrl(apiUrl);
|
|
35448
|
+
const firstTable = Object.keys(effectiveDef.schema ?? {})[0];
|
|
35449
|
+
info(` Dashboard: ${baseUrl}/platform/subgraphs/${effectiveDef.name}`);
|
|
35450
|
+
if (firstTable) {
|
|
35451
|
+
info(` REST: ${apiUrl}/api/subgraphs/${effectiveDef.name}/${firstTable}`);
|
|
35452
|
+
}
|
|
35453
|
+
info(` Watch: sl subgraphs status ${effectiveDef.name}`);
|
|
35454
|
+
} catch {}
|
|
35455
|
+
};
|
|
35341
35456
|
if (result.action === "unchanged") {
|
|
35342
35457
|
info(`Subgraph "${effectiveDef.name}" is up to date (v${result.version} — no changes)`);
|
|
35343
35458
|
} else if (result.action === "created") {
|
|
35344
35459
|
success(`Subgraph "${effectiveDef.name}" created → v${result.version}`);
|
|
35460
|
+
await printDeployFooter();
|
|
35345
35461
|
} else if (result.action === "reindexed") {
|
|
35346
35462
|
if (result.diff) {
|
|
35347
35463
|
const { addedTables, addedColumns, breakingChanges } = result.diff;
|
|
@@ -35364,6 +35480,7 @@ Stopped watching.`);
|
|
|
35364
35480
|
process.exit(0);
|
|
35365
35481
|
}
|
|
35366
35482
|
success(`Subgraph "${effectiveDef.name}" updated → v${result.version} (reindexing)`);
|
|
35483
|
+
await printDeployFooter();
|
|
35367
35484
|
} else {
|
|
35368
35485
|
if (result.diff) {
|
|
35369
35486
|
const { addedTables, addedColumns } = result.diff;
|
|
@@ -35374,6 +35491,7 @@ Stopped watching.`);
|
|
|
35374
35491
|
}
|
|
35375
35492
|
}
|
|
35376
35493
|
success(`Subgraph "${effectiveDef.name}" updated → v${result.version}`);
|
|
35494
|
+
await printDeployFooter();
|
|
35377
35495
|
}
|
|
35378
35496
|
} else {
|
|
35379
35497
|
if (dryRun) {
|
|
@@ -35438,8 +35556,8 @@ ${data.length} subgraph(s) total`));
|
|
|
35438
35556
|
handleApiError(err, "list subgraphs");
|
|
35439
35557
|
}
|
|
35440
35558
|
});
|
|
35441
|
-
subgraphs.command("status <name>").description("Show detailed subgraph status").action(async (name) => {
|
|
35442
|
-
|
|
35559
|
+
subgraphs.command("status <name>").description("Show detailed subgraph status").option("-w, --watch", "Refresh every 2s until synced or Ctrl-C").action(async (name, options2) => {
|
|
35560
|
+
const renderOnce = async () => {
|
|
35443
35561
|
const subgraph = await getSubgraphApi(name);
|
|
35444
35562
|
const rowCounts = Object.entries(subgraph.tables).map(([t, info2]) => `${t}: ${info2.rowCount}`).join(", ") || "N/A";
|
|
35445
35563
|
const totalRows = Object.values(subgraph.tables).reduce((sum, info2) => sum + info2.rowCount, 0);
|
|
@@ -35482,6 +35600,23 @@ Table endpoints:`));
|
|
|
35482
35600
|
console.log(dim(` ${info2.endpoint}`));
|
|
35483
35601
|
}
|
|
35484
35602
|
}
|
|
35603
|
+
return subgraph;
|
|
35604
|
+
};
|
|
35605
|
+
try {
|
|
35606
|
+
if (!options2.watch) {
|
|
35607
|
+
await renderOnce();
|
|
35608
|
+
return;
|
|
35609
|
+
}
|
|
35610
|
+
while (true) {
|
|
35611
|
+
process.stdout.write("\x1Bc");
|
|
35612
|
+
const sg = await renderOnce();
|
|
35613
|
+
if (sg && sg.status === "synced") {
|
|
35614
|
+
console.log(dim(`
|
|
35615
|
+
Synced — exiting watch.`));
|
|
35616
|
+
return;
|
|
35617
|
+
}
|
|
35618
|
+
await new Promise((res) => setTimeout(res, 2000));
|
|
35619
|
+
}
|
|
35485
35620
|
} catch (err) {
|
|
35486
35621
|
handleApiError(err, "get subgraph status");
|
|
35487
35622
|
}
|
|
@@ -35671,9 +35806,19 @@ ${rows.length} row(s)`));
|
|
|
35671
35806
|
try {
|
|
35672
35807
|
if (!options2.yes && !options2.force) {
|
|
35673
35808
|
const { confirm: confirm4 } = await import("@inquirer/prompts");
|
|
35674
|
-
|
|
35675
|
-
|
|
35676
|
-
|
|
35809
|
+
let ok = false;
|
|
35810
|
+
try {
|
|
35811
|
+
ok = await confirm4({
|
|
35812
|
+
message: `Delete subgraph "${name}" and all its data? This cannot be undone.`
|
|
35813
|
+
});
|
|
35814
|
+
} catch (promptErr) {
|
|
35815
|
+
const m = promptErr instanceof Error ? promptErr.message : String(promptErr);
|
|
35816
|
+
if (m.includes("ExitPromptError") || m.includes("force closed")) {
|
|
35817
|
+
error("Interactive prompt unavailable. Re-run with -y to skip confirmation.");
|
|
35818
|
+
process.exit(1);
|
|
35819
|
+
}
|
|
35820
|
+
throw promptErr;
|
|
35821
|
+
}
|
|
35677
35822
|
if (!ok) {
|
|
35678
35823
|
info("Cancelled");
|
|
35679
35824
|
return;
|
|
@@ -36955,5 +37100,5 @@ registerAccountCommand(program);
|
|
|
36955
37100
|
registerBillingCommand(program);
|
|
36956
37101
|
program.parse();
|
|
36957
37102
|
|
|
36958
|
-
//# debugId=
|
|
37103
|
+
//# debugId=F6198A7D6A4B4A3F64756E2164756E21
|
|
36959
37104
|
//# sourceMappingURL=cli.js.map
|