@secondlayer/cli 3.5.1 → 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.1",
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,8 +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],
34899
- ["Total Events", String(subgraph.health.totalProcessed)],
34901
+ ["Rows Indexed", totalRows.toLocaleString()],
34902
+ ["Table Rows", rowCounts],
34900
34903
  ["Total Errors", String(subgraph.health.totalErrors)],
34901
34904
  ["Error Rate", errorRate],
34902
34905
  ["Last Error", subgraph.health.lastError ?? "none"],
@@ -35041,9 +35044,9 @@ ${rows.length} row(s)`));
35041
35044
  handleApiError(err, "query subgraph");
35042
35045
  }
35043
35046
  });
35044
- 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) => {
35045
35048
  try {
35046
- if (!options2.yes) {
35049
+ if (!options2.yes && !options2.force) {
35047
35050
  const { confirm: confirm4 } = await import("@inquirer/prompts");
35048
35051
  const ok = await confirm4({
35049
35052
  message: `Delete subgraph "${name}" and all its data? This cannot be undone.`
@@ -35053,7 +35056,9 @@ ${rows.length} row(s)`));
35053
35056
  return;
35054
35057
  }
35055
35058
  }
35056
- const result = await deleteSubgraphApi(name);
35059
+ const result = await deleteSubgraphApi(name, {
35060
+ force: options2.force
35061
+ });
35057
35062
  success(result.message);
35058
35063
  } catch (err) {
35059
35064
  handleApiError(err, "delete subgraph");
@@ -36040,6 +36045,7 @@ init_output();
36040
36045
  init_project_file();
36041
36046
  init_resolve_tenant();
36042
36047
  import { confirm as confirm5, input as input4, select as select4 } from "@inquirer/prompts";
36048
+ var INSTANCE_CREATE_TIMEOUT_MS = 180000;
36043
36049
  function registerInstanceCommand(program2) {
36044
36050
  const instance = program2.command("instance").description("Manage your dedicated Secondlayer instance");
36045
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) => {
@@ -36050,14 +36056,22 @@ function registerInstanceCommand(program2) {
36050
36056
  error(`Invalid plan: ${plan} (expected hobby, launch, grow, or scale)`);
36051
36057
  process.exit(1);
36052
36058
  }
36059
+ const spinner = createSpinner("Provisioning your instance (~60s; safe to interrupt — instance will still be created; check `sl instance info`)");
36053
36060
  try {
36054
36061
  const res = await httpPlatform(`/api/projects/${encodeURIComponent(activeSlug)}/instance`, {
36055
36062
  method: "POST",
36056
- body: { plan }
36063
+ body: { plan },
36064
+ timeoutMs: INSTANCE_CREATE_TIMEOUT_MS
36057
36065
  });
36058
- success(`Instance provisioned: ${res.tenant.slug}`);
36066
+ spinner.succeed(`Instance provisioned: ${res.tenant.slug}`);
36059
36067
  printKeyReveal(res.tenant, res.credentials);
36060
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.");
36061
36075
  handleInstanceError(err, "provision instance");
36062
36076
  }
36063
36077
  });
@@ -36136,6 +36150,10 @@ function registerInstanceCommand(program2) {
36136
36150
  }
36137
36151
  const slug = info_.tenant.slug;
36138
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
+ }
36139
36157
  const typed = await input4({
36140
36158
  message: `Type the slug "${slug}" to confirm permanent deletion`,
36141
36159
  validate: (v) => v === slug ? true : "Slug must match exactly"
@@ -36303,6 +36321,43 @@ function printKeyReveal(tenant, creds) {
36303
36321
  console.log(dim("Run `sl subgraphs deploy <file>` to deploy your first subgraph."));
36304
36322
  console.log("");
36305
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
+ }
36306
36361
  function warn_box(message) {
36307
36362
  return `${"━".repeat(message.length + 4)}
36308
36363
  ${message}
@@ -36322,10 +36377,10 @@ function handleInstanceError(err, action) {
36322
36377
  error("This project already has an instance. Run `sl instance info` to see it.");
36323
36378
  process.exit(1);
36324
36379
  }
36325
- error(err.message);
36380
+ error(err.message || `Failed to ${action}.`);
36326
36381
  process.exit(1);
36327
36382
  }
36328
- 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)}`);
36329
36384
  process.exit(1);
36330
36385
  }
36331
36386
  // src/commands/project.ts
@@ -36336,15 +36391,24 @@ init_project_file();
36336
36391
  import { input as input5 } from "@inquirer/prompts";
36337
36392
  function registerProjectCommand(program2) {
36338
36393
  const project = program2.command("project").description("Manage Secondlayer projects");
36339
- 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 = {}) => {
36340
36395
  const name = nameArg ?? await input5({
36341
36396
  message: "Project name",
36342
36397
  validate: (v) => v.length >= 2 ? true : "Name must be at least 2 characters"
36343
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
+ }
36344
36405
  try {
36345
- const res = await httpPlatform("/api/projects", { method: "POST", body: { name } });
36346
- success(`Created project ${res.project.name} (${res.project.slug})`);
36347
- 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());
36348
36412
  info(dim(`Bound to this directory → ${path2}`));
36349
36413
  info(dim("Next: sl instance create --plan launch"));
36350
36414
  } catch (err) {
@@ -36411,6 +36475,18 @@ function handleProjectError(err) {
36411
36475
  error(err instanceof Error ? err.message : String(err));
36412
36476
  process.exit(1);
36413
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
+ }
36414
36490
  // src/cli.ts
36415
36491
  var { version } = package_default;
36416
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)");
@@ -36452,5 +36528,5 @@ registerLocalCommand(program);
36452
36528
  registerAccountCommand(program);
36453
36529
  program.parse();
36454
36530
 
36455
- //# debugId=4D8FC47BED33A85764756E2164756E21
36531
+ //# debugId=BE2AD99915C01F0964756E2164756E21
36456
36532
  //# sourceMappingURL=cli.js.map