@secondlayer/cli 0.1.1 → 0.2.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
@@ -1,40 +1,33 @@
1
1
  #!/usr/bin/env node
2
+ // @bun
2
3
  var __defProp = Object.defineProperty;
3
- var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __esm = (fn, res) => function __init() {
5
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
- };
7
4
  var __export = (target, all) => {
8
5
  for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
6
+ __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true,
9
+ configurable: true,
10
+ set: (newValue) => all[name] = () => newValue
11
+ });
10
12
  };
11
-
12
- // node_modules/tsup/assets/esm_shims.js
13
- import path from "path";
14
- import { fileURLToPath } from "url";
15
- var init_esm_shims = __esm({
16
- "node_modules/tsup/assets/esm_shims.js"() {
17
- "use strict";
18
- }
19
- });
13
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
20
14
 
21
15
  // src/commands/init.ts
22
- var init_exports = {};
23
- __export(init_exports, {
16
+ var exports_init = {};
17
+ __export(exports_init, {
24
18
  init: () => init
25
19
  });
26
- import { promises as fs3 } from "fs";
20
+ import { promises as fs4 } from "fs";
27
21
  import path4 from "path";
28
22
  import ora2 from "ora";
29
23
  async function init() {
30
24
  const spinner = ora2("Initializing").start();
31
25
  const configPath = path4.join(process.cwd(), "stacks.config.ts");
32
26
  try {
33
- await fs3.access(configPath);
27
+ await fs4.access(configPath);
34
28
  spinner.warn("stacks.config.ts already exists");
35
29
  return;
36
- } catch {
37
- }
30
+ } catch {}
38
31
  const hasClarinetProject = await fileExists("./Clarinet.toml");
39
32
  let config;
40
33
  if (hasClarinetProject) {
@@ -55,86 +48,70 @@ export default defineConfig({
55
48
  plugins: [],
56
49
  });`;
57
50
  }
58
- await fs3.writeFile(configPath, config);
51
+ await fs4.writeFile(configPath, config);
59
52
  spinner.succeed("Created `stacks.config.ts`");
60
- console.log(
61
- "\nRun `secondlayer generate` to generate type-safe interfaces, functions, and hooks!"
62
- );
53
+ console.log("\nRun `secondlayer generate` to generate type-safe interfaces, functions, and hooks!");
63
54
  }
64
55
  async function fileExists(filePath) {
65
56
  try {
66
- await fs3.access(filePath);
57
+ await fs4.access(filePath);
67
58
  return true;
68
59
  } catch {
69
60
  return false;
70
61
  }
71
62
  }
72
- var init_init = __esm({
73
- "src/commands/init.ts"() {
74
- "use strict";
75
- init_esm_shims();
76
- }
77
- });
63
+ var init_init = () => {};
78
64
 
79
65
  // src/cli.ts
80
- init_esm_shims();
81
66
  import { program } from "commander";
82
67
 
83
68
  // src/commands/generate.ts
84
- init_esm_shims();
69
+ import path3 from "path";
85
70
  import chalk from "chalk";
86
71
  import ora from "ora";
72
+ var {Glob } = globalThis.Bun;
87
73
 
88
74
  // src/utils/config.ts
89
- init_esm_shims();
90
75
  import { promises as fs2 } from "fs";
91
- import path3 from "path";
76
+ import path2 from "path";
92
77
  import { pathToFileURL } from "url";
93
78
  import { createRequire } from "module";
94
79
  import { transformSync } from "esbuild";
95
80
 
96
81
  // src/core/plugin-manager.ts
97
- init_esm_shims();
98
82
  import { format } from "prettier";
99
83
  import { promises as fs } from "fs";
100
- import path2 from "path";
84
+ import path from "path";
101
85
  import { validateStacksAddress } from "@stacks/transactions";
102
- var PluginManager = class {
86
+
87
+ class PluginManager {
88
+ plugins = [];
89
+ logger;
90
+ utils;
91
+ executionContext;
103
92
  constructor() {
104
- this.plugins = [];
105
93
  this.logger = this.createLogger();
106
94
  this.utils = this.createUtils();
107
95
  this.executionContext = {
108
96
  phase: "config",
109
97
  startTime: Date.now(),
110
- results: /* @__PURE__ */ new Map()
98
+ results: new Map
111
99
  };
112
100
  }
113
- /**
114
- * Register a plugin
115
- */
116
101
  register(plugin) {
117
102
  if (!plugin.name || !plugin.version) {
118
103
  throw new Error("Plugin must have a name and version");
119
104
  }
120
105
  const existing = this.plugins.find((p) => p.name === plugin.name);
121
106
  if (existing) {
122
- throw new Error(
123
- `Plugin "${plugin.name}" is already registered (version ${existing.version})`
124
- );
107
+ throw new Error(`Plugin "${plugin.name}" is already registered (version ${existing.version})`);
125
108
  }
126
109
  this.plugins.push(plugin);
127
110
  this.logger.debug(`Registered plugin: ${plugin.name}@${plugin.version}`);
128
111
  }
129
- /**
130
- * Get all registered plugins
131
- */
132
112
  getPlugins() {
133
113
  return [...this.plugins];
134
114
  }
135
- /**
136
- * Transform user config through all plugins
137
- */
138
115
  async transformConfig(config) {
139
116
  this.executionContext.phase = "config";
140
117
  let transformedConfig = { ...config };
@@ -153,9 +130,7 @@ var PluginManager = class {
153
130
  success: false,
154
131
  error: err
155
132
  });
156
- throw new Error(
157
- `Plugin "${plugin.name}" failed during config transformation: ${err.message}`
158
- );
133
+ throw new Error(`Plugin "${plugin.name}" failed during config transformation: ${err.message}`);
159
134
  }
160
135
  }
161
136
  }
@@ -165,9 +140,6 @@ var PluginManager = class {
165
140
  };
166
141
  return resolvedConfig;
167
142
  }
168
- /**
169
- * Transform contracts through all plugins
170
- */
171
143
  async transformContracts(contracts, _config) {
172
144
  const processedContracts = [];
173
145
  for (let contract of contracts) {
@@ -185,6 +157,20 @@ var PluginManager = class {
185
157
  processedContracts.push(processed);
186
158
  continue;
187
159
  }
160
+ if (contract._directFile && contract.abi) {
161
+ const address = typeof contract.address === "string" ? contract.address : "";
162
+ const [contractAddress, contractName] = address.split(".");
163
+ const processed = {
164
+ name: contract.name || contractName,
165
+ address: contractAddress,
166
+ contractName,
167
+ abi: contract.abi,
168
+ source: "local",
169
+ metadata: { source: "direct" }
170
+ };
171
+ processedContracts.push(processed);
172
+ continue;
173
+ }
188
174
  for (const plugin of this.plugins) {
189
175
  if (plugin.transformContract) {
190
176
  this.executionContext.currentPlugin = plugin;
@@ -199,9 +185,7 @@ var PluginManager = class {
199
185
  success: false,
200
186
  error: err
201
187
  });
202
- this.logger.warn(
203
- `Plugin "${plugin.name}" failed to transform contract: ${err.message}`
204
- );
188
+ this.logger.warn(`Plugin "${plugin.name}" failed to transform contract: ${err.message}`);
205
189
  }
206
190
  }
207
191
  }
@@ -212,7 +196,6 @@ var PluginManager = class {
212
196
  contractName: contract.name || "unknown",
213
197
  abi: contract.abi,
214
198
  source: "api",
215
- // Use "api" as default for plugin-processed contracts
216
199
  metadata: contract.metadata
217
200
  };
218
201
  processedContracts.push(processed);
@@ -220,9 +203,6 @@ var PluginManager = class {
220
203
  }
221
204
  return processedContracts;
222
205
  }
223
- /**
224
- * Execute lifecycle hooks
225
- */
226
206
  async executeHook(hookName, context) {
227
207
  for (const plugin of this.plugins) {
228
208
  const hook = plugin[hookName];
@@ -239,19 +219,14 @@ var PluginManager = class {
239
219
  success: false,
240
220
  error: err
241
221
  });
242
- this.logger.error(
243
- `Plugin "${plugin.name}" failed during ${hookName}: ${err.message}`
244
- );
222
+ this.logger.error(`Plugin "${plugin.name}" failed during ${hookName}: ${err.message}`);
245
223
  }
246
224
  }
247
225
  }
248
226
  }
249
- /**
250
- * Execute generation phase with full context
251
- */
252
227
  async executeGeneration(contracts, config) {
253
228
  this.executionContext.phase = "generate";
254
- const outputs = /* @__PURE__ */ new Map();
229
+ const outputs = new Map;
255
230
  const context = {
256
231
  config,
257
232
  logger: this.logger,
@@ -270,22 +245,16 @@ var PluginManager = class {
270
245
  await this.executeHook("afterGenerate", context);
271
246
  return outputs;
272
247
  }
273
- /**
274
- * Transform outputs through plugins
275
- */
276
248
  async transformOutputs(outputs) {
277
249
  this.executionContext.phase = "output";
278
- const transformedOutputs = /* @__PURE__ */ new Map();
250
+ const transformedOutputs = new Map;
279
251
  for (const [key, output] of outputs) {
280
252
  let transformedContent = output.content;
281
253
  for (const plugin of this.plugins) {
282
254
  if (plugin.transformOutput) {
283
255
  this.executionContext.currentPlugin = plugin;
284
256
  try {
285
- transformedContent = await plugin.transformOutput(
286
- transformedContent,
287
- output.type || "other"
288
- );
257
+ transformedContent = await plugin.transformOutput(transformedContent, output.type || "other");
289
258
  this.recordHookResult(plugin.name, "transformOutput", {
290
259
  success: true
291
260
  });
@@ -295,9 +264,7 @@ var PluginManager = class {
295
264
  success: false,
296
265
  error: err
297
266
  });
298
- this.logger.warn(
299
- `Plugin "${plugin.name}" failed to transform output: ${err.message}`
300
- );
267
+ this.logger.warn(`Plugin "${plugin.name}" failed to transform output: ${err.message}`);
301
268
  }
302
269
  }
303
270
  }
@@ -308,14 +275,11 @@ var PluginManager = class {
308
275
  }
309
276
  return transformedOutputs;
310
277
  }
311
- /**
312
- * Write outputs to disk
313
- */
314
278
  async writeOutputs(outputs) {
315
279
  for (const [, output] of outputs) {
316
280
  try {
317
- const resolvedPath = path2.resolve(process.cwd(), output.path);
318
- await this.utils.ensureDir(path2.dirname(resolvedPath));
281
+ const resolvedPath = path.resolve(process.cwd(), output.path);
282
+ await this.utils.ensureDir(path.dirname(resolvedPath));
319
283
  await this.utils.writeFile(resolvedPath, output.content);
320
284
  } catch (error) {
321
285
  const err = error;
@@ -324,15 +288,9 @@ var PluginManager = class {
324
288
  }
325
289
  }
326
290
  }
327
- /**
328
- * Get execution results for debugging
329
- */
330
291
  getExecutionResults() {
331
292
  return new Map(this.executionContext.results);
332
293
  }
333
- /**
334
- * Augment existing output with additional content
335
- */
336
294
  augmentOutput(outputs, outputKey, contractName, content) {
337
295
  const existing = outputs.get(outputKey);
338
296
  if (!existing) {
@@ -348,18 +306,12 @@ ${JSON.stringify(content, null, 2)}`;
348
306
  content: augmentedContent
349
307
  });
350
308
  }
