@secondlayer/cli 3.4.0 → 3.5.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/README.md CHANGED
@@ -8,14 +8,33 @@ bun add -g @secondlayer/cli
8
8
  sl --version
9
9
  ```
10
10
 
11
- ## Quickstart
11
+ ## Beta Quickstart
12
+
13
+ Use this path for a first hosted demo. The CLI stores your login session,
14
+ binds the current directory to a project, provisions a dedicated tenant, then
15
+ deploys and queries a subgraph through short-lived tenant credentials.
16
+
17
+ ```bash
18
+ bun add -g @secondlayer/cli
19
+
20
+ sl login
21
+ sl project create my-app
22
+ sl project use my-app
23
+ sl instance create --plan hobby
24
+
25
+ sl subgraphs scaffold SP1234ABCD.my-contract -o subgraphs/my-contract.ts
26
+ sl subgraphs deploy subgraphs/my-contract.ts --start-block <recent-block>
27
+ sl subgraphs query my-contract <table> --sort _block_height --order desc
28
+ ```
29
+
30
+ Then wire a receiver:
12
31
 
13
32
  ```bash
14
- sl login # magic-link auth, session cached at ~/.secondlayer/session.json
15
- sl project create my-app # scaffold a project
16
- sl project use my-app # bind cwd to the project (writes ./.secondlayer/project)
17
- sl instance create --plan launch # provision dedicated Postgres + API + processor
18
- sl subgraphs deploy ./x.ts # deploy to your instance
33
+ sl create subscription my-hook \
34
+ --runtime node \
35
+ --subgraph my-contract \
36
+ --table <table> \
37
+ --url https://<receiver-host>/webhook
19
38
  ```
20
39
 
21
40
  ## Command surface
@@ -49,7 +68,7 @@ One instance per project. The platform API spawns a dedicated `sl-pg-{slug}`,
49
68
 
50
69
  | Command | What it does |
51
70
  |---|---|
52
- | `sl instance create --plan <launch\|grow\|scale>` | Provision containers. Boxed reveal of `serviceKey` + `anonKey` (shown once). |
71
+ | `sl instance create --plan <hobby\|launch\|grow\|scale>` | Provision containers. Boxed reveal of `serviceKey` + `anonKey` (shown once). |
53
72
  | `sl instance info` | Plan, status, resource usage |
54
73
  | `sl instance resize --plan <...>` | Recreate containers with new CPU/memory (~30s downtime) |
55
74
  | `sl instance suspend` / `resume` | Stop/start containers, volume preserved |
package/dist/cli.js CHANGED
@@ -2529,7 +2529,8 @@ async function request(url, opts) {
2529
2529
  const res = await fetch(url, {
2530
2530
  method: opts.method ?? "GET",
2531
2531
  headers,
2532
- body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined
2532
+ body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
2533
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
2533
2534
  });
2534
2535
  if (!res.ok) {
2535
2536
  let body = {};
@@ -2559,7 +2560,7 @@ async function httpPlatform(path2, opts = {}) {
2559
2560
  async function httpPlatformAnon(path2, opts = {}) {
2560
2561
  return request(`${PLATFORM_API_URL}${path2}`, opts);
2561
2562
  }
2562
- var CliHttpError, PLATFORM_API_URL;
2563
+ var CliHttpError, PLATFORM_API_URL, REQUEST_TIMEOUT_MS = 30000;
2563
2564
  var init_http = __esm(() => {
2564
2565
  init_session();
2565
2566
  CliHttpError = class CliHttpError extends Error {
@@ -12996,7 +12997,7 @@ class StacksApiClient {
12996
12997
  constructor(network = "mainnet", apiKey, apiUrl, slApiUrl) {
12997
12998
  this.useProxy = !apiUrl && network !== "devnet";
12998
12999
  if (this.useProxy) {
12999
- this.baseUrl = slApiUrl || "";
13000
+ this.baseUrl = slApiUrl?.replace(/\/$/, "") || "";
13000
13001
  this.headers = {};
13001
13002
  } else {
13002
13003
  this.baseUrl = apiUrl || process.env.STACKS_NODE_RPC_URL || "http://localhost:3999";
@@ -13007,24 +13008,31 @@ class StacksApiClient {
13007
13008
  if (!this.useProxy || this.baseUrl)
13008
13009
  return;
13009
13010
  const { apiUrl, ephemeralKey } = await resolveActiveTenant();
13010
- this.baseUrl = apiUrl;
13011
+ this.baseUrl = apiUrl.replace(/\/$/, "");
13011
13012
  this.headers = { authorization: `Bearer ${ephemeralKey}` };
13012
13013
  }
13014
+ describeContractInfoSource() {
13015
+ if (this.useProxy)
13016
+ return "Secondlayer node";
13017
+ return `Stacks node RPC at ${this.baseUrl}`;
13018
+ }
13013
13019
  async fetchWithErrorHandling(url, resourceType, resourceId) {
13014
13020
  try {
13015
- const response2 = await gotWithRetry(url, {
13021
+ const response2 = await contractFetch(url, {
13016
13022
  headers: this.headers,
13017
13023
  responseType: "json"
13018
13024
  });
13019
13025
  return response2.body;
13020
13026
  } catch (error2) {
13021
- if (error2.response?.statusCode === 401) {
13027
+ const statusCode = typeof error2 === "object" && error2 !== null && "response" in error2 && typeof error2.response === "object" && error2.response !== null && "statusCode" in error2.response ? error2.response.statusCode : undefined;
13028
+ const message = error2 instanceof Error ? error2.message : String(error2);
13029
+ if (statusCode === 401) {
13022
13030
  throw new Error("Authentication required. Run: secondlayer auth login");
13023
13031
  }
13024
- if (error2.response?.statusCode === 404) {
13032
+ if (statusCode === 404) {
13025
13033
  throw new Error(`${resourceType} not found: ${resourceId}`);
13026
13034
  }
13027
- throw new Error(`Failed to fetch ${resourceType.toLowerCase()}: ${error2.message}`);
13035
+ throw new Error(`Failed to fetch ${resourceType.toLowerCase()}: ${message}`);
13028
13036
  }
13029
13037
  }
13030
13038
  async getContractInfo(contractId) {
@@ -13045,17 +13053,16 @@ class StacksApiClient {
13045
13053
  return data.source;
13046
13054
  }
13047
13055
  }
13048
- var gotWithRetry;
13056
+ var contractFetch;
13049
13057
  var init_api = __esm(() => {
13050
13058
  init_source3();
13051
13059
  init_resolve_tenant();
13052
- gotWithRetry = source_default2.extend({
13053
- timeout: { request: 30000 },
13060
+ contractFetch = source_default2.extend({
13061
+ timeout: { request: 15000 },
13054
13062
  retry: {
13055
- limit: 3,
13063
+ limit: 0,
13056
13064
  methods: ["GET", "POST"],
13057
- statusCodes: [408, 429, 500, 502, 503, 504],
13058
- calculateDelay: ({ attemptCount }) => attemptCount * 1000
13065
+ statusCodes: [408, 429, 500, 502, 503, 504]
13059
13066
  }
13060
13067
  });
13061
13068
  });
@@ -32224,7 +32231,7 @@ Examples:`));
32224
32231
  No .clar files or contract addresses matched the provided inputs`));
32225
32232
  process.exit(1);
32226
32233
  }
32227
- const apiKey = options3.apiKey || process.env.HIRO_API_KEY;
32234
+ const apiKey = options3.apiKey || process.env.STACKS_NODE_API_KEY || process.env.HIRO_API_KEY;
32228
32235
  config = await buildConfigFromInputs(parsedInputs, options3.out, apiKey);
32229
32236
  } else {
32230
32237
  config = await loadConfig2(options3.config);
@@ -32430,7 +32437,7 @@ var {
32430
32437
  // package.json
32431
32438
  var package_default = {
32432
32439
  name: "@secondlayer/cli",
32433
- version: "3.4.0",
32440
+ version: "3.5.0",
32434
32441
  description: "CLI for subgraphs and blockchain indexing on Stacks",
32435
32442
  type: "module",
32436
32443
  bin: {
@@ -32472,11 +32479,11 @@ var package_default = {
32472
32479
  license: "MIT",
32473
32480
  dependencies: {
32474
32481
  "@inquirer/prompts": "^8.2.0",
32475
- "@secondlayer/bundler": "^0.3.1",
32482
+ "@secondlayer/bundler": "^0.3.2",
32476
32483
  "@secondlayer/sdk": "^3.2.0",
32477
32484
  "@secondlayer/shared": "^4.2.0",
32478
32485
  "@secondlayer/stacks": "^2.0.0",
32479
- "@secondlayer/subgraphs": "^1.2.0",
32486
+ "@secondlayer/subgraphs": "^1.2.1",
32480
32487
  "@biomejs/js-api": "^0.7.0",
32481
32488
  "@biomejs/wasm-nodejs": "^1.9.0",
32482
32489
  esbuild: "^0.19.0",
@@ -34576,6 +34583,48 @@ function parseStartBlockOption(value) {
34576
34583
  }
34577
34584
  return parsed;
34578
34585
  }
34586
+ function createSubgraphDeployPreview(def, options2 = {}) {
34587
+ const tableColumns = Object.entries(def.schema).map(([table, schema]) => `${table}: ${Object.keys(schema.columns).join(", ") || "(no columns)"}`);
34588
+ return {
34589
+ name: def.name,
34590
+ version: def.version ?? "(auto)",
34591
+ description: def.description ?? "",
34592
+ startBlock: String(def.startBlock ?? 1),
34593
+ sources: Object.keys(def.sources).join(", ") || "(none)",
34594
+ handlers: Object.keys(def.handlers).join(", ") || "(none)",
34595
+ tables: Object.keys(def.schema).join(", ") || "(none)",
34596
+ tableColumns,
34597
+ ...options2.bundleBytes !== undefined ? { bundleSize: `${options2.bundleBytes} bytes` } : {}
34598
+ };
34599
+ }
34600
+ function printSubgraphDeployPreview(preview, context) {
34601
+ success("Subgraph deploy dry run passed");
34602
+ const pairs = [
34603
+ ["File", context.file],
34604
+ ["Network", context.network],
34605
+ ["Name", preview.name],
34606
+ ["Version", preview.version],
34607
+ ["Start Block", preview.startBlock],
34608
+ ["Sources", preview.sources],
34609
+ ["Handlers", preview.handlers],
34610
+ ["Tables", preview.tables]
34611
+ ];
34612
+ if (preview.bundleSize)
34613
+ pairs.push(["Bundle Size", preview.bundleSize]);
34614
+ console.log(formatKeyValue(pairs));
34615
+ if (preview.description) {
34616
+ console.log(`
34617
+ ${dim("Description")} ${preview.description}`);
34618
+ }
34619
+ if (preview.tableColumns.length > 0) {
34620
+ console.log(`
34621
+ ${dim("Columns")}`);
34622
+ for (const line of preview.tableColumns)
34623
+ console.log(` ${line}`);
34624
+ }
34625
+ const deployTarget = context.bundled ? "tenant API" : "local database";
34626
+ info(`Dry run only. No ${deployTarget} changes were made.`);
34627
+ }
34579
34628
  function registerSubgraphsCommand(program2) {
34580
34629
  const subgraphs = program2.command("subgraphs").description("Manage materialized subgraphs");
34581
34630
  subgraphs.command("new <name>").description("Scaffold a new subgraph definition file").action(async (name) => {
@@ -34654,10 +34703,11 @@ Stopped watching.`);
34654
34703
  });
