@secondlayer/cli 5.3.0 → 5.4.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/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.3.0",
32324
+ version: "5.4.0",
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.0",
32368
+ "@secondlayer/shared": "^6.4.1",
32369
32369
  "@secondlayer/stacks": "^2.2.0",
32370
- "@secondlayer/subgraphs": "^2.0.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
- const next = cur.replace(/^SIGNING_SECRET=.*/m, `SIGNING_SECRET=${signingSecret}`);
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
  }
@@ -33485,6 +33525,7 @@ function printSubscriptionDetail(sub) {
33485
33525
  ["Circuit Opened", sub.circuitOpenedAt ?? "none"],
33486
33526
  ["Last Error", sub.lastError ?? "none"],
33487
33527
  ["Max Retries", String(sub.maxRetries)],
33528
+ ["Backoff", "30s → 2m → 10m → 1h → 6h → 24h → 72h"],
33488
33529
  ["Timeout", `${sub.timeoutMs}ms`],
33489
33530
  ["Concurrency", String(sub.concurrency)],
33490
33531
  ["Created", sub.createdAt],
@@ -33791,7 +33832,22 @@ ${data.length} subscription(s) total`));
33791
33832
  commonOptions(subscriptions.command("delete <idOrName>").description("Delete a subscription").option("-y, --yes", "Skip confirmation").option("--json", "Output as JSON")).action(async (idOrName, options) => {
33792
33833
  try {
33793
33834
  const client = await getSubscriptionClient(options);
33794
- const { id, detail } = await resolveSubscriptionRef(client, idOrName);
33835
+ let resolved = null;
33836
+ try {
33837
+ resolved = await resolveSubscriptionRef(client, idOrName);
33838
+ } catch (err) {
33839
+ const msg = err instanceof Error ? err.message : String(err);
33840
+ const status = err?.status;
33841
+ if (status === 404 || /not found/i.test(msg)) {
33842
+ if (options.json)
33843
+ printJson({ deleted: false, reason: "not_found" });
33844
+ else
33845
+ info(`Subscription "${idOrName}" not found (already deleted?)`);
33846
+ return;
33847
+ }
33848
+ throw err;
33849
+ }
33850
+ const { id, detail } = resolved;
33795
33851
  const ok = await confirmOrExit(`Delete subscription "${detail.name}"? Pending outbox rows will be removed.`, options.yes);
33796
33852
  if (!ok)
33797
33853
  return;
@@ -34971,6 +35027,45 @@ function decodeBuffUtf8(value: unknown): string | null {
34971
35027
 
34972
35028
  // src/commands/subgraphs.ts
34973
35029
  init_api();
35030
+ init_resolve_tenant();
35031
+ async function loadSubgraphWithDepCheck(absPath) {
35032
+ try {
35033
+ return await import(absPath);
35034
+ } catch (err) {
35035
+ const msg = err instanceof Error ? err.message : String(err);
35036
+ const code = err?.code;
35037
+ const missingSdk = (code === "ERR_MODULE_NOT_FOUND" || msg.includes("ERR_MODULE_NOT_FOUND")) && msg.includes("@secondlayer/subgraphs");
35038
+ if (!missingSdk)
35039
+ throw err;
35040
+ warn("Missing dependency: @secondlayer/subgraphs");
35041
+ const install = await confirm3({
35042
+ message: "Install with `bun add @secondlayer/subgraphs`?",
35043
+ default: true
35044
+ });
35045
+ if (!install)
35046
+ throw err;
35047
+ await new Promise((res, rej) => {
35048
+ const child = spawn("bun", ["add", "@secondlayer/subgraphs"], {
35049
+ stdio: "inherit"
35050
+ });
35051
+ child.on("error", rej);
35052
+ child.on("exit", (c) => c === 0 ? res() : rej(new Error(`bun add exit ${c}`)));
35053
+ });
35054
+ return await import(absPath);
35055
+ }
35056
+ }
35057
+ async function typecheckHandler(absPath) {
35058
+ await new Promise((res, rej) => {
35059
+ const child = spawn("bunx", ["tsc", "--noEmit", "--allowJs", "--target", "es2022", absPath], { stdio: "inherit" });
35060
+ child.on("error", (err) => rej(new Error(`Failed to run tsc — install typescript (\`bun add -d typescript\`) or drop --strict. (${err.message})`)));
35061
+ child.on("exit", (code) => {
35062
+ if (code === 0)
35063
+ res();
35064
+ else
35065
+ rej(new Error(`Type-check failed (tsc exit ${code})`));
35066
+ });
35067
+ });
35068
+ }
34974
35069
  function parseStartBlockOption(value) {
34975
35070
  if (value === undefined)
34976
35071
  return;
@@ -35290,7 +35385,7 @@ Stopped watching.`);
35290
35385
  });
35291
35386
  await new Promise(() => {});
35292
35387
  });
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) => {
35388
+ 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
35389
  try {
35295
35390
  const absPath = resolve2(file);
35296
35391
  const config = await loadConfig();
@@ -35302,8 +35397,12 @@ Stopped watching.`);
35302
35397
  if (startBlock !== undefined) {
35303
35398
  warn(`--start-block ${startBlock} overrides the definition's startBlock for this deploy.`);
35304
35399
  }
35400
+ if (options2.strict) {
35401
+ info("Type-checking handler (tsc --noEmit)...");
35402
+ await typecheckHandler(absPath);
35403
+ }
35305
35404
  info(`Loading subgraph from ${absPath}`);
35306
- const mod = await import(absPath);
35405
+ const mod = await loadSubgraphWithDepCheck(absPath);
35307
35406
  const def = mod.default ?? mod;