351
- /**
352
- * Record hook execution result
353
- */
354
309
  recordHookResult(pluginName, hookName, result) {
355
310
  const key = `${pluginName}:${hookName}`;
356
311
  const existing = this.executionContext.results.get(key) || [];
357
312
  existing.push({ ...result, plugin: pluginName });
358
313
  this.executionContext.results.set(key, existing);
359
314
  }
360
- /**
361
- * Create logger instance
362
- */
363
315
  createLogger() {
364
316
  return {
365
317
  info: (message) => console.log(`\u2139\uFE0F ${message}`),
@@ -367,15 +319,12 @@ ${JSON.stringify(content, null, 2)}`;
367
319
  error: (message) => console.error(`\u274C ${message}`),
368
320
  debug: (message) => {
369
321
  if (process.env.DEBUG) {
370
- console.log(`\u{1F41B} ${message}`);
322
+ console.log(`\uD83D\uDC1B ${message}`);
371
323
  }
372
324
  },
373
325
  success: (message) => console.log(`\u2705 ${message}`)
374
326
  };
375
327
  }
376
- /**
377
- * Create utils instance
378
- */
379
328
  createUtils() {
380
329
  return {
381
330
  toCamelCase: (str) => {
@@ -401,7 +350,7 @@ ${JSON.stringify(content, null, 2)}`;
401
350
  });