34655
34704
  await new Promise(() => {});
34656
34705
  });
34657
- 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("--force", "Skip confirmation prompt for reindex operations").action(async (file, options2) => {
34706
+ 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) => {
34658
34707
  try {
34659
34708
  const absPath = resolve3(file);
34660
34709
  const config = await loadConfig();
34710
+ const dryRun = options2.dryRun || options2.preview;
34661
34711
  const startBlock = parseStartBlockOption(options2.startBlock);
34662
34712
  if (startBlock !== undefined) {
34663
34713
  warn(`--start-block ${startBlock} overrides the definition's startBlock for this deploy.`);
@@ -34667,14 +34717,27 @@ Stopped watching.`);
34667
34717
  const def = mod.default ?? mod;
34668
34718
  const effectiveDef = startBlock === undefined ? def : { ...def, startBlock };
34669
34719
  const { validateSubgraphDefinition } = await import("@secondlayer/subgraphs/validate");
34670
- validateSubgraphDefinition(effectiveDef);
34720
+ const validated = validateSubgraphDefinition(effectiveDef);
34671
34721
  if (config.network !== "local") {
34672
- info(`Bundling for remote deploy (${config.network})...`);
34722
+ info(`${dryRun ? "Bundling for remote deploy dry run" : "Bundling for remote deploy"} (${config.network})...`);
34673
34723
  const { readFile: readFile4 } = await import("node:fs/promises");
34674
34724
  const source = await readFile4(absPath, "utf8");
34675
34725
  const { bundleSubgraphCode } = await import("@secondlayer/bundler");
34676
34726
  const bundled = await bundleSubgraphCode(source);
34677
34727
  const handlerCode = bundled.handlerCode;
34728
+ if (dryRun) {
34729
+ printSubgraphDeployPreview(createSubgraphDeployPreview({
34730
+ ...validated,
34731
+ version: options2.version ?? validated.version
34732
+ }, {
34733
+ bundleBytes: Buffer.byteLength(handlerCode, "utf8")
34734
+ }), {
34735
+ network: config.network,
34736
+ file: absPath,
34737
+ bundled: true
34738
+ });
34739
+ return;
34740
+ }
34678
34741
  const result = await deploySubgraphApi({
34679
34742
  name: effectiveDef.name,
34680
34743
  version: options2.version,
@@ -34722,6 +34785,17 @@ Stopped watching.`);
34722
34785
  success(`Subgraph "${effectiveDef.name}" updated → v${result.version}`);
34723
34786
  }
34724
34787
  } else {
34788
+ if (dryRun) {
34789
+ printSubgraphDeployPreview(createSubgraphDeployPreview({
34790
+ ...validated,
34791
+ version: options2.version ?? validated.version
34792
+ }), {
34793
+ network: config.network,
34794
+ file: absPath,
34795
+ bundled: false
34796
+ });
34797
+ return;
34798
+ }
34725
34799
  const { deploySchema } = await import("@secondlayer/subgraphs");
34726
34800
  const { getDb: getDb2, closeDb } = await import("@secondlayer/shared/db");
34727
34801
  const db = getDb2();
@@ -34956,7 +35030,7 @@ ${rows.length} row(s)`));
34956
35030
  handleApiError(err, "delete subgraph");
34957
35031
  }
34958
35032
  });
34959
- subgraphs.command("scaffold <contractAddress>").description("Scaffold a defineSubgraph() file from a contract ABI").option("-o, --output <path>", "Output file path (required)").option("--api-key <key>", "Hiro API key").action(async (contractAddress, options2) => {
35033
+ subgraphs.command("scaffold <contractAddress>").description("Scaffold a defineSubgraph() file from a contract ABI").option("-o, --output <path>", "Output file path (required)").option("--api-key <key>", "Stacks node API key for direct RPC URLs").action(async (contractAddress, options2) => {
34960
35034
  try {
34961
35035
  if (!options2.output) {
34962
35036
  error("--output <path> is required");
@@ -34964,9 +35038,9 @@ ${rows.length} row(s)`));
34964
35038
  }