35308
35407
  const effectiveDef = startBlock === undefined ? def : { ...def, startBlock };
35309
35408
  const { validateSubgraphDefinition } = await import("@secondlayer/subgraphs/validate");
@@ -35338,10 +35437,23 @@ Stopped watching.`);
35338
35437
  sourceCode: source,
35339
35438
  ...startBlock !== undefined ? { startBlock } : {}
35340
35439
  });
35440
+ const printDeployFooter = async () => {
35441
+ try {
35442
+ const { apiUrl } = await resolveActiveTenant();
35443
+ const baseUrl = deriveBaseUrl(apiUrl);
35444
+ const firstTable = Object.keys(effectiveDef.schema ?? {})[0];
35445
+ info(` Dashboard: ${baseUrl}/platform/subgraphs/${effectiveDef.name}`);
35446
+ if (firstTable) {
35447
+ info(` REST: ${apiUrl}/api/subgraphs/${effectiveDef.name}/${firstTable}`);
35448
+ }
35449
+ info(` Watch: sl subgraphs status ${effectiveDef.name}`);
35450
+ } catch {}
35451
+ };
35341
35452
  if (result.action === "unchanged") {
35342
35453
  info(`Subgraph "${effectiveDef.name}" is up to date (v${result.version} — no changes)`);
35343
35454
  } else if (result.action === "created") {
35344
35455
  success(`Subgraph "${effectiveDef.name}" created → v${result.version}`);
35456
+ await printDeployFooter();
35345
35457
  } else if (result.action === "reindexed") {
35346
35458
  if (result.diff) {
35347
35459
  const { addedTables, addedColumns, breakingChanges } = result.diff;
@@ -35364,6 +35476,7 @@ Stopped watching.`);
35364
35476
  process.exit(0);
35365
35477
  }
35366
35478
  success(`Subgraph "${effectiveDef.name}" updated → v${result.version} (reindexing)`);
35479
+ await printDeployFooter();
35367
35480
  } else {
35368
35481
  if (result.diff) {
35369
35482
  const { addedTables, addedColumns } = result.diff;
@@ -35374,6 +35487,7 @@ Stopped watching.`);
35374
35487
  }
35375
35488
  }
35376
35489
  success(`Subgraph "${effectiveDef.name}" updated → v${result.version}`);
35490
+ await printDeployFooter();
35377
35491
  }
35378
35492
  } else {
35379
35493
  if (dryRun) {
@@ -35438,8 +35552,8 @@ ${data.length} subgraph(s) total`));
35438
35552
  handleApiError(err, "list subgraphs");
35439
35553
  }
35440
35554
  });
35441
- subgraphs.command("status <name>").description("Show detailed subgraph status").action(async (name) => {
35442
- try {
35555
+ subgraphs.command("status <name>").description("Show detailed subgraph status").option("-w, --watch", "Refresh every 2s until synced or Ctrl-C").action(async (name, options2) => {
35556
+ const renderOnce = async () => {
35443
35557
  const subgraph = await getSubgraphApi(name);
35444
35558
  const rowCounts = Object.entries(subgraph.tables).map(([t, info2]) => `${t}: ${info2.rowCount}`).join(", ") || "N/A";
35445
35559
  const totalRows = Object.values(subgraph.tables).reduce((sum, info2) => sum + info2.rowCount, 0);
@@ -35482,6 +35596,23 @@ Table endpoints:`));
35482
35596
  console.log(dim(` ${info2.endpoint}`));
35483
35597
  }
35484
35598
  }
35599
+ return subgraph;
35600
+ };
35601
+ try {
35602
+ if (!options2.watch) {
35603
+ await renderOnce();
35604
+ return;
35605
+ }
35606
+ while (true) {
35607
+ process.stdout.write("\x1Bc");
35608
+ const sg = await renderOnce();
35609
+ if (sg && sg.status === "synced") {
35610
+ console.log(dim(`
35611
+ Synced — exiting watch.`));
35612
+ return;
35613
+ }
35614
+ await new Promise((res) => setTimeout(res, 2000));
35615
+ }
35485
35616
  } catch (err) {
35486
35617
  handleApiError(err, "get subgraph status");
35487
35618
  }
@@ -35671,9 +35802,19 @@ ${rows.length} row(s)`));
35671
35802
  try {
35672
35803
  if (!options2.yes && !options2.force) {
35673
35804
  const { confirm: confirm4 } = await import("@inquirer/prompts");
35674
- const ok = await confirm4({
35675
- message: `Delete subgraph "${name}" and all its data? This cannot be undone.`
35676
- });
35805
+ let ok = false;
35806
+ try {
35807
+ ok = await confirm4({
35808
+ message: `Delete subgraph "${name}" and all its data? This cannot be undone.`
35809
+ });
35810
+ } catch (promptErr) {
35811
+ const m = promptErr instanceof Error ? promptErr.message : String(promptErr);
35812
+ if (m.includes("ExitPromptError") || m.includes("force closed")) {
35813
+ error("Interactive prompt unavailable. Re-run with -y to skip confirmation.");
35814
+ process.exit(1);
35815
+ }
35816
+ throw promptErr;
35817
+ }
35677
35818
  if (!ok) {
35678
35819
  info("Cancelled");
35679
35820
  return;
@@ -36955,5 +37096,5 @@ registerAccountCommand(program);
36955
37096
  registerBillingCommand(program);
36956
37097
  program.parse();
36957
37098
 
36958
- //# debugId=C9F2E56BDB69B19A64756E2164756E21
37099
+ //# debugId=7D07B6242D81B6A564756E2164756E21
36959
37100
  //# sourceMappingURL=cli.js.map