402
351
  },
403
352
  resolvePath: (relativePath) => {
404
- return path2.resolve(process.cwd(), relativePath);
353
+ return path.resolve(process.cwd(), relativePath);
405
354
  },
406
355
  fileExists: async (filePath) => {
407
356
  try {
@@ -422,32 +371,29 @@ ${JSON.stringify(content, null, 2)}`;
422
371
  }
423
372
  };
424
373
  }
425
- };
374
+ }
426
375
 
427
376
  // src/utils/config.ts
428
377
  var CONFIG_FILE_NAMES = [
429
378
  "stacks.config.ts",
430
- "stacks.config.js",
379
+ "stacks.config",
431
380
  "stacks.config.mjs"
432
381
  ];
433
382
  async function findConfigFile(cwd) {
434
383
  for (const fileName of CONFIG_FILE_NAMES) {
435
- const filePath = path3.join(cwd, fileName);
384
+ const filePath = path2.join(cwd, fileName);
436
385
  try {
437
386
  await fs2.access(filePath);
438
387
  return filePath;
439
- } catch {
440
- }
388
+ } catch {}
441
389
  }
442
390
  return null;
443
391
  }
444
392
  async function loadConfig(configPath) {
445
393
  const cwd = process.cwd();
446
- const resolvedPath = configPath ? path3.resolve(cwd, configPath) : await findConfigFile(cwd);
394
+ const resolvedPath = configPath ? path2.resolve(cwd, configPath) : await findConfigFile(cwd);
447
395
  if (!resolvedPath) {
448
- throw new Error(
449
- "No config file found. Create a stacks.config.ts file or specify a path with --config"
450
- );
396
+ throw new Error("No config file found. Create a stacks.config.ts file or specify a path with --config");
451
397
  }
452
398
  let config;
453
399
  if (resolvedPath.endsWith(".ts")) {
@@ -458,14 +404,11 @@ async function loadConfig(configPath) {
458
404
  const packagePath = require2.resolve("@secondlayer/cli");
459
405
  replacementPath = pathToFileURL(packagePath).href;
460
406
  } catch {
461
- const currentModuleDir = path3.dirname(new URL(import.meta.url).pathname);
462
- const indexPath = path3.resolve(currentModuleDir, "../index.js");
407
+ const currentModuleDir = path2.dirname(new URL(import.meta.url).pathname);
408
+ const indexPath = path2.resolve(currentModuleDir, "../index");
463
409
  replacementPath = pathToFileURL(indexPath).href;
464
410
  }
465
- const transformedCode = code.replace(
466
- /from\s+["']@stacks\/cli["']/g,
467
- `from '${replacementPath}'`
468
- );
411
+ const transformedCode = code.replace(/from\s+["']@stacks\/cli["']/g, `from '${replacementPath}'`);
469
412
  const result = transformSync(transformedCode, {
470
413
  format: "esm",
471
414
  target: "node18",
@@ -478,8 +421,7 @@ async function loadConfig(configPath) {
478
421
  const module = await import(fileUrl);
479
422
  config = module.default;
480
423
  } finally {
481
- await fs2.unlink(tempPath).catch(() => {
482
- });
424
+ await fs2.unlink(tempPath).catch(() => {});
483
425
  }
484
426
  } else {
485
427
  const fileUrl = pathToFileURL(resolvedPath).href;
@@ -493,7 +435,7 @@ async function loadConfig(configPath) {
493
435
  config = config({});
494
436
  }
495
437
  validateConfig(config);
496
- const pluginManager = new PluginManager();
438
+ const pluginManager = new PluginManager;
497
439
  if (config.plugins && Array.isArray(config.plugins)) {
498
440
  for (const plugin of config.plugins) {
499
441
  pluginManager.register(plugin);
@@ -526,14 +468,231 @@ function validateConfig(config) {
526
468
  }
527
469
 
528
470
  // src/utils/api.ts
529
- init_esm_shims();
530
471
  import got from "got";
472
+ var API_URLS = {
473
+ mainnet: "https://api.hiro.so",
474
+ testnet: "https://api.testnet.hiro.so",
475
+ devnet: "http://localhost:3999"
476
+ };
477
+
478
+ class StacksApiClient {
479
+ baseUrl;
480
+ headers;
481
+ constructor(network = "mainnet", apiKey, apiUrl) {
482
+ this.baseUrl = apiUrl || API_URLS[network];
483
+ this.headers = apiKey ? { "x-api-key": apiKey } : {};
484
+ }
485
+ async getContractInfo(contractId) {
486
+ const [address, contractName] = contractId.split(".");
487
+ if (!address || !contractName) {
488
+ throw new Error(`Invalid contract ID format: ${contractId}. Expected format: ADDRESS.CONTRACT_NAME`);
489
+ }
490
+ const url = `${this.baseUrl}/v2/contracts/interface/${address}/${contractName}`;
491
+ try {
492
+ const response = await got(url, {
493
+ headers: this.headers,
494
+ responseType: "json"
495
+ });
496
+ return response.body;
497
+ } catch (error) {
498
+ if (error.response?.statusCode === 404) {
499
+ throw new Error(`Contract not found: ${contractId}`);
500
+ }
501
+ if (error.response?.statusCode === 429) {
502
+ throw new Error("Rate limited. Please provide an API key in your config.");
503
+ }
504
+ throw new Error(`Failed to fetch contract: ${error.message}`);
505
+ }
506
+ }
507
+ async getContractSource(contractId) {
508
+ const [address, contractName] = contractId.split(".");
509
+ if (!address || !contractName) {
510
+ throw new Error(`Invalid contract ID format: ${contractId}. Expected format: ADDRESS.CONTRACT_NAME`);
511
+ }
512
+ const url = `${this.baseUrl}/v2/contracts/source/${address}/${contractName}`;
513
+ try {
514
+ const response = await got(url, {
515
+ headers: this.headers,
516
+ responseType: "json"
517
+ });
518
+ const data = response.body;
519
+ return data.source;
520
+ } catch (error) {
521
+ return "";
522
+ }
523
+ }
524
+ }
531
525
 
532
526
  // src/parsers/clarity.ts
533
- init_esm_shims();
527
+ import { promises as fs3 } from "fs";
528
+ async function parseClarityFile(filePath) {
529
+ const content = await fs3.readFile(filePath, "utf-8");
530
+ return parseClarityContent(content);
531
+ }
532
+ function parseClarityContent(content) {
533
+ const functions = [];
534
+ const functionRegex = /\(define-(public|read-only|private)\s+\(([^)]+)\)([\s\S]*?)\)\s*$/gm;
535
+ let match;
536
+ while ((match = functionRegex.exec(content)) !== null) {
537
+ const [, access, signature, body] = match;
538
+ const func = parseFunctionSignature(signature, access, body);
539
+ if (func) {
540
+ functions.push(func);
541
+ }
542
+ }
543
+ return { functions };
544
+ }
545
+ function parseFunctionSignature(signature, access, body) {
546
+ const parts = signature.trim().split(/\s+/);
547
+ const name = parts[0];
548
+ const args = [];
549
+ for (let i = 1;i < parts.length; i += 2) {
550
+ if (parts[i] && parts[i + 1]) {
551
+ const argName = parts[i].replace(/[()]/g, "");
552
+ const argType = parseType(parts[i + 1]);
553
+ if (argType) {
554
+ args.push({ name: argName, type: argType });
555
+ }
556
+ }
557
+ }
558
+ const outputs = inferReturnType(body);
559
+ return {
560
+ name,
561
+ access,
562
+ args,
563
+ outputs
564
+ };
565
+ }
566
+ function parseType(typeStr) {
567
+ typeStr = typeStr.replace(/[()]/g, "").trim();
568
+ switch (typeStr) {
569
+ case "uint":
570
+ case "uint128":
571
+ return "uint128";
572
+ case "int":
573
+ case "int128":
574
+ return "int128";
575
+ case "bool":
576
+ return "bool";
577
+ case "principal":
578
+ return "principal";
579
+ case "trait_reference":
580
+ return "principal";
581
+ default:
582
+ if (typeStr.startsWith("string-ascii")) {
583
+ return { "string-ascii": { length: 256 } };
584
+ }
585
+ if (typeStr.startsWith("string-utf8")) {
586
+ return { "string-utf8": { length: 256 } };
587
+ }
588
+ if (typeStr.startsWith("buff")) {
589
+ return { buff: { length: 32 } };
590
+ }
591
+ return "uint128";
592
+ }
593
+ }
594
+ function inferReturnType(body) {
595
+ if (body.includes("(ok")) {
596
+ if (body.includes("(err")) {
597
+ return {
598
+ response: {
599
+ ok: "bool",
600
+ error: "uint128"
601
+ }
602
+ };
603
+ }
604
+ }
605
+ if (body.includes("true") || body.includes("false")) {
606
+ return "bool";
607
+ }
608
+ return "bool";
609
+ }
610
+ function parseApiResponse(apiResponse) {
611
+ try {
612
+ const functions = [];
613
+ if (apiResponse.functions) {
614
+ for (const func of apiResponse.functions) {
615
+ const access = func.access === "read_only" ? "read-only" : func.access;
616
+ functions.push({
617
+ name: func.name,
618
+ access,
619
+ args: func.args.map((arg) => ({
620
+ name: arg.name,
621
+ type: convertApiType(arg.type)
622
+ })),
623
+ outputs: convertApiType(func.outputs.type)
624
+ });
625
+ }
626
+ }
627
+ return { functions };
628
+ } catch (error) {
629
+ throw new Error(`Failed to parse API response: ${error}`);
630
+ }
631
+ }
632
+ function convertApiType(apiType) {
633
+ if (typeof apiType === "string") {
634
+ if (apiType === "trait_reference") {
635
+ return "trait_reference";
636
+ }
637
+ return parseType(apiType) || "uint128";
638
+ }
639
+ if (apiType.response) {
640
+ return {
641
+ response: {
642
+ ok: convertApiType(apiType.response.ok),
643
+ error: convertApiType(apiType.response.error)
644
+ }
645
+ };
646
+ }
647
+ if (apiType.optional) {
648
+ return {
649
+ optional: convertApiType(apiType.optional)
650
+ };
651
+ }
652
+ if (apiType.list) {
653
+ return {
654
+ list: {
655
+ type: convertApiType(apiType.list.type),
656
+ length: apiType.list.length || 100
657
+ }
658
+ };
659
+ }
660
+ if (apiType.tuple) {
661
+ return {
662
+ tuple: apiType.tuple.map((field) => ({
663
+ name: field.name,
664
+ type: convertApiType(field.type)
665
+ }))
666
+ };
667
+ }
668
+ if (apiType.buffer) {
669
+ return {
670
+ buff: {
671
+ length: apiType.buffer.length || 32
672
+ }
673
+ };
674
+ }
675
+ if (apiType["string-ascii"]) {
676
+ return {
677
+ "string-ascii": {
678
+ length: apiType["string-ascii"].length || 256
679
+ }
680
+ };
681
+ }
682
+ if (apiType["string-utf8"]) {
683
+ return {
684
+ "string-utf8": {
685
+ length: apiType["string-utf8"].length || 256
686
+ }
687
+ };
688
+ }
689
+ if (apiType === "none") {
690
+ return "uint128";
691
+ }
692
+ return "uint128";
693
+ }
534
694
 
535
695
  // src/generators/contract.ts
536
- init_esm_shims();
537
696
  import { format as format2 } from "prettier";
538
697
  async function generateContractInterface(contracts) {
539
698
  const imports = `import { Cl, validateStacksAddress } from '@stacks/transactions'`;
@@ -541,7 +700,9 @@ async function generateContractInterface(contracts) {
541
700
  * Generated by @secondlayer/cli
542
701
  * DO NOT EDIT MANUALLY
543
702
  */`;
544
- const contractsCode = contracts.map((contract) => generateContract(contract)).join("\n\n");
703
+ const contractsCode = contracts.map((contract) => generateContract(contract)).join(`
704
+
705
+ `);
545
706
  const code = `${imports}
546
707
 
547
708
  ${header}
@@ -559,7 +720,9 @@ ${contractsCode}`;
559
720
  function generateContract(contract) {
560
721
  const { name, address, contractName, abi } = contract;
561
722
  const abiCode = generateAbiConstant(name, abi);
562
- const methods = abi.functions.filter((func) => func.access !== "private").map((func) => generateMethod(func, address, contractName)).join(",\n\n ");
723
+ const methods = abi.functions.filter((func) => func.access !== "private").map((func) => generateMethod(func, address, contractName)).join(`,
724
+
725
+ `);
563
726
  const contractCode = `export const ${name} = {
564
727
  address: '${address}',
565
728
  contractAddress: '${address}',
@@ -663,9 +826,7 @@ function getTypeForArg(arg) {
663
826
  return `${innerType}[]`;
664
827
  }
665
828
  if (type.tuple) {
666
- const fields = type.tuple.map(
667
- (field) => `${toCamelCase(field.name)}: ${getTypeForArg({ type: field.type })}`
668
- ).join("; ");
829
+ const fields = type.tuple.map((field) => `${toCamelCase(field.name)}: ${getTypeForArg({ type: field.type })}`).join("; ");
669
830
  return `{ ${fields} }`;
670
831
  }
671
832
  if (type.response) {
@@ -762,10 +923,7 @@ function generateClarityConversion(argName, argType) {
762
923
  if (type.tuple) {
763
924
  const fields = type.tuple.map((field) => {
764
925
  const camelFieldName = toCamelCase(field.name);
765
- const fieldConversion = generateClarityConversion(
766
- `${argName}.${camelFieldName}`,
767
- { type: field.type }
768
- );
926
+ const fieldConversion = generateClarityConversion(`${argName}.${camelFieldName}`, { type: field.type });
769
927
  return `"${field.name}": ${fieldConversion}`;
770
928
  }).join(", ");
771
929
  return `Cl.tuple({ ${fields} })`;
@@ -783,43 +941,144 @@ function generateClarityConversion(argName, argType) {
783
941
  }
784
942
 
785
943
  // src/commands/generate.ts
786
- async function generate(options) {
944
+ function isContractAddress(input) {
945
+ const contractIdPattern = /^(SP|ST|SM|SN)[A-Z0-9]{38,}\.[a-zA-Z][a-zA-Z0-9-]*$/;
946
+ return contractIdPattern.test(input);
947
+ }
948
+ function inferNetwork(address) {
949
+ if (address.startsWith("SP") || address.startsWith("SM")) {
950
+ return "mainnet";
951
+ }
952
+ return "testnet";
953
+ }
954
+ async function parseInputs(inputs) {
955
+ const files = [];
956
+ const contractIds = [];
957
+ for (const input of inputs) {
958
+ if (isContractAddress(input)) {
959
+ contractIds.push(input);
960
+ continue;
961
+ }
962
+ if (input.includes("*") || input.includes("?")) {
963
+ const glob = new Glob(input);
964
+ for await (const file of glob.scan({ cwd: process.cwd(), absolute: true })) {
965
+ if (file.endsWith(".clar")) {
966
+ files.push(file);
967
+ }
968
+ }
969
+ continue;
970
+ }
971
+ if (input.endsWith(".clar")) {
972
+ const absolutePath = path3.resolve(process.cwd(), input);
973
+ files.push(absolutePath);
974
+ }
975
+ }
976
+ return {
977
+ files: [...new Set(files)],
978
+ contractIds: [...new Set(contractIds)]
979
+ };
980
+ }
981
+ function deriveContractName(filePath) {
982
+ const basename = path3.basename(filePath, ".clar");
983
+ return basename.replace(/[-_](.)/g, (_, char) => char.toUpperCase()).replace(/^(.)/, (_, char) => char.toLowerCase()).replace(/^\d/, "_$&");
984
+ }
985
+ function toCamelCase2(str) {
986
+ return str.replace(/[-_](.)/g, (_, char) => char.toUpperCase()).replace(/^(.)/, (_, char) => char.toLowerCase()).replace(/^\d/, "_$&");
987
+ }
988
+ async function buildConfigFromInputs(parsedInputs, outPath, apiKey, spinner) {
989
+ const contracts = [];
990
+ for (const file of parsedInputs.files) {
991
+ const abi = await parseClarityFile(file);
992
+ const name = deriveContractName(file);
993
+ contracts.push({
994
+ name,
995
+ address: `ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.${name}`,
996
+ abi,
997
+ _directFile: true
998
+ });
999
+ }
1000
+ for (const contractId of parsedInputs.contractIds) {
1001
+ const [address, contractName] = contractId.split(".");
1002
+ const network = inferNetwork(address);
1003
+ spinner.text = `Fetching ${contractName} from ${network}...`;
1004
+ try {
1005
+ const apiClient = new StacksApiClient(network, apiKey);
1006
+ const contractInfo = await apiClient.getContractInfo(contractId);
1007
+ const abi = parseApiResponse(contractInfo);
1008
+ const name = toCamelCase2(contractName);
1009
+ contracts.push({
1010
+ name,
1011
+ address: contractId,
1012
+ abi,
1013
+ _directFile: true
1014
+ });
1015
+ } catch (error) {
1016
+ throw new Error(`Failed to fetch contract ${contractId}: ${error.message}`);
1017
+ }
1018
+ }
1019
+ return {
1020
+ out: outPath,
1021
+ contracts,
1022
+ plugins: []
1023
+ };
1024
+ }
1025
+ async function generate(files, options) {
787
1026
  const spinner = ora("Processing contracts").start();
788
1027
  try {
789
- const config = await loadConfig(options.config);
790
- const pluginManager = new PluginManager();
1028
+ let config;
1029
+ if (files && files.length > 0) {
1030
+ if (!options.out) {
1031
+ spinner.fail("Output path required");
1032
+ console.error(chalk.red(`
1033
+ When using direct inputs, you must specify an output path with -o/--out`));
1034
+ console.log(chalk.gray(`
1035
+ Examples:`));
1036
+ console.log(chalk.gray(" secondlayer generate ./contracts/*.clar -o ./src/generated.ts"));
1037
+ console.log(chalk.gray(" secondlayer generate SP2C2YFP12AJZB1M6DY7SF9A3PRHWKGYGVWQKW3.my-token -o ./src/generated.ts"));
1038
+ process.exit(1);
1039
+ }
1040
+ const parsedInputs = await parseInputs(files);
1041
+ const totalInputs = parsedInputs.files.length + parsedInputs.contractIds.length;
1042
+ if (totalInputs === 0) {
1043
+ spinner.fail("No valid inputs found");
1044
+ console.error(chalk.red(`
1045
+ No .clar files or contract addresses matched the provided inputs`));
1046
+ process.exit(1);
1047
+ }
1048
+ spinner.text = `Processing ${totalInputs} contract(s)...`;
1049
+ const apiKey = options.apiKey || process.env.HIRO_API_KEY;
1050
+ config = await buildConfigFromInputs(parsedInputs, options.out, apiKey, spinner);
1051
+ } else {
1052
+ config = await loadConfig(options.config);
1053
+ }
1054
+ const pluginManager = new PluginManager;
791
1055
  if (config.plugins) {
792
1056
  for (const plugin of config.plugins) {
793
1057
  pluginManager.register(plugin);
794
1058
  }
795
1059
  }
796
- await pluginManager.executeHook("configResolved", config);
797
- const contractConfigs = (config.contracts || []).map(
798
- (contract) => ({
799
- name: contract.name,
800
- address: contract.address,
801
- source: contract.source,
802
- abi: contract.abi,
803
- // Include ABI if it exists (from plugins)
804
- _clarinetSource: contract._clarinetSource
805
- // Include plugin flags
806
- })
807
- );
808
- const processedContracts = await pluginManager.transformContracts(
809
- contractConfigs,
810
- config
811
- );
1060
+ const resolvedConfig = {
1061
+ ...config,
1062
+ plugins: pluginManager.getPlugins()
1063
+ };
1064
+ await pluginManager.executeHook("configResolved", resolvedConfig);
1065
+ const contractConfigs = (config.contracts || []).map((contract) => ({
1066
+ name: contract.name,
1067
+ address: contract.address,
1068
+ source: contract.source,
1069
+ abi: contract.abi,
1070
+ _clarinetSource: contract._clarinetSource
1071
+ }));
1072
+ const processedContracts = await pluginManager.transformContracts(contractConfigs, resolvedConfig);
812
1073
  if (processedContracts.length === 0) {
813
1074
  spinner.warn("No contracts found to generate");
814
- console.log("\nTo get started:");
1075
+ console.log(`
1076
+ To get started:`);
815
1077
  console.log(" \u2022 Add contracts to your config file, or");
816
1078
  console.log(" \u2022 Use plugins like clarinet() for local contracts");
817
1079
  return;
818
1080
  }
819
- const outputs = await pluginManager.executeGeneration(
820
- processedContracts,
821
- config
822
- );
1081
+ const outputs = await pluginManager.executeGeneration(processedContracts, resolvedConfig);
823
1082
  if (!outputs.has("contracts") && processedContracts.length > 0) {
824
1083
  const contractsCode = await generateContractInterface(processedContracts);
825
1084
  outputs.set("contracts", {
@@ -834,22 +1093,14 @@ async function generate(options) {
834
1093
  const contractWord = contractCount === 1 ? "contract" : "contracts";
835
1094
  spinner.succeed(`Generation complete for ${contractCount} ${contractWord}`);
836
1095
  console.log(`
837
- \u{1F4C4} ${config.out}`);
1096
+ \uD83D\uDCC4 ${config.out}`);
838
1097
  console.log(`
839
- \u{1F4A1} Import your contracts:`);
1098
+ \uD83D\uDCA1 Import your contracts:`);
840
1099
  if (processedContracts.length > 0) {
841
1100
  const exampleContract = processedContracts[0];
842
- console.log(
843
- chalk.gray(
844
- ` import { ${exampleContract.name} } from '${config.out.replace(/\.ts$/, "")}'`
845
- )
846
- );
1101
+ console.log(chalk.gray(` import { ${exampleContract.name} } from '${config.out.replace(/\.ts$/, "")}'`));
847
1102
  if (processedContracts.length > 1) {
848
- console.log(
849
- chalk.gray(
850
- ` // Also available: ${processedContracts.slice(1).map((c) => c.name).join(", ")}`
851
- )
852
- );
1103
+ console.log(chalk.gray(` // Also available: ${processedContracts.slice(1).map((c) => c.name).join(", ")}`));
853
1104
  }
854
1105
  }
855
1106
  } catch (error) {
@@ -865,10 +1116,12 @@ ${error.message}`));
865
1116
 
866
1117
  // src/cli.ts
867
1118
  program.name("stacks").description("CLI tool for generating type-safe Stacks contract interfaces").version("0.1.0");
868
- program.command("generate").alias("gen").description("Generate TypeScript interfaces from Clarity contracts").option("-c, --config <path>", "Path to config file").option("-w, --watch", "Watch for changes").action(generate);
1119
+ program.command("generate [files...]").alias("gen").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(generate);
869
1120
  program.command("init").description("Initialize a new stacks.config.ts file").action(async () => {
870
- const { init: init2 } = await Promise.resolve().then(() => (init_init(), init_exports));
1121
+ const { init: init2 } = await Promise.resolve().then(() => (init_init(), exports_init));
871
1122
  await init2();
872
1123
  });
873
1124
  program.parse();
874
- //# sourceMappingURL=cli.js.map
1125
+
1126
+ //# debugId=5EBBA30969CCC98F64756E2164756E21
1127
+ //# sourceMappingURL=cli.js.map