@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 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.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.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
  }
@@ -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
- const { id, detail } = await resolveSubscriptionRef(client, idOrName);
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 import(absPath);
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
- try {
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
- const ok = await confirm4({
35675
- message: `Delete subgraph "${name}" and all its data? This cannot be undone.`
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=C9F2E56BDB69B19A64756E2164756E21
37103
+ //# debugId=F6198A7D6A4B4A3F64756E2164756E21
36959
37104
  //# sourceMappingURL=cli.js.map