@secondlayer/cli 5.8.0 → 5.9.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
@@ -2565,6 +2565,9 @@ function cyan(text) {
2565
2565
  function dim(text) {
2566
2566
  return `${colors.dim}${text}${colors.reset}`;
2567
2567
  }
2568
+ function bold(text) {
2569
+ return `${colors.bold}${text}${colors.reset}`;
2570
+ }
2568
2571
  function success(message) {
2569
2572
  console.log(green(`✓ ${message}`));
2570
2573
  }
@@ -5906,8 +5909,8 @@ var require_source = __commonJS((exports, module) => {
5906
5909
  var deferToConnect = (socket, fn) => {
5907
5910
  let listeners;
5908
5911
  if (typeof fn === "function") {
5909
- const connect = fn;
5910
- listeners = { connect };
5912
+ const connect2 = fn;
5913
+ listeners = { connect: connect2 };
5911
5914
  } else {
5912
5915
  listeners = fn;
5913
5916
  }
@@ -9808,7 +9811,7 @@ var require_client_request = __commonJS((exports, module) => {
9808
9811
  // ../../node_modules/resolve-alpn/index.js
9809
9812
  var require_resolve_alpn = __commonJS((exports, module) => {
9810
9813
  var tls2 = __require("tls");
9811
- module.exports = (options = {}, connect = tls2.connect) => new Promise((resolve2, reject) => {
9814
+ module.exports = (options = {}, connect2 = tls2.connect) => new Promise((resolve2, reject) => {
9812
9815
  let timeout = false;
9813
9816
  let socket;
9814
9817
  const callback = async () => {
@@ -9832,7 +9835,7 @@ var require_resolve_alpn = __commonJS((exports, module) => {
9832
9835
  };
9833
9836
  const socketPromise = (async () => {
9834
9837
  try {
9835
- socket = await connect(options, callback);
9838
+ socket = await connect2(options, callback);
9836
9839
  socket.on("error", reject);
9837
9840
  socket.once("timeout", onTimeout);
9838
9841
  } catch (error2) {
@@ -9910,7 +9913,7 @@ var require_auto = __commonJS((exports, module) => {
9910
9913
  socket.on("agentRemove", onRemove);
9911
9914
  agent.emit("free", socket, options);
9912
9915
  };
9913
- var createResolveProtocol = (cache2, queue2 = new Map, connect = undefined) => {
9916
+ var createResolveProtocol = (cache2, queue2 = new Map, connect2 = undefined) => {
9914
9917
  return async (options) => {
9915
9918
  const name = `${options.host}:${options.port}:${options.ALPNProtocols.sort()}`;
9916
9919
  if (!cache2.has(name)) {
@@ -9920,7 +9923,7 @@ var require_auto = __commonJS((exports, module) => {
9920
9923
  }
9921
9924
  const { path: path2 } = options;
9922
9925
  options.path = options.socketPath;
9923
- const resultPromise = resolveALPN(options, connect);
9926
+ const resultPromise = resolveALPN(options, connect2);
9924
9927
  queue2.set(name, resultPromise);
9925
9928
  try {
9926
9929
  const result = await resultPromise;
@@ -13345,10 +13348,10 @@ var init_manager = __esm(() => {
13345
13348
  });
13346
13349
 
13347
13350
  // src/services/indexer.ts
13348
- import { dirname as dirname4, resolve as resolve3 } from "node:path";
13351
+ import { dirname as dirname6, resolve as resolve3 } from "node:path";
13349
13352
  async function startIndexer(options2) {
13350
13353
  const port = options2.port ?? 3700;
13351
- const rootDir = dirname4(dirname4(dirname4(dirname4(import.meta.dir))));
13354
+ const rootDir = dirname6(dirname6(dirname6(dirname6(import.meta.dir))));
13352
13355
  const indexerPath = resolve3(rootDir, "packages/indexer/src/index.ts");
13353
13356
  await serviceManager.start(SERVICE_NAME, ["bun", "run", "--watch", indexerPath], {
13354
13357
  port,
@@ -13365,9 +13368,9 @@ var init_indexer = __esm(() => {
13365
13368
  });
13366
13369
 
13367
13370
  // src/services/worker.ts
13368
- import { dirname as dirname5, resolve as resolve4 } from "node:path";
13371
+ import { dirname as dirname7, resolve as resolve4 } from "node:path";
13369
13372
  async function startWorker(options2) {
13370
- const rootDir = dirname5(dirname5(dirname5(dirname5(import.meta.dir))));
13373
+ const rootDir = dirname7(dirname7(dirname7(dirname7(import.meta.dir))));
13371
13374
  const workerPath = resolve4(rootDir, "packages/worker/src/index.ts");
13372
13375
  await serviceManager.start(SERVICE_NAME2, ["bun", "run", "--watch", workerPath], {
13373
13376
  onStdout: options2.onLog,
@@ -13380,10 +13383,10 @@ var init_worker = __esm(() => {
13380
13383
  });
13381
13384
 
13382
13385
  // src/services/api.ts
13383
- import { dirname as dirname6, resolve as resolve5 } from "node:path";
13386
+ import { dirname as dirname8, resolve as resolve5 } from "node:path";
13384
13387
  async function startApi(options2) {
13385
13388
  const port = options2.port ?? 3800;
13386
- const rootDir = dirname6(dirname6(dirname6(dirname6(import.meta.dir))));
13389
+ const rootDir = dirname8(dirname8(dirname8(dirname8(import.meta.dir))));
13387
13390
  const apiPath = resolve5(rootDir, "packages/api/src/index.ts");
13388
13391
  await serviceManager.start(SERVICE_NAME3, ["bun", "run", "--watch", apiPath], {
13389
13392
  port,
@@ -13400,9 +13403,9 @@ var init_api2 = __esm(() => {
13400
13403
  });
13401
13404
 
13402
13405
  // src/services/subgraph-processor.ts
13403
- import { dirname as dirname7, resolve as resolve6 } from "node:path";
13406
+ import { dirname as dirname9, resolve as resolve6 } from "node:path";
13404
13407
  async function startSubgraphProcessor(options2) {
13405
- const rootDir = dirname7(dirname7(dirname7(dirname7(import.meta.dir))));
13408
+ const rootDir = dirname9(dirname9(dirname9(dirname9(import.meta.dir))));
13406
13409
  const servicePath = resolve6(rootDir, "packages/subgraphs/src/service.ts");
13407
13410
  await serviceManager.start(SERVICE_NAME4, ["bun", "run", "--watch", servicePath], {
13408
13411
  env: {
@@ -13437,8 +13440,8 @@ __export(exports_dev_impl, {
13437
13440
  restartDev: () => restartDev,
13438
13441
  isDevAlreadyRunning: () => isDevAlreadyRunning
13439
13442
  });
13440
- import { mkdirSync as mkdirSync3 } from "node:fs";
13441
- import { dirname as dirname8, join as join7, resolve as resolve7 } from "node:path";
13443
+ import { mkdirSync as mkdirSync4 } from "node:fs";
13444
+ import { dirname as dirname10, join as join9, resolve as resolve7 } from "node:path";
13442
13445
  async function isDevAlreadyRunning() {
13443
13446
  if (await isDevRunning()) {
13444
13447
  const running = await getRunningServices();
@@ -13516,7 +13519,7 @@ async function runBackground(options2) {
13516
13519
  startedAt: new Date().toISOString()
13517
13520
  };
13518
13521
  try {
13519
- const packagesDir = dirname8(dirname8(dirname8(dirname8(import.meta.dir))));
13522
+ const packagesDir = dirname10(dirname10(dirname10(dirname10(import.meta.dir))));
13520
13523
  const env = { DATABASE_URL: databaseUrl, DEV_MODE: "true" };
13521
13524
  const apiLogFile = getLogFile("api");
13522
13525
  const apiProc = Bun.spawn(["bun", "run", resolve7(packagesDir, "packages/api/src/index.ts")], {
@@ -13866,7 +13869,7 @@ async function restartDev() {
13866
13869
  await clearDevState();
13867
13870
  await clearLogs();
13868
13871
  const config = await loadConfig();
13869
- const packagesDir = dirname8(dirname8(dirname8(dirname8(import.meta.dir))));
13872
+ const packagesDir = dirname10(dirname10(dirname10(dirname10(import.meta.dir))));
13870
13873
  const env = state.env;
13871
13874
  const newState = {
13872
13875
  services: {},
@@ -14047,8 +14050,8 @@ async function ensureDevPostgres(dataDir) {
14047
14050
  if (check.stdout.toString().trim()) {
14048
14051
  return false;
14049
14052
  }
14050
- const pgDataDir = join7(dataDir, "postgres");
14051
- mkdirSync3(pgDataDir, { recursive: true });
14053
+ const pgDataDir = join9(dataDir, "postgres");
14054
+ mkdirSync4(pgDataDir, { recursive: true });
14052
14055
  const stopped = await Bun.$`docker ps -aq -f name=secondlayer-dev-postgres`.quiet().nothrow();
14053
14056
  if (stopped.stdout.toString().trim()) {
14054
14057
  await Bun.$`docker rm secondlayer-dev-postgres`.quiet().nothrow();
@@ -14063,7 +14066,7 @@ async function ensureDevPostgres(dataDir) {
14063
14066
  throw new Error("PostgreSQL failed to start");
14064
14067
  }
14065
14068
  async function runMigrations(databaseUrl) {
14066
- const packagesDir = dirname8(dirname8(dirname8(dirname8(import.meta.dir))));
14069
+ const packagesDir = dirname10(dirname10(dirname10(dirname10(import.meta.dir))));
14067
14070
  const migrateScript = resolve7(packagesDir, "packages/shared/src/db/migrate.ts");
14068
14071
  const result = await Bun.$`DATABASE_URL=${databaseUrl} bun run ${migrateScript}`.quiet().nothrow();
14069
14072
  if (result.exitCode !== 0) {
@@ -21791,11 +21794,11 @@ var init_commands = __esm(() => {
21791
21794
  });
21792
21795
 
21793
21796
  // ../../node_modules/@antfu/ni/dist/shared/ni.B5qNAuoI.mjs
21794
- import path3, { join as join9, dirname as dirname10, resolve as resolve9 } from "node:path";
21797
+ import path3, { join as join11, dirname as dirname12, resolve as resolve9 } from "node:path";
21795
21798
  import process$1 from "node:process";
21796
21799
  import require$$0 from "readline";
21797
21800
  import require$$2 from "events";
21798
- import fs5, { promises, existsSync as existsSync4 } from "node:fs";
21801
+ import fs5, { promises, existsSync as existsSync6 } from "node:fs";
21799
21802
  import require$$0$1 from "os";
21800
21803
  import require$$1 from "tty";
21801
21804
  import os4 from "node:os";
@@ -24806,7 +24809,7 @@ function requireLib() {
24806
24809
  return lib;
24807
24810
  hasRequiredLib = 1;
24808
24811
  const { isexe, sync: isexeSync } = requireCjs();
24809
- const { join: join10, delimiter, sep, posix: posix2 } = require$$1$2;
24812
+ const { join: join12, delimiter, sep, posix: posix2 } = require$$1$2;
24810
24813
  const isWindows = process.platform === "win32";
24811
24814
  const rSlash = new RegExp(`[${posix2.sep}${sep === posix2.sep ? "" : sep}]`.replace(/(\\)/g, "\\$1"));
24812
24815
  const rRel = new RegExp(`^\\.${rSlash.source}`);
@@ -24833,7 +24836,7 @@ function requireLib() {
24833
24836
  const getPathPart = (raw, cmd) => {
24834
24837
  const pathPart = /^".*"$/.test(raw) ? raw.slice(1, -1) : raw;
24835
24838
  const prefix = !pathPart && rRel.test(cmd) ? cmd.slice(0, 2) : "";
24836
- return prefix + join10(pathPart, cmd);
24839
+ return prefix + join12(pathPart, cmd);
24837
24840
  };
24838
24841
  const which = async (cmd, opt = {}) => {
24839
24842
  const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
@@ -24982,7 +24985,7 @@ var init_ni_B5qNAuoI = __esm(() => {
24982
24985
  options2 = {};
24983
24986
  libExports = requireLib();
24984
24987
  which = /* @__PURE__ */ getDefaultExportFromCjs(libExports);
24985
- CLI_TEMP_DIR = join9(os4.tmpdir(), "antfu-ni");
24988
+ CLI_TEMP_DIR = join11(os4.tmpdir(), "antfu-ni");
24986
24989
  customRcPath = process$1.env.NI_CONFIG_FILE;
24987
24990
  home = process$1.platform === "win32" ? process$1.env.USERPROFILE : process$1.env.HOME;
24988
24991
  defaultRcPath = path3.join(home || "~/", ".nirc");
@@ -25635,11 +25638,11 @@ var hasColors, format = (open, close) => {
25635
25638
  result += string.slice(lastIndex) + closeCode;
25636
25639
  return result;
25637
25640
  };
25638
- }, reset2, bold, dim2, italic, underline, overline, inverse, hidden, strikethrough, black, red2, green2, yellow2, blue2, magenta2, cyan2, white, gray, bgBlack, bgRed, bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite, bgGray, redBright, greenBright, yellowBright, blueBright, magentaBright, cyanBright, whiteBright, bgRedBright, bgGreenBright, bgYellowBright, bgBlueBright, bgMagentaBright, bgCyanBright, bgWhiteBright;
25641
+ }, reset2, bold2, dim2, italic, underline, overline, inverse, hidden, strikethrough, black, red2, green2, yellow2, blue2, magenta2, cyan2, white, gray, bgBlack, bgRed, bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite, bgGray, redBright, greenBright, yellowBright, blueBright, magentaBright, cyanBright, whiteBright, bgRedBright, bgGreenBright, bgYellowBright, bgBlueBright, bgMagentaBright, bgCyanBright, bgWhiteBright;
25639
25642
  var init_base = __esm(() => {
25640
25643
  hasColors = tty2?.WriteStream?.prototype?.hasColors?.() ?? false;
25641
25644
  reset2 = format(0, 0);
25642
- bold = format(1, 22);
25645
+ bold2 = format(1, 22);
25643
25646
  dim2 = format(2, 22);
25644
25647
  italic = format(3, 23);
25645
25648
  underline = format(4, 24);
@@ -25717,7 +25720,7 @@ var init_default = __esm(() => {
25717
25720
  duration: getFinalIcon
25718
25721
  };
25719
25722
  COLORS = {
25720
- command: () => bold,
25723
+ command: () => bold2,
25721
25724
  output: () => identity,
25722
25725
  ipc: () => identity,
25723
25726
  error: ({ reject }) => reject ? redBright : yellowBright,
@@ -26294,7 +26297,7 @@ var require_cross_spawn = __commonJS((exports, module) => {
26294
26297
  enoent.hookChildProcess(spawned, parsed);
26295
26298
  return spawned;
26296
26299
  }
26297
- function spawnSync(command, args, options3) {
26300
+ function spawnSync2(command, args, options3) {
26298
26301
  const parsed = parse2(command, args, options3);
26299
26302
  const result = cp.spawnSync(parsed.command, parsed.args, parsed.options);
26300
26303
  result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
@@ -26302,7 +26305,7 @@ var require_cross_spawn = __commonJS((exports, module) => {
26302
26305
  }
26303
26306
  module.exports = spawn2;
26304
26307
  module.exports.spawn = spawn2;
26305
- module.exports.sync = spawnSync;
26308
+ module.exports.sync = spawnSync2;
26306
26309
  module.exports._parse = parse2;
26307
26310
  module.exports._enoent = enoent;
26308
26311
  });
@@ -28706,7 +28709,7 @@ var init_stdio_option = __esm(() => {
28706
28709
  });
28707
28710
 
28708
28711
  // ../../node_modules/execa/lib/stdio/native.js
28709
- import { readFileSync as readFileSync3 } from "node:fs";
28712
+ import { readFileSync as readFileSync4 } from "node:fs";
28710
28713
  import tty3 from "node:tty";
28711
28714
  var handleNativeStream = ({ stdioItem, stdioItem: { type }, isStdioArray, fdNumber, direction, isSync }) => {
28712
28715
  if (!isStdioArray || type !== "native") {
@@ -28738,7 +28741,7 @@ var handleNativeStream = ({ stdioItem, stdioItem: { type }, isStdioArray, fdNumb
28738
28741
  if (tty3.isatty(targetFdNumber)) {
28739
28742
  throw new TypeError(`The \`${optionName}: ${serializeOptionValue(value)}\` option is invalid: it cannot be a TTY with synchronous methods.`);
28740
28743
  }
28741
- return { type: "uint8Array", value: bufferToUint8Array(readFileSync3(targetFdNumber)), optionName };
28744
+ return { type: "uint8Array", value: bufferToUint8Array(readFileSync4(targetFdNumber)), optionName };
28742
28745
  }, getTargetFdNumber = (value, fdNumber) => {
28743
28746
  if (value === "inherit") {
28744
28747
  return fdNumber;
@@ -29041,7 +29044,7 @@ var init_handle = __esm(() => {
29041
29044
  });
29042
29045
 
29043
29046
  // ../../node_modules/execa/lib/stdio/handle-sync.js
29044
- import { readFileSync as readFileSync4 } from "node:fs";
29047
+ import { readFileSync as readFileSync5 } from "node:fs";
29045
29048
  var handleStdioSync = (options3, verboseInfo) => handleStdio(addPropertiesSync, options3, verboseInfo, true), forbiddenIfSync = ({ type, optionName }) => {
29046
29049
  throwInvalidSyncValue(optionName, TYPE_TO_MESSAGE[type]);
29047
29050
  }, forbiddenNativeIfSync = ({ optionName, value }) => {
@@ -29069,8 +29072,8 @@ var init_handle_sync = __esm(() => {
29069
29072
  addPropertiesSync = {
29070
29073
  input: {
29071
29074
  ...addProperties,
29072
- fileUrl: ({ value }) => ({ contents: [bufferToUint8Array(readFileSync4(value))] }),
29073
- filePath: ({ value: { file } }) => ({ contents: [bufferToUint8Array(readFileSync4(file))] }),
29075
+ fileUrl: ({ value }) => ({ contents: [bufferToUint8Array(readFileSync5(value))] }),
29076
+ filePath: ({ value: { file } }) => ({ contents: [bufferToUint8Array(readFileSync5(file))] }),
29074
29077
  fileNumber: forbiddenIfSync,
29075
29078
  iterable: ({ value }) => ({ contents: [...value] }),
29076
29079
  string: ({ value }) => ({ contents: [value] }),
@@ -29439,7 +29442,7 @@ var init_output2 = __esm(() => {
29439
29442
  });
29440
29443
 
29441
29444
  // ../../node_modules/execa/lib/io/output-sync.js
29442
- import { writeFileSync as writeFileSync3, appendFileSync } from "node:fs";
29445
+ import { writeFileSync as writeFileSync5, appendFileSync } from "node:fs";
29443
29446
  var transformOutputSync = ({ fileDescriptors, syncResult: { output }, options: options3, isMaxBuffer, verboseInfo }) => {
29444
29447
  if (output === null) {
29445
29448
  return { output: Array.from({ length: 3 }) };
@@ -29532,7 +29535,7 @@ var transformOutputSync = ({ fileDescriptors, syncResult: { output }, options: o
29532
29535
  appendFileSync(path9, serializedResult);
29533
29536
  } else {
29534
29537
  outputFiles.add(pathString);
29535
- writeFileSync3(path9, serializedResult);
29538
+ writeFileSync5(path9, serializedResult);
29536
29539
  }
29537
29540
  }
29538
29541
  };
@@ -29629,7 +29632,7 @@ var init_exit_sync = __esm(() => {
29629
29632
  });
29630
29633
 
29631
29634
  // ../../node_modules/execa/lib/methods/main-sync.js
29632
- import { spawnSync } from "node:child_process";
29635
+ import { spawnSync as spawnSync2 } from "node:child_process";
29633
29636
  var execaCoreSync = (rawFile, rawArguments, rawOptions) => {
29634
29637
  const { file, commandArguments, command, escapedCommand, startTime, verboseInfo, options: options3, fileDescriptors } = handleSyncArguments(rawFile, rawArguments, rawOptions);
29635
29638
  const result = spawnSubprocessSync({
@@ -29714,7 +29717,7 @@ var execaCoreSync = (rawFile, rawArguments, rawOptions) => {
29714
29717
  try {
29715
29718
  addInputOptionsSync(fileDescriptors, options3);
29716
29719
  const normalizedOptions = normalizeSpawnSyncOptions(options3);
29717
- return spawnSync(...concatenateShell(file, commandArguments, normalizedOptions));
29720
+ return spawnSync2(...concatenateShell(file, commandArguments, normalizedOptions));
29718
29721
  } catch (error2) {
29719
29722
  return makeEarlyError({
29720
29723
  error: error2,
@@ -32321,7 +32324,7 @@ var {
32321
32324
  // package.json
32322
32325
  var package_default = {
32323
32326
  name: "@secondlayer/cli",
32324
- version: "5.8.0",
32327
+ version: "5.9.0",
32325
32328
  repository: {
32326
32329
  type: "git",
32327
32330
  url: "git+https://github.com/ryanwaits/secondlayer.git",
@@ -32377,6 +32380,7 @@ var package_default = {
32377
32380
  "@biomejs/js-api": "^0.7.0",
32378
32381
  "@biomejs/wasm-nodejs": "^1.9.0",
32379
32382
  esbuild: "^0.19.0",
32383
+ "smol-toml": "^1.6.1",
32380
32384
  zod: "^4.3.6"
32381
32385
  },
32382
32386
  devDependencies: {
@@ -34429,16 +34433,303 @@ async function resyncDatabase(skipConfirm, backfill) {
34429
34433
  process.exit(1);
34430
34434
  }
34431
34435
  }
34436
+ // src/commands/devnet.ts
34437
+ import { spawnSync } from "node:child_process";
34438
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "node:fs";
34439
+ import { dirname as dirname4, join as join7 } from "node:path";
34440
+
34441
+ // src/lib/devnet-compose.ts
34442
+ var DEFAULT_IMAGE_OWNER = "ryanwaits";
34443
+ var DEFAULT_IMAGE_TAG = "latest";
34444
+ function buildDevnetCompose(opts = {}) {
34445
+ const owner = opts.owner ?? DEFAULT_IMAGE_OWNER;
34446
+ const tag = opts.imageTag ?? DEFAULT_IMAGE_TAG;
34447
+ const apiPort = opts.apiPort ?? 3800;
34448
+ const indexerPort = opts.indexerPort ?? 3700;
34449
+ const apiImage = `ghcr.io/${owner}/secondlayer-api:${tag}`;
34450
+ const indexerImage = `ghcr.io/${owner}/secondlayer-indexer:${tag}`;
34451
+ const dbUrl = "postgres://secondlayer:secondlayer@postgres:5432/secondlayer";
34452
+ return `# Generated by \`sl devnet connect\` — do not edit by hand.
34453
+ # Secondlayer app services for local clarinet devnet development.
34454
+ name: secondlayer-devnet
34455
+
34456
+ services:
34457
+ postgres:
34458
+ image: postgres:17-alpine
34459
+ environment:
34460
+ POSTGRES_USER: secondlayer
34461
+ POSTGRES_PASSWORD: secondlayer
34462
+ POSTGRES_DB: secondlayer
34463
+ volumes:
34464
+ - postgres_data:/var/lib/postgresql/data
34465
+ healthcheck:
34466
+ test: ["CMD-SHELL", "pg_isready -U secondlayer"]
34467
+ interval: 5s
34468
+ timeout: 5s
34469
+ retries: 10
34470
+ restart: unless-stopped
34471
+
34472
+ migrate:
34473
+ image: ${apiImage}
34474
+ command: ["bun", "run", "packages/shared/src/db/migrate.ts"]
34475
+ environment:
34476
+ DATABASE_URL: ${dbUrl}
34477
+ depends_on:
34478
+ postgres:
34479
+ condition: service_healthy
34480
+ restart: "no"
34481
+
34482
+ api:
34483
+ image: ${apiImage}
34484
+ environment:
34485
+ INSTANCE_MODE: oss
34486
+ DATABASE_URL: ${dbUrl}
34487
+ PORT: 3800
34488
+ NODE_ENV: production
34489
+ DATA_DIR: /data
34490
+ ports:
34491
+ - "${apiPort}:3800"
34492
+ volumes:
34493
+ - subgraphs_data:/data/subgraphs
34494
+ depends_on:
34495
+ migrate:
34496
+ condition: service_completed_successfully
34497
+ postgres:
34498
+ condition: service_healthy
34499
+ healthcheck:
34500
+ test: ["CMD", "curl", "-f", "http://localhost:3800/health"]
34501
+ interval: 10s
34502
+ timeout: 3s
34503
+ start_period: 10s
34504
+ retries: 5
34505
+ restart: unless-stopped
34506
+
34507
+ indexer:
34508
+ image: ${indexerImage}
34509
+ environment:
34510
+ DATABASE_URL: ${dbUrl}
34511
+ PORT: 3700
34512
+ NODE_ENV: production
34513
+ # Keep every network key aligned: the indexer writes index_progress under
34514
+ # STACKS_NETWORK, the subgraph processor reads it under NETWORK. If they
34515
+ # differ, catch-up finds no progress row and subgraphs never advance.
34516
+ NETWORKS: testnet
34517
+ STACKS_NETWORK: testnet
34518
+ NETWORK: testnet
34519
+ # Fed by the devnet node's event observer — never reach out to Hiro mainnet.
34520
+ AUTO_BACKFILL_ENABLED: "false"
34521
+ TIP_FOLLOWER_ENABLED: "false"
34522
+ # Bind on all interfaces so the devnet's stacks-node container can POST to
34523
+ # host.docker.internal:3700.
34524
+ ports:
34525
+ - "${indexerPort}:3700"
34526
+ extra_hosts:
34527
+ - "host.docker.internal:host-gateway"
34528
+ depends_on:
34529
+ migrate:
34530
+ condition: service_completed_successfully
34531
+ postgres:
34532
+ condition: service_healthy
34533
+ healthcheck:
34534
+ test: ["CMD", "curl", "-f", "http://localhost:3700/health"]
34535
+ interval: 10s
34536
+ timeout: 3s
34537
+ start_period: 10s
34538
+ retries: 5
34539
+ restart: unless-stopped
34540
+
34541
+ subgraph-processor:
34542
+ image: ${apiImage}
34543
+ command: ["bun", "run", "packages/subgraphs/src/service.ts"]
34544
+ environment:
34545
+ DATABASE_URL: ${dbUrl}
34546
+ NODE_ENV: production
34547
+ DATA_DIR: /data
34548
+ # Must match the indexer's network key (see indexer note) — catch-up reads
34549
+ # index_progress under NETWORK.
34550
+ NETWORK: testnet
34551
+ STACKS_NETWORK: testnet
34552
+ volumes:
34553
+ - subgraphs_data:/data/subgraphs
34554
+ depends_on:
34555
+ migrate:
34556
+ condition: service_completed_successfully
34557
+ postgres:
34558
+ condition: service_healthy
34559
+ healthcheck:
34560
+ disable: true
34561
+ restart: unless-stopped
34562
+
34563
+ volumes:
34564
+ postgres_data:
34565
+ subgraphs_data:
34566
+ `;
34567
+ }
34568
+
34569
+ // src/lib/devnet-config.ts
34570
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
34571
+ import { dirname as dirname3, join as join6, parse as parsePath } from "node:path";
34572
+ import { parse as parseToml } from "smol-toml";
34573
+ var DEFAULT_INDEXER_OBSERVER = "host.docker.internal:3700";
34574
+ function findClarinetProject(startDir) {
34575
+ let dir = startDir;
34576
+ const { root } = parsePath(dir);
34577
+ while (true) {
34578
+ if (existsSync2(join6(dir, "Clarinet.toml")))
34579
+ return dir;
34580
+ if (dir === root)
34581
+ return null;
34582
+ dir = dirname3(dir);
34583
+ }
34584
+ }
34585
+ function ensureEventObserver(devnetTomlPath, endpoint = DEFAULT_INDEXER_OBSERVER) {
34586
+ const exists = existsSync2(devnetTomlPath);
34587
+ const original = exists ? readFileSync2(devnetTomlPath, "utf8") : "";
34588
+ if (original.trim()) {
34589
+ const parsed = parseToml(original);
34590
+ const observers = parsed.devnet?.stacks_node_events_observers;
34591
+ if (Array.isArray(observers) && observers.includes(endpoint)) {
34592
+ return { status: "present" };
34593
+ }
34594
+ }
34595
+ const entry = JSON.stringify(endpoint);
34596
+ const arrayStart = original.match(/^[^#\n]*stacks_node_events_observers\s*=\s*\[/m);
34597
+ if (arrayStart) {
34598
+ const at = (arrayStart.index ?? 0) + arrayStart[0].length;
34599
+ const next = original.slice(at).trimStart().startsWith("]") ? entry : `${entry}, `;
34600
+ const patched = original.slice(0, at) + next + original.slice(at);
34601
+ writeFileSync2(devnetTomlPath, patched);
34602
+ return { status: "added" };
34603
+ }
34604
+ const header = original.match(/^\[devnet\]\s*$/m);
34605
+ if (header) {
34606
+ const at = (header.index ?? 0) + header[0].length;
34607
+ const patched = `${original.slice(0, at)}
34608
+ stacks_node_events_observers = [${entry}]${original.slice(at)}`;
34609
+ writeFileSync2(devnetTomlPath, patched);
34610
+ return { status: "added" };
34611
+ }
34612
+ const prefix = original && !original.endsWith(`
34613
+ `) ? `${original}
34614
+ ` : original;
34615
+ const block = `${prefix}${original.trim() ? `
34616
+ ` : ""}[devnet]
34617
+ stacks_node_events_observers = [${entry}]
34618
+ `;
34619
+ writeFileSync2(devnetTomlPath, block);
34620
+ return { status: exists ? "added" : "created" };
34621
+ }
34622
+
34623
+ // src/commands/devnet.ts
34624
+ init_output();
34625
+ var COMPOSE_REL = join7(".secondlayer", "docker-compose.yml");
34626
+ function ensureDocker() {
34627
+ const probe = spawnSync("docker", ["info"], { stdio: "ignore" });
34628
+ if (probe.error || probe.status !== 0) {
34629
+ error(`Docker isn't available.
34630
+
34631
+ ` + `Start Docker Desktop or OrbStack (macOS), or install Docker:
34632
+ ` + ` macOS: brew install --cask docker (or https://orbstack.dev)
34633
+ ` + " Linux: curl -fsSL https://get.docker.com | sh");
34634
+ process.exit(1);
34635
+ }
34636
+ }
34637
+ function dockerCompose(composePath, args) {
34638
+ const res = spawnSync("docker", ["compose", "-f", composePath, ...args], {
34639
+ stdio: "inherit"
34640
+ });
34641
+ return res.status ?? 1;
34642
+ }
34643
+ function registerDevnetCommand(program2) {
34644
+ const devnet = program2.command("devnet").description("Run Secondlayer services against a local Clarinet devnet");
34645
+ devnet.command("connect").description("Point your clarinet project's devnet at a local Secondlayer stack and start it").option("--project <dir>", "Clarinet project directory (defaults to the nearest Clarinet.toml)").option("--image-tag <tag>", "Published image tag to run", DEFAULT_IMAGE_TAG).option("--owner <owner>", "ghcr image owner (namespace) to pull from").option("--no-up", "Patch config + write compose without starting docker").action(async (options) => {
34646
+ await connect(options);
34647
+ });
34648
+ devnet.command("down").description("Stop the local Secondlayer stack started by `sl devnet connect`").option("--project <dir>", "Clarinet project directory").option("--purge", "Also remove volumes (wipes the local index)").action(async (options) => {
34649
+ await down(options);
34650
+ });
34651
+ }
34652
+ function resolveProject(explicit) {
34653
+ if (explicit) {
34654
+ if (!existsSync3(join7(explicit, "Clarinet.toml"))) {
34655
+ error(`No Clarinet.toml in ${explicit}`);
34656
+ process.exit(1);
34657
+ }
34658
+ return explicit;
34659
+ }
34660
+ const found = findClarinetProject(process.cwd());
34661
+ if (!found) {
34662
+ error(`No Clarinet.toml found in this directory or any parent.
34663
+ ` + "Run this from inside a clarinet project, or pass --project <dir>.");
34664
+ process.exit(1);
34665
+ }
34666
+ return found;
34667
+ }
34668
+ async function connect(options) {
34669
+ const project = resolveProject(options.project);
34670
+ const devnetToml = join7(project, "settings", "Devnet.toml");
34671
+ mkdirSync2(dirname4(devnetToml), { recursive: true });
34672
+ const result = ensureEventObserver(devnetToml, DEFAULT_INDEXER_OBSERVER);
34673
+ console.log(`${green("✓")} found Clarinet.toml`);
34674
+ if (result.status === "present") {
34675
+ console.log(dim(` settings/Devnet.toml already forwards to ${DEFAULT_INDEXER_OBSERVER}`));
34676
+ } else {
34677
+ console.log(`${green("✓")} ${result.status === "created" ? "created" : "patched"} settings/Devnet.toml`);
34678
+ console.log(dim(` + stacks_node_events_observers = ["${DEFAULT_INDEXER_OBSERVER}"]`));
34679
+ }
34680
+ const composePath = join7(project, COMPOSE_REL);
34681
+ mkdirSync2(dirname4(composePath), { recursive: true });
34682
+ writeFileSync3(composePath, buildDevnetCompose({ owner: options.owner, imageTag: options.imageTag }));
34683
+ console.log(`${green("✓")} wrote ${COMPOSE_REL}`);
34684
+ if (!options.up) {
34685
+ console.log(dim(`
34686
+ Skipped docker. Start it with:
34687
+ docker compose -f ${COMPOSE_REL} up -d`));
34688
+ return;
34689
+ }
34690
+ ensureDocker();
34691
+ console.log(dim(`
34692
+ Starting Secondlayer stack (docker compose up -d)…`));
34693
+ if (dockerCompose(composePath, ["up", "-d"]) !== 0) {
34694
+ error("docker compose up failed — see output above.");
34695
+ process.exit(1);
34696
+ }
34697
+ console.log(`
34698
+ ${bold("Secondlayer stack up")}`);
34699
+ console.log(` api → ${cyan("http://localhost:3800")}`);
34700
+ console.log(` indexer → ${cyan("http://localhost:3700")}`);
34701
+ console.log(`
34702
+ ${bold("Next:")}`);
34703
+ console.log(` ${yellow("clarinet devnet start")} ${dim("# auto-deploys + streams to the indexer")}`);
34704
+ console.log(` ${yellow("SL_API_URL=http://localhost:3800 SL_SERVICE_KEY=dummy sl subgraphs deploy ./subgraph.ts")}`);
34705
+ console.log(dim(`
34706
+ Stop with: sl devnet down`));
34707
+ }
34708
+ async function down(options) {
34709
+ const project = resolveProject(options.project);
34710
+ const composePath = join7(project, COMPOSE_REL);
34711
+ if (!existsSync3(composePath)) {
34712
+ error(`No ${COMPOSE_REL} in ${project} — nothing to stop. Run \`sl devnet connect\` first.`);
34713
+ process.exit(1);
34714
+ }
34715
+ ensureDocker();
34716
+ const args = options.purge ? ["down", "-v"] : ["down"];
34717
+ if (dockerCompose(composePath, args) !== 0) {
34718
+ error("docker compose down failed — see output above.");
34719
+ process.exit(1);
34720
+ }
34721
+ console.log(`${green("✓")}${options.purge ? " stack stopped, volumes removed" : " stack stopped"}`);
34722
+ }
34432
34723
  // src/commands/subgraphs.ts
34433
34724
  import { spawn } from "node:child_process";
34434
34725
  import {
34435
- existsSync as existsSync2,
34436
- mkdirSync as mkdirSync2,
34437
- readFileSync as readFileSync2,
34726
+ existsSync as existsSync4,
34727
+ mkdirSync as mkdirSync3,
34728
+ readFileSync as readFileSync3,
34438
34729
  watch,
34439
- writeFileSync as writeFileSync2
34730
+ writeFileSync as writeFileSync4
34440
34731
  } from "node:fs";
34441
- import { dirname as dirname3, join as join6, resolve as resolve2 } from "node:path";
34732
+ import { dirname as dirname5, join as join8, resolve as resolve2 } from "node:path";
34442
34733
  import { fileURLToPath as fileURLToPath2 } from "node:url";
34443
34734
  import { confirm as confirm4 } from "@inquirer/prompts";
34444
34735
 
@@ -35249,8 +35540,8 @@ async function writeOrPrintSubgraphSpec(spec, format, output) {
35249
35540
  }
35250
35541
  const outPath = resolve2(output);
35251
35542
  const dir = resolve2(outPath, "..");
35252
- if (!existsSync2(dir))
35253
- mkdirSync2(dir, { recursive: true });
35543
+ if (!existsSync4(dir))
35544
+ mkdirSync3(dir, { recursive: true });
35254
35545
  await writeTextFile(outPath, text);
35255
35546
  success(`Created ${outPath}`);
35256
35547
  }
@@ -35312,15 +35603,15 @@ function createLocalSubgraphDetail(input3) {
35312
35603
  };
35313
35604
  }
35314
35605
  function readCliSubgraphsDependency() {
35315
- const here = dirname3(fileURLToPath2(import.meta.url));
35606
+ const here = dirname5(fileURLToPath2(import.meta.url));
35316
35607
  const candidates = [
35317
35608
  resolve2(here, "..", "..", "package.json"),
35318
35609
  resolve2(here, "..", "package.json")
35319
35610
  ];
35320
35611
  for (const candidate of candidates) {
35321
- if (!existsSync2(candidate))
35612
+ if (!existsSync4(candidate))
35322
35613
  continue;
35323
- const pkg = JSON.parse(readFileSync2(candidate, "utf8"));
35614
+ const pkg = JSON.parse(readFileSync3(candidate, "utf8"));
35324
35615
  const dep = pkg.dependencies?.["@secondlayer/subgraphs"];
35325
35616
  if (dep)
35326
35617
  return dep;
@@ -35328,10 +35619,10 @@ function readCliSubgraphsDependency() {
35328
35619
  return "^1.3.2";
35329
35620
  }
35330
35621
  function ensureScaffoldPackageJson(dir) {
35331
- const packagePath = join6(dir, "package.json");
35622
+ const packagePath = join8(dir, "package.json");
35332
35623
  const subgraphsDep = readCliSubgraphsDependency();
35333
- if (!existsSync2(packagePath)) {
35334
- writeFileSync2(packagePath, `${JSON.stringify({
35624
+ if (!existsSync4(packagePath)) {
35625
+ writeFileSync4(packagePath, `${JSON.stringify({
35335
35626
  type: "module",
35336
35627
  dependencies: {
35337
35628
  "@secondlayer/subgraphs": subgraphsDep
@@ -35340,14 +35631,14 @@ function ensureScaffoldPackageJson(dir) {
35340
35631
  `, "utf8");
35341
35632
  return;
35342
35633
  }
35343
- const pkg = JSON.parse(readFileSync2(packagePath, "utf8"));
35634
+ const pkg = JSON.parse(readFileSync3(packagePath, "utf8"));
35344
35635
  if (pkg.dependencies?.["@secondlayer/subgraphs"])
35345
35636
  return;
35346
35637
  pkg.dependencies = {
35347
35638
  ...pkg.dependencies ?? {},
35348
35639
  "@secondlayer/subgraphs": subgraphsDep
35349
35640
  };
35350
- writeFileSync2(packagePath, `${JSON.stringify(pkg, null, 2)}
35641
+ writeFileSync4(packagePath, `${JSON.stringify(pkg, null, 2)}
35351
35642
  `, "utf8");
35352
35643
  }
35353
35644
  async function runBunInstall(dir) {
@@ -35456,12 +35747,12 @@ ${SUBGRAPH_TEMPLATE_SLUGS.map((s) => ` ${s.padEnd(20)} ${SUBGRAPH_TEMPLATE_DESC
35456
35747
  }
35457
35748
  const dir = resolve2("subgraphs");
35458
35749
  const filePath = resolve2(dir, `${name}.ts`);
35459
- if (existsSync2(filePath)) {
35750
+ if (existsSync4(filePath)) {
35460
35751
  error(`File already exists: ${filePath}`);
35461
35752
  process.exit(1);
35462
35753
  }
35463
- if (!existsSync2(dir)) {
35464
- mkdirSync2(dir, { recursive: true });
35754
+ if (!existsSync4(dir)) {
35755
+ mkdirSync3(dir, { recursive: true });
35465
35756
  }
35466
35757
  const content = generateSubgraphTemplate(name, slug);
35467
35758
  await writeTextFile(filePath, content);
@@ -35474,7 +35765,7 @@ ${SUBGRAPH_TEMPLATE_SLUGS.map((s) => ` ${s.padEnd(20)} ${SUBGRAPH_TEMPLATE_DESC
35474
35765
  subgraphs.command("dev <file>").description("Watch a subgraph file and auto-redeploy on change").action(async (file) => {
35475
35766
  await requireLocalNetwork();
35476
35767
  const absPath = resolve2(file);
35477
- if (!existsSync2(absPath)) {
35768
+ if (!existsSync4(absPath)) {
35478
35769
  error(`File not found: ${absPath}`);
35479
35770
  process.exit(1);
35480
35771
  }
@@ -35791,7 +36082,7 @@ Synced — exiting watch.`));
35791
36082
  try {
35792
36083
  const format = parseSubgraphSpecFormat(options2.format);
35793
36084
  const absPath = resolve2(file);
35794
- if (!existsSync2(absPath)) {
36085
+ if (!existsSync4(absPath)) {
35795
36086
  error(`File not found: ${absPath}`);
35796
36087
  process.exit(1);
35797
36088
  }
@@ -35841,7 +36132,7 @@ Synced — exiting watch.`));
35841
36132
  process.exit(1);
35842
36133
  }
35843
36134
  const absPath = resolve2(file);
35844
- if (!existsSync2(absPath)) {
36135
+ if (!existsSync4(absPath)) {
35845
36136
  error(`File not found: ${absPath}`);
35846
36137
  process.exit(1);
35847
36138
  }
@@ -36104,8 +36395,8 @@ ${rows.length} row(s)`));
36104
36395
  });
36105
36396
  }
36106
36397
  const dir = resolve2(outPath, "..");
36107
- if (!existsSync2(dir))
36108
- mkdirSync2(dir, { recursive: true });
36398
+ if (!existsSync4(dir))
36399
+ mkdirSync3(dir, { recursive: true });
36109
36400
  await writeTextFile(outPath, content);
36110
36401
  ensureScaffoldPackageJson(dir);
36111
36402
  success(`Created ${outPath}`);
@@ -36139,8 +36430,8 @@ ${rows.length} row(s)`));
36139
36430
  info("Generating typed client...");
36140
36431
  const content = await generateSubgraphConsumer(subgraphName, subgraphDetail);
36141
36432
  const dir = resolve2(outPath, "..");
36142
- if (!existsSync2(dir))
36143
- mkdirSync2(dir, { recursive: true });
36433
+ if (!existsSync4(dir))
36434
+ mkdirSync3(dir, { recursive: true });
36144
36435
  await writeTextFile(outPath, content);
36145
36436
  success(`Created ${outPath}`);
36146
36437
  } catch (err) {
@@ -37152,10 +37443,10 @@ init_config();
37152
37443
  init_output();
37153
37444
 
37154
37445
  // src/lib/project-file.ts
37155
- import { existsSync as existsSync3 } from "node:fs";
37446
+ import { existsSync as existsSync5 } from "node:fs";
37156
37447
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
37157
37448
  import { homedir as homedir5 } from "node:os";
37158
- import { dirname as dirname9, join as join8, parse as parsePath, resolve as resolve8 } from "node:path";
37449
+ import { dirname as dirname11, join as join10, parse as parsePath2, resolve as resolve8 } from "node:path";
37159
37450
  import { z as z3 } from "zod/v4";
37160
37451
  var ProjectFileSchema = z3.object({
37161
37452
  slug: z3.string().min(1)
@@ -37165,21 +37456,21 @@ var DIRNAME = ".secondlayer";
37165
37456
  async function readActiveProject(cwd, globalDefault) {
37166
37457
  let dir = resolve8(cwd);
37167
37458
  const home = resolve8(homedir5());
37168
- const fsRoot = parsePath(dir).root;
37459
+ const fsRoot = parsePath2(dir).root;
37169
37460
  while (true) {
37170
- const candidate = join8(dir, DIRNAME, FILENAME);
37171
- if (existsSync3(candidate)) {
37461
+ const candidate = join10(dir, DIRNAME, FILENAME);
37462
+ if (existsSync5(candidate)) {
37172
37463
  try {
37173
37464
  const raw = await readFile3(candidate, "utf8");
37174
37465
  const parsed = ProjectFileSchema.parse(JSON.parse(raw));
37175
37466
  return { slug: parsed.slug, resolvedFrom: candidate };
37176
37467
  } catch {}
37177
37468
  }
37178
- if (existsSync3(join8(dir, ".git")))
37469
+ if (existsSync5(join10(dir, ".git")))
37179
37470
  break;
37180
37471
  if (dir === home || dir === fsRoot)
37181
37472
  break;
37182
- const parent = dirname9(dir);
37473
+ const parent = dirname11(dir);
37183
37474
  if (parent === dir)
37184
37475
  break;
37185
37476
  dir = parent;
@@ -37187,15 +37478,15 @@ async function readActiveProject(cwd, globalDefault) {
37187
37478
  if (globalDefault) {
37188
37479
  return {
37189
37480
  slug: globalDefault,
37190
- resolvedFrom: join8(homedir5(), ".secondlayer", "config.json")
37481
+ resolvedFrom: join10(homedir5(), ".secondlayer", "config.json")
37191
37482
  };
37192
37483
  }
37193
37484
  return null;
37194
37485
  }
37195
37486
  async function writeActiveProject(slug, cwd) {
37196
- const dir = join8(resolve8(cwd), DIRNAME);
37487
+ const dir = join10(resolve8(cwd), DIRNAME);
37197
37488
  await mkdir3(dir, { recursive: true });
37198
- const file = join8(dir, FILENAME);
37489
+ const file = join10(dir, FILENAME);
37199
37490
  await writeFile3(file, `${JSON.stringify({ slug }, null, 2)}
37200
37491
  `, "utf8");
37201
37492
  return file;
@@ -37400,9 +37691,10 @@ registerDbCommand(program);
37400
37691
  registerDoctorCommand(program);
37401
37692
  registerConfigCommand(program);
37402
37693
  registerLocalCommand(program);
37694
+ registerDevnetCommand(program);
37403
37695
  registerAccountCommand(program);
37404
37696
  registerBillingCommand(program);
37405
37697
  program.parse();
37406
37698
 
37407
- //# debugId=BC7D4D440C943F4464756E2164756E21
37699
+ //# debugId=FDDBCAB122A19D1564756E2164756E21
37408
37700
  //# sourceMappingURL=cli.js.map