34965
35039
  const outPath = resolve3(options2.output);
34966
35040
  const network = inferNetwork(contractAddress) ?? "mainnet";
34967
- const apiKey = options2.apiKey ?? process.env.HIRO_API_KEY;
34968
- info(`Fetching ABI for ${contractAddress}...`);
35041
+ const apiKey = options2.apiKey ?? process.env.STACKS_NODE_API_KEY ?? process.env.HIRO_API_KEY;
34969
35042
  const client = new StacksApiClient(network, apiKey);
35043
+ info(`Fetching ABI for ${contractAddress} via ${client.describeContractInfoSource()}...`);
34970
35044
  const contractInfo = await client.getContractInfo(contractAddress);
34971
35045
  const abi = parseApiResponse(contractInfo);
34972
35046
  info("Generating scaffold...");
@@ -36324,7 +36398,7 @@ Quickstart:
36324
36398
  $ sl instance create --plan launch # Provision a dedicated instance
36325
36399
  $ sl subgraphs deploy ./x.ts # Deploy a subgraph — targets your instance
36326
36400
  `);
36327
- program.command("generate [files...]").aliases(["gen", "codegen"]).description("Generate TypeScript interfaces from Clarity contracts").option("-c, --config <path>", "Path to config file").option("-o, --out <path>", "Output file path (required when using direct files)").option("-k, --api-key <key>", "Hiro API key (or set HIRO_API_KEY env var)").option("-w, --watch", "Watch for changes").action(async (files, options3) => {
36401
+ program.command("generate [files...]").aliases(["gen", "codegen"]).description("Generate TypeScript interfaces from Clarity contracts").option("-c, --config <path>", "Path to config file").option("-o, --out <path>", "Output file path (required when using direct files)").option("-k, --api-key <key>", "Stacks node API key for direct RPC URLs").option("-w, --watch", "Watch for changes").action(async (files, options3) => {
36328
36402
  const { generate: generate2 } = await Promise.resolve().then(() => (init_generate(), exports_generate));
36329
36403
  await generate2(files, options3);
36330
36404
  });
@@ -36349,5 +36423,5 @@ registerLocalCommand(program);
36349
36423
  registerAccountCommand(program);
36350
36424
  program.parse();
36351
36425
 
36352
- //# debugId=5D73330C3279063064756E2164756E21
36426
+ //# debugId=02FDB52BEFC9082464756E2164756E21
36353
36427
  //# sourceMappingURL=cli.js.map