@secondlayer/cli 3.5.2 → 3.5.3

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
@@ -2530,7 +2530,7 @@ async function request(url, opts) {
2530
2530
  method: opts.method ?? "GET",
2531
2531
  headers,
2532
2532
  body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
2533
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
2533
+ signal: AbortSignal.timeout(opts.timeoutMs ?? REQUEST_TIMEOUT_MS)
2534
2534
  });
2535
2535
  if (!res.ok) {
2536
2536
  let body = {};
@@ -2957,7 +2957,8 @@ function success(message) {
2957
2957
  console.log(green(`✓ ${message}`));
2958
2958
  }
2959
2959
  function error(message) {
2960
- console.error(red(`✗ ${message}`));
2960
+ const text = message.trim() || "Command failed.";
2961
+ console.error(red(`✗ ${text}`));
2961
2962
  }
2962
2963
  function warn(message) {
2963
2964
  console.log(yellow(`⚠ ${message}`));
@@ -2966,17 +2967,17 @@ function info(message) {
2966
2967
  console.log(blue(`ℹ ${message}`));
2967
2968
  }
2968
2969
  function stripAnsi(text) {
2969
- return text.replace(/\x1b\[[0-9;]*m/g, "");
2970
+ return text.replace(ANSI_ESCAPE_PATTERN, "");
2970
2971
  }
2971
2972
  function formatTable(headers, rows) {
2972
2973
  const widths = headers.map((h, i) => {
2973
2974
  const colValues = [h, ...rows.map((r) => r[i] || "")];
2974
2975
  return Math.max(...colValues.map((v) => stripAnsi(v).length));
2975
2976
  });
2976
- const headerRow = headers.map((h, i) => h.padEnd(widths[i])).join(" ");
2977
+ const headerRow = headers.map((h, i) => h.padEnd(widths[i] ?? 0)).join(" ");
2977
2978
  const separator = widths.map((w) => "-".repeat(w)).join(" ");
2978
2979
  const dataRows = rows.map((row) => row.map((cell, i) => {
2979
- const width = widths[i];
2980
+ const width = widths[i] ?? 0;
2980
2981
  const padding = width - stripAnsi(cell).length;
2981
2982
  return cell + " ".repeat(Math.max(0, padding));
2982
2983
  }).join(" "));
@@ -2988,7 +2989,7 @@ function formatKeyValue(pairs) {
2988
2989
  return pairs.map(([key, value]) => `${dim(key.padEnd(maxKeyLen))} ${value}`).join(`
2989
2990
  `);
2990
2991
  }
2991
- var colors;
2992
+ var colors, ANSI_ESCAPE_PATTERN;
2992
2993
  var init_output = __esm(() => {
2993
2994
  colors = {
2994
2995
  reset: "\x1B[0m",
@@ -3001,6 +3002,7 @@ var init_output = __esm(() => {
3001
3002
  dim: "\x1B[2m",
3002
3003
  bold: "\x1B[1m"
3003
3004
  };
3005
+ ANSI_ESCAPE_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
3004
3006
  });
3005
3007
 
3006
3008
  // src/lib/docker.ts
@@ -32437,7 +32439,7 @@ var {
32437
32439
  // package.json
32438
32440
  var package_default = {
32439
32441
  name: "@secondlayer/cli",
32440
- version: "3.5.2",
32442
+ version: "3.5.3",
32441
32443
  description: "CLI for subgraphs and blockchain indexing on Stacks",
32442
32444
  type: "module",
32443
32445
  bin: {
@@ -32480,10 +32482,10 @@ var package_default = {
32480
32482
  dependencies: {
32481
32483
  "@inquirer/prompts": "^8.2.0",
32482
32484
  "@secondlayer/bundler": "^0.3.2",
32483
- "@secondlayer/sdk": "^3.2.0",
32484
- "@secondlayer/shared": "^4.3.2",
32485
+ "@secondlayer/sdk": "^3.2.2",
32486
+ "@secondlayer/shared": "^4.3.3",
32485
32487
  "@secondlayer/stacks": "^2.0.0",
32486
- "@secondlayer/subgraphs": "^1.2.1",
32488
+ "@secondlayer/subgraphs": "^1.3.2",
32487
32489
  "@biomejs/js-api": "^0.7.0",
32488
32490
  "@biomejs/wasm-nodejs": "^1.9.0",
32489
32491
  esbuild: "^0.19.0",
@@ -32562,8 +32564,8 @@ async function backfillSubgraphApi(name, options) {
32562
32564
  async function stopSubgraphApi(name) {
32563
32565
  return (await getTenantClient()).subgraphs.stop(name);
32564
32566
  }
32565
- async function deleteSubgraphApi(name) {
32566
- return (await getTenantClient()).subgraphs.delete(name);
32567
+ async function deleteSubgraphApi(name, options) {
32568
+ return (await getTenantClient()).subgraphs.delete(name, options);
32567
32569
  }
32568
32570
  async function deploySubgraphApi(data) {
32569
32571
  return (await getTenantClient()).subgraphs.deploy(data);
@@ -34877,6 +34879,7 @@ ${data.length} subgraph(s) total`));
34877
34879
  try {
34878
34880
  const subgraph = await getSubgraphApi(name);
34879
34881
  const rowCounts = Object.entries(subgraph.tables).map(([t, info2]) => `${t}: ${info2.rowCount}`).join(", ") || "N/A";
34882
+ const totalRows = Object.values(subgraph.tables).reduce((sum, info2) => sum + info2.rowCount, 0);
34880
34883
  const errorRate = subgraph.health.totalProcessed > 0 ? `${(subgraph.health.errorRate * 100).toFixed(2)}%` : "N/A";
34881
34884
  const sync = subgraph.sync;
34882
34885
  const syncDisplay = sync ? formatSubgraphSync(sync) : {
@@ -34895,7 +34898,8 @@ ${data.length} subgraph(s) total`));
34895
34898
  ["Integrity", integrity],
34896
34899
  ["Gaps", gapSummary],
34897
34900
  ["Last Block", String(subgraph.lastProcessedBlock)],
34898
- ["Row Count", rowCounts],
34901
+ ["Rows Indexed", totalRows.toLocaleString()],
34902
+ ["Table Rows", rowCounts],
34899
34903
  ["Total Errors", String(subgraph.health.totalErrors)],
34900
34904
  ["Error Rate", errorRate],
34901
34905
  ["Last Error", subgraph.health.lastError ?? "none"],
@@ -35040,9 +35044,9 @@ ${rows.length} row(s)`));
35040
35044
  handleApiError(err, "query subgraph");
35041
35045
  }
35042
35046
  });
35043
- subgraphs.command("delete <name>").description("Delete a subgraph and its data").option("-y, --yes", "Skip confirmation").action(async (name, options2) => {
35047
+ subgraphs.command("delete <name>").description("Delete a subgraph and its data").option("-y, --yes", "Skip confirmation").option("--force", "Cancel active operations and force delete").action(async (name, options2) => {
35044
35048
  try {
35045
- if (!options2.yes) {
35049
+ if (!options2.yes && !options2.force) {
35046
35050
  const { confirm: confirm4 } = await import("@inquirer/prompts");
35047
35051
  const ok = await confirm4({
35048
35052
  message: `Delete subgraph "${name}" and all its data? This cannot be undone.`
@@ -35052,7 +35056,9 @@ ${rows.length} row(s)`));
35052
35056
  return;
35053
35057
  }
35054
35058
  }
35055
- const result = await deleteSubgraphApi(name);
35059
+ const result = await deleteSubgraphApi(name, {
35060
+ force: options2.force
35061
+ });
35056
35062
  success(result.message);
35057
35063
  } catch (err) {
35058
35064
  handleApiError(err, "delete subgraph");
@@ -36039,6 +36045,7 @@ init_output();
36039
36045
  init_project_file();
36040
36046
  init_resolve_tenant();
36041
36047
  import { confirm as confirm5, input as input4, select as select4 } from "@inquirer/prompts";
36048
+ var INSTANCE_CREATE_TIMEOUT_MS = 180000;
36042
36049
  function registerInstanceCommand(program2) {
36043
36050
  const instance = program2.command("instance").description("Manage your dedicated Secondlayer instance");
36044
36051
  instance.command("create").description("Provision a new dedicated instance for the active project").option("--plan <plan>", "Plan: hobby (free) | launch | grow | scale", "hobby").action(async (opts) => {
@@ -36049,14 +36056,22 @@ function registerInstanceCommand(program2) {
36049
36056
  error(`Invalid plan: ${plan} (expected hobby, launch, grow, or scale)`);
36050
36057
  process.exit(1);
36051
36058
  }
36059
+ const spinner = createSpinner("Provisioning your instance (~60s; safe to interrupt — instance will still be created; check `sl instance info`)");
36052
36060
  try {
36053
36061
  const res = await httpPlatform(`/api/projects/${encodeURIComponent(activeSlug)}/instance`, {
36054
36062
  method: "POST",
36055
- body: { plan }
36063
+ body: { plan },
36064
+ timeoutMs: INSTANCE_CREATE_TIMEOUT_MS
36056
36065
  });
36057
- success(`Instance provisioned: ${res.tenant.slug}`);
36066
+ spinner.succeed(`Instance provisioned: ${res.tenant.slug}`);
36058
36067
  printKeyReveal(res.tenant, res.credentials);
36059
36068
  } catch (err) {
36069
+ if (isTimeoutError(err)) {
36070
+ spinner.fail("Provision request timed out after 3 minutes.");
36071
+ error("Provisioning may still finish server-side. Run `sl instance info` to check before retrying.");
36072
+ process.exit(1);
36073
+ }
36074
+ spinner.fail("Provision failed.");
36060
36075
  handleInstanceError(err, "provision instance");
36061
36076
  }
36062
36077
  });
@@ -36135,6 +36150,10 @@ function registerInstanceCommand(program2) {
36135
36150
  }
36136
36151
  const slug = info_.tenant.slug;
36137
36152
  if (!opts.yes) {
36153
+ if (!process.stdin.isTTY) {
36154
+ error(`Refusing to prompt in a non-interactive terminal. Re-run with --yes to delete instance "${slug}".`);
36155
+ process.exit(1);
36156
+ }
36138
36157
  const typed = await input4({
36139
36158
  message: `Type the slug "${slug}" to confirm permanent deletion`,
36140
36159
  validate: (v) => v === slug ? true : "Slug must match exactly"
@@ -36302,6 +36321,43 @@ function printKeyReveal(tenant, creds) {
36302
36321
  console.log(dim("Run `sl subgraphs deploy <file>` to deploy your first subgraph."));
36303
36322
  console.log("");
36304
36323
  }
36324
+ function createSpinner(message) {
36325
+ if (!process.stderr.isTTY) {
36326
+ info(message);
36327
+ return {
36328
+ succeed: success,
36329
+ fail: error
36330
+ };
36331
+ }
36332
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
36333
+ let index = 0;
36334
+ const render = () => {
36335
+ const frame = frames[index % frames.length] ?? frames[0];
36336
+ index += 1;
36337
+ process.stderr.write(`\r${blue(frame)} ${message}`);
36338
+ };
36339
+ const clear = () => {
36340
+ clearInterval(timer3);
36341
+ process.stderr.write("\r\x1B[2K");
36342
+ };
36343
+ const timer3 = setInterval(render, 80);
36344
+ render();
36345
+ return {
36346
+ succeed(message2) {
36347
+ clear();
36348
+ success(message2);
36349
+ },
36350
+ fail(message2) {
36351
+ clear();
36352
+ error(message2);
36353
+ }
36354
+ };
36355
+ }
36356
+ function isTimeoutError(err) {
36357
+ if (!(err instanceof Error))
36358
+ return false;
36359
+ return err.name === "TimeoutError" || err.name === "AbortError" || err.message.toLowerCase().includes("timeout");
36360
+ }
36305
36361
  function warn_box(message) {
36306
36362
  return `${"━".repeat(message.length + 4)}
36307
36363
  ${message}
@@ -36321,10 +36377,10 @@ function handleInstanceError(err, action) {
36321
36377
  error("This project already has an instance. Run `sl instance info` to see it.");
36322
36378
  process.exit(1);
36323
36379
  }
36324
- error(err.message);
36380
+ error(err.message || `Failed to ${action}.`);
36325
36381
  process.exit(1);
36326
36382
  }
36327
- error(`Failed to ${action}: ${err instanceof Error ? err.message : String(err)}`);
36383
+ error(`Failed to ${action}: ${err instanceof Error ? err.message || "Unknown error" : String(err)}`);
36328
36384
  process.exit(1);
36329
36385
  }
36330
36386
  // src/commands/project.ts
@@ -36335,15 +36391,24 @@ init_project_file();
36335
36391
  import { input as input5 } from "@inquirer/prompts";
36336
36392
  function registerProjectCommand(program2) {
36337
36393
  const project = program2.command("project").description("Manage Secondlayer projects");
36338
- project.command("create [name]").description("Create a new project").action(async (nameArg) => {
36394
+ project.command("create [name]").description("Create a new project").option("--slug <slug>", "Project URL identifier").action(async (nameArg, options2 = {}) => {
36339
36395
  const name = nameArg ?? await input5({
36340
36396
  message: "Project name",
36341
36397
  validate: (v) => v.length >= 2 ? true : "Name must be at least 2 characters"
36342
36398
  });
36399
+ const slug = options2.slug ?? slugifyProjectName(name);
36400
+ const validation = validateProjectSlug(slug);
36401
+ if (validation !== true) {
36402
+ error(`${validation}. Pass --slug <slug> to choose one explicitly.`);
36403
+ process.exit(1);
36404
+ }
36343
36405
  try {
36344
- const res = await httpPlatform("/api/projects", { method: "POST", body: { name } });
36345
- success(`Created project ${res.project.name} (${res.project.slug})`);
36346
- const path2 = await writeActiveProject(res.project.slug, process.cwd());
36406
+ const res = await httpPlatform("/api/projects", {
36407
+ method: "POST",
36408
+ body: { name, slug }
36409
+ });
36410
+ success(`Created project ${res.name} (${res.slug})`);
36411
+ const path2 = await writeActiveProject(res.slug, process.cwd());
36347
36412
  info(dim(`Bound to this directory → ${path2}`));
36348
36413
  info(dim("Next: sl instance create --plan launch"));
36349
36414
  } catch (err) {
@@ -36410,6 +36475,18 @@ function handleProjectError(err) {
36410
36475
  error(err instanceof Error ? err.message : String(err));
36411
36476
  process.exit(1);
36412
36477
  }
36478
+ function slugifyProjectName(name) {
36479
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63).replace(/-+$/g, "");
36480
+ }
36481
+ function validateProjectSlug(slug) {
36482
+ if (slug.length < 2 || slug.length > 63) {
36483
+ return "Project slug must be 2-63 characters";
36484
+ }
36485
+ if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(slug)) {
36486
+ return "Project slug must use lowercase letters, numbers, and hyphens, and start/end with a letter or number";
36487
+ }
36488
+ return true;
36489
+ }
36413
36490
  // src/cli.ts
36414
36491
  var { version } = package_default;
36415
36492
  program.name("secondlayer").alias("sl").description("SecondLayer CLI — dedicated Stacks indexing + real-time subgraphs").version(version).option("--network <network>", "Override network (local, testnet, mainnet)");
@@ -36451,5 +36528,5 @@ registerLocalCommand(program);
36451
36528
  registerAccountCommand(program);
36452
36529
  program.parse();
36453
36530
 
36454
- //# debugId=8D29D5D4F1C33F0564756E2164756E21
36531
+ //# debugId=BE2AD99915C01F0964756E2164756E21
36455
36532
  //# sourceMappingURL=cli.js.map