@secondlayer/cli 0.1.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 ADDED
@@ -0,0 +1,874 @@
1
+ #!/usr/bin/env node
2
+ 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
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
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
+ });
20
+
21
+ // src/commands/init.ts
22
+ var init_exports = {};
23
+ __export(init_exports, {
24
+ init: () => init
25
+ });
26
+ import { promises as fs3 } from "fs";
27
+ import path4 from "path";
28
+ import ora2 from "ora";
29
+ async function init() {
30
+ const spinner = ora2("Initializing").start();
31
+ const configPath = path4.join(process.cwd(), "stacks.config.ts");
32
+ try {
33
+ await fs3.access(configPath);
34
+ spinner.warn("stacks.config.ts already exists");
35
+ return;
36
+ } catch {
37
+ }
38
+ const hasClarinetProject = await fileExists("./Clarinet.toml");
39
+ let config;
40
+ if (hasClarinetProject) {
41
+ config = `import { defineConfig } from '@stacks/codegen';
42
+ import { clarinet } from '@stacks/codegen/plugins';
43
+
44
+ export default defineConfig({
45
+ out: './src/generated/contracts.ts',
46
+ plugins: [
47
+ clarinet() // Found Clarinet.toml in current directory
48
+ ]
49
+ });`;
50
+ } else {
51
+ config = `import { defineConfig } from '@stacks/codegen';
52
+
53
+ export default defineConfig({
54
+ out: './src/generated/contracts.ts',
55
+ plugins: [],
56
+ });`;
57
+ }
58
+ await fs3.writeFile(configPath, config);
59
+ spinner.succeed("Created `stacks.config.ts`");
60
+ console.log(
61
+ "\nRun `codegen generate` to generate type-safe interfaces, functions, and hooks!"
62
+ );
63
+ }
64
+ async function fileExists(filePath) {
65
+ try {
66
+ await fs3.access(filePath);
67
+ return true;
68
+ } catch {
69
+ return false;
70
+ }
71
+ }
72
+ var init_init = __esm({
73
+ "src/commands/init.ts"() {
74
+ "use strict";
75
+ init_esm_shims();
76
+ }
77
+ });
78
+
79
+ // src/cli.ts
80
+ init_esm_shims();
81
+ import { program } from "commander";
82
+
83
+ // src/commands/generate.ts
84
+ init_esm_shims();
85
+ import chalk from "chalk";
86
+ import ora from "ora";
87
+
88
+ // src/utils/config.ts
89
+ init_esm_shims();
90
+ import { promises as fs2 } from "fs";
91
+ import path3 from "path";
92
+ import { pathToFileURL } from "url";
93
+ import { createRequire } from "module";
94
+ import { transformSync } from "esbuild";
95
+
96
+ // src/core/plugin-manager.ts
97
+ init_esm_shims();
98
+ import { format } from "prettier";
99
+ import { promises as fs } from "fs";
100
+ import path2 from "path";
101
+ import { validateStacksAddress } from "@stacks/transactions";
102
+ var PluginManager = class {
103
+ constructor() {
104
+ this.plugins = [];
105
+ this.logger = this.createLogger();
106
+ this.utils = this.createUtils();
107
+ this.executionContext = {
108
+ phase: "config",
109
+ startTime: Date.now(),
110
+ results: /* @__PURE__ */ new Map()
111
+ };
112
+ }
113
+ /**
114
+ * Register a plugin
115
+ */
116
+ register(plugin) {
117
+ if (!plugin.name || !plugin.version) {
118
+ throw new Error("Plugin must have a name and version");
119
+ }
120
+ const existing = this.plugins.find((p) => p.name === plugin.name);
121
+ if (existing) {
122
+ throw new Error(
123
+ `Plugin "${plugin.name}" is already registered (version ${existing.version})`
124
+ );
125
+ }
126
+ this.plugins.push(plugin);
127
+ this.logger.debug(`Registered plugin: ${plugin.name}@${plugin.version}`);
128
+ }
129
+ /**
130
+ * Get all registered plugins
131
+ */
132
+ getPlugins() {
133
+ return [...this.plugins];
134
+ }
135
+ /**
136
+ * Transform user config through all plugins
137
+ */
138
+ async transformConfig(config) {
139
+ this.executionContext.phase = "config";
140
+ let transformedConfig = { ...config };
141
+ for (const plugin of this.plugins) {
142
+ if (plugin.transformConfig) {
143
+ this.executionContext.currentPlugin = plugin;
144
+ try {
145
+ const result = await plugin.transformConfig(transformedConfig);
146
+ transformedConfig = result;
147
+ this.recordHookResult(plugin.name, "transformConfig", {
148
+ success: true
149
+ });
150
+ } catch (error) {
151
+ const err = error;
152
+ this.recordHookResult(plugin.name, "transformConfig", {
153
+ success: false,
154
+ error: err
155
+ });
156
+ throw new Error(
157
+ `Plugin "${plugin.name}" failed during config transformation: ${err.message}`
158
+ );
159
+ }
160
+ }
161
+ }
162
+ const resolvedConfig = {
163
+ ...transformedConfig,
164
+ plugins: this.plugins
165
+ };
166
+ return resolvedConfig;
167
+ }
168
+ /**
169
+ * Transform contracts through all plugins
170
+ */
171
+ async transformContracts(contracts, _config) {
172
+ const processedContracts = [];
173
+ for (let contract of contracts) {
174
+ if (contract._clarinetSource && contract.abi) {
175
+ const address = typeof contract.address === "string" ? contract.address : "";
176
+ const [contractAddress, contractName] = address.split(".");
177
+ const processed = {
178
+ name: contract.name || contractName,
179
+ address: contractAddress,
180
+ contractName,
181
+ abi: contract.abi,
182
+ source: "local",
183
+ metadata: { source: "clarinet" }
184
+ };
185
+ processedContracts.push(processed);
186
+ continue;
187
+ }
188
+ for (const plugin of this.plugins) {
189
+ if (plugin.transformContract) {
190
+ this.executionContext.currentPlugin = plugin;
191
+ try {
192
+ contract = await plugin.transformContract(contract);
193
+ this.recordHookResult(plugin.name, "transformContract", {
194
+ success: true
195
+ });
196
+ } catch (error) {
197
+ const err = error;
198
+ this.recordHookResult(plugin.name, "transformContract", {
199
+ success: false,
200
+ error: err
201
+ });
202
+ this.logger.warn(
203
+ `Plugin "${plugin.name}" failed to transform contract: ${err.message}`
204
+ );
205
+ }
206
+ }
207
+ }
208
+ if (contract.abi) {
209
+ const processed = {
210
+ name: contract.name || "unknown",
211
+ address: typeof contract.address === "string" ? contract.address.split(".")[0] : "unknown",
212
+ contractName: contract.name || "unknown",
213
+ abi: contract.abi,
214
+ source: "api",
215
+ // Use "api" as default for plugin-processed contracts
216
+ metadata: contract.metadata
217
+ };
218
+ processedContracts.push(processed);
219
+ }
220
+ }
221
+ return processedContracts;
222
+ }
223
+ /**
224
+ * Execute lifecycle hooks
225
+ */
226
+ async executeHook(hookName, context) {
227
+ for (const plugin of this.plugins) {
228
+ const hook = plugin[hookName];
229
+ if (typeof hook === "function") {
230
+ this.executionContext.currentPlugin = plugin;
231
+ try {
232
+ await hook.call(plugin, context);
233
+ this.recordHookResult(plugin.name, hookName, {
234
+ success: true
235
+ });
236
+ } catch (error) {
237
+ const err = error;
238
+ this.recordHookResult(plugin.name, hookName, {
239
+ success: false,
240
+ error: err
241
+ });
242
+ this.logger.error(
243
+ `Plugin "${plugin.name}" failed during ${hookName}: ${err.message}`
244
+ );
245
+ }
246
+ }
247
+ }
248
+ }
249
+ /**
250
+ * Execute generation phase with full context
251
+ */
252
+ async executeGeneration(contracts, config) {
253
+ this.executionContext.phase = "generate";
254
+ const outputs = /* @__PURE__ */ new Map();
255
+ const context = {
256
+ config,
257
+ logger: this.logger,
258
+ utils: this.utils,
259
+ contracts,
260
+ outputs,
261
+ augment: (outputKey, contractName, content) => {
262
+ this.augmentOutput(outputs, outputKey, contractName, content);
263
+ },
264
+ addOutput: (key, output) => {
265
+ outputs.set(key, output);
266
+ }
267
+ };
268
+ await this.executeHook("beforeGenerate", context);
269
+ await this.executeHook("generate", context);
270
+ await this.executeHook("afterGenerate", context);
271
+ return outputs;
272
+ }
273
+ /**
274
+ * Transform outputs through plugins
275
+ */
276
+ async transformOutputs(outputs) {
277
+ this.executionContext.phase = "output";
278
+ const transformedOutputs = /* @__PURE__ */ new Map();
279
+ for (const [key, output] of outputs) {
280
+ let transformedContent = output.content;
281
+ for (const plugin of this.plugins) {
282
+ if (plugin.transformOutput) {
283
+ this.executionContext.currentPlugin = plugin;
284
+ try {
285
+ transformedContent = await plugin.transformOutput(
286
+ transformedContent,
287
+ output.type || "other"
288
+ );
289
+ this.recordHookResult(plugin.name, "transformOutput", {
290
+ success: true
291
+ });
292
+ } catch (error) {
293
+ const err = error;
294
+ this.recordHookResult(plugin.name, "transformOutput", {
295
+ success: false,
296
+ error: err
297
+ });
298
+ this.logger.warn(
299
+ `Plugin "${plugin.name}" failed to transform output: ${err.message}`
300
+ );
301
+ }
302
+ }
303
+ }
304
+ transformedOutputs.set(key, {
305
+ ...output,
306
+ content: transformedContent
307
+ });
308
+ }
309
+ return transformedOutputs;
310
+ }
311
+ /**
312
+ * Write outputs to disk
313
+ */
314
+ async writeOutputs(outputs) {
315
+ for (const [, output] of outputs) {
316
+ try {
317
+ const resolvedPath = path2.resolve(process.cwd(), output.path);
318
+ await this.utils.ensureDir(path2.dirname(resolvedPath));
319
+ await this.utils.writeFile(resolvedPath, output.content);
320
+ } catch (error) {
321
+ const err = error;
322
+ this.logger.error(`Failed to write ${output.path}: ${err.message}`);
323
+ throw err;
324
+ }
325
+ }
326
+ }
327
+ /**
328
+ * Get execution results for debugging
329
+ */
330
+ getExecutionResults() {
331
+ return new Map(this.executionContext.results);
332
+ }
333
+ /**
334
+ * Augment existing output with additional content
335
+ */
336
+ augmentOutput(outputs, outputKey, contractName, content) {
337
+ const existing = outputs.get(outputKey);
338
+ if (!existing) {
339
+ this.logger.warn(`Cannot augment non-existent output: ${outputKey}`);
340
+ return;
341
+ }
342
+ const augmentedContent = `${existing.content}
343
+
344
+ // Augmented by plugin for ${contractName}
345
+ ${JSON.stringify(content, null, 2)}`;
346
+ outputs.set(outputKey, {
347
+ ...existing,
348
+ content: augmentedContent
349
+ });
350
+ }
351
+ /**
352
+ * Record hook execution result
353
+ */
354
+ recordHookResult(pluginName, hookName, result) {
355
+ const key = `${pluginName}:${hookName}`;
356
+ const existing = this.executionContext.results.get(key) || [];
357
+ existing.push({ ...result, plugin: pluginName });
358
+ this.executionContext.results.set(key, existing);
359
+ }
360
+ /**
361
+ * Create logger instance
362
+ */
363
+ createLogger() {
364
+ return {
365
+ info: (message) => console.log(`\u2139\uFE0F ${message}`),
366
+ warn: (message) => console.warn(`\u26A0\uFE0F ${message}`),
367
+ error: (message) => console.error(`\u274C ${message}`),
368
+ debug: (message) => {
369
+ if (process.env.DEBUG) {
370
+ console.log(`\u{1F41B} ${message}`);
371
+ }
372
+ },
373
+ success: (message) => console.log(`\u2705 ${message}`)
374
+ };
375
+ }
376
+ /**
377
+ * Create utils instance
378
+ */
379
+ createUtils() {
380
+ return {
381
+ toCamelCase: (str) => {
382
+ return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
383
+ },
384
+ toKebabCase: (str) => {
385
+ return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
386
+ },
387
+ validateAddress: (address) => {
388
+ return validateStacksAddress(address.split(".")[0]);
389
+ },
390
+ parseContractId: (contractId) => {
391
+ const [address, contractName] = contractId.split(".");
392
+ return { address, contractName };
393
+ },
394
+ formatCode: async (code) => {
395
+ return format(code, {
396
+ parser: "typescript",
397
+ singleQuote: true,
398
+ semi: true,
399
+ printWidth: 100,
400
+ trailingComma: "es5"
401
+ });
402
+ },
403
+ resolvePath: (relativePath) => {
404
+ return path2.resolve(process.cwd(), relativePath);
405
+ },
406
+ fileExists: async (filePath) => {
407
+ try {
408
+ await fs.access(filePath);
409
+ return true;
410
+ } catch {
411
+ return false;
412
+ }
413
+ },
414
+ readFile: async (filePath) => {
415
+ return fs.readFile(filePath, "utf-8");
416
+ },
417
+ writeFile: async (filePath, content) => {
418
+ await fs.writeFile(filePath, content, "utf-8");
419
+ },
420
+ ensureDir: async (dirPath) => {
421
+ await fs.mkdir(dirPath, { recursive: true });
422
+ }
423
+ };
424
+ }
425
+ };
426
+
427
+ // src/utils/config.ts
428
+ var CONFIG_FILE_NAMES = [
429
+ "stacks.config.ts",
430
+ "stacks.config.js",
431
+ "stacks.config.mjs"
432
+ ];
433
+ async function findConfigFile(cwd) {
434
+ for (const fileName of CONFIG_FILE_NAMES) {
435
+ const filePath = path3.join(cwd, fileName);
436
+ try {
437
+ await fs2.access(filePath);
438
+ return filePath;
439
+ } catch {
440
+ }
441
+ }
442
+ return null;
443
+ }
444
+ async function loadConfig(configPath) {
445
+ const cwd = process.cwd();
446
+ const resolvedPath = configPath ? path3.resolve(cwd, configPath) : await findConfigFile(cwd);
447
+ if (!resolvedPath) {
448
+ throw new Error(
449
+ "No config file found. Create a stacks.config.ts file or specify a path with --config"
450
+ );
451
+ }
452
+ let config;
453
+ if (resolvedPath.endsWith(".ts")) {
454
+ const code = await fs2.readFile(resolvedPath, "utf-8");
455
+ let replacementPath;
456
+ try {
457
+ const require2 = createRequire(import.meta.url);
458
+ const packagePath = require2.resolve("@stacks/codegen");
459
+ replacementPath = pathToFileURL(packagePath).href;
460
+ } catch {
461
+ const currentModuleDir = path3.dirname(new URL(import.meta.url).pathname);
462
+ const indexPath = path3.resolve(currentModuleDir, "../index.js");
463
+ replacementPath = pathToFileURL(indexPath).href;
464
+ }
465
+ const transformedCode = code.replace(
466
+ /from\s+["']@stacks\/cli["']/g,
467
+ `from '${replacementPath}'`
468
+ );
469
+ const result = transformSync(transformedCode, {
470
+ format: "esm",
471
+ target: "node18",
472
+ loader: "ts"
473
+ });
474
+ const tempPath = resolvedPath.replace(/\.ts$/, ".mjs");
475
+ await fs2.writeFile(tempPath, result.code);
476
+ try {
477
+ const fileUrl = pathToFileURL(tempPath).href;
478
+ const module = await import(fileUrl);
479
+ config = module.default;
480
+ } finally {
481
+ await fs2.unlink(tempPath).catch(() => {
482
+ });
483
+ }
484
+ } else {
485
+ const fileUrl = pathToFileURL(resolvedPath).href;
486
+ const module = await import(fileUrl);
487
+ config = module.default;
488
+ }
489
+ if (!config) {
490
+ throw new Error("Config file must export a default configuration");
491
+ }
492
+ if (typeof config === "function") {
493
+ config = config({});
494
+ }
495
+ validateConfig(config);
496
+ const pluginManager = new PluginManager();
497
+ if (config.plugins && Array.isArray(config.plugins)) {
498
+ for (const plugin of config.plugins) {
499
+ pluginManager.register(plugin);
500
+ }
501
+ }
502
+ const resolvedConfig = await pluginManager.transformConfig(config);
503
+ return resolvedConfig;
504
+ }
505
+ function validateConfig(config) {
506
+ if (!config || typeof config !== "object") {
507
+ throw new Error("Config must be an object");
508
+ }
509
+ const c = config;
510
+ if (c.contracts && !Array.isArray(c.contracts)) {
511
+ throw new Error("Config contracts must be an array");
512
+ }
513
+ if (!c.out || typeof c.out !== "string") {
514
+ throw new Error("Config out must be a string path");
515
+ }
516
+ if (c.contracts) {
517
+ for (const contract of c.contracts) {
518
+ if (!contract.address && !contract.source) {
519
+ throw new Error("Each contract must have either an address or source");
520
+ }
521
+ }
522
+ }
523
+ if (c.plugins && !Array.isArray(c.plugins)) {
524
+ throw new Error("Config plugins must be an array");
525
+ }
526
+ }
527
+
528
+ // src/utils/api.ts
529
+ init_esm_shims();
530
+ import got from "got";
531
+
532
+ // src/parsers/clarity.ts
533
+ init_esm_shims();
534
+
535
+ // src/generators/contract.ts
536
+ init_esm_shims();
537
+ import { format as format2 } from "prettier";
538
+ async function generateContractInterface(contracts) {
539
+ const imports = `import { Cl, validateStacksAddress } from '@stacks/transactions'`;
540
+ const header = `/**
541
+ * Generated by @stacks/codegen
542
+ * DO NOT EDIT MANUALLY
543
+ */`;
544
+ const contractsCode = contracts.map((contract) => generateContract(contract)).join("\n\n");
545
+ const code = `${imports}
546
+
547
+ ${header}
548
+
549
+ ${contractsCode}`;
550
+ const formatted = await format2(code, {
551
+ parser: "typescript",
552
+ singleQuote: true,
553
+ semi: true,
554
+ printWidth: 100,
555
+ trailingComma: "es5"
556
+ });
557
+ return formatted;
558
+ }
559
+ function generateContract(contract) {
560
+ const { name, address, contractName, abi } = contract;
561
+ const abiCode = generateAbiConstant(name, abi);
562
+ const methods = abi.functions.filter((func) => func.access !== "private").map((func) => generateMethod(func, address, contractName)).join(",\n\n ");
563
+ const contractCode = `export const ${name} = {
564
+ address: '${address}',
565
+ contractAddress: '${address}',
566
+ contractName: '${contractName}',
567
+
568
+ ${methods}
569
+ } as const`;
570
+ return `${abiCode}
571
+
572
+ ${contractCode}`;
573
+ }
574
+ function generateAbiConstant(name, abi) {
575
+ const abiJson = JSON.stringify(abi, null, 2).replace(/"([a-zA-Z_$][a-zA-Z0-9_$]*)":/g, "$1:").replace(/"/g, "'");
576
+ return `export const ${name}Abi = ${abiJson} as const`;
577
+ }
578
+ function generateMethod(func, address, contractName) {
579
+ const methodName = toCamelCase(func.name);
580
+ if (func.args.length === 0) {
581
+ return `${methodName}() {
582
+ return {
583
+ contractAddress: '${address}',
584
+ contractName: '${contractName}',
585
+ functionName: '${func.name}',
586
+ functionArgs: []
587
+ }
588
+ }`;
589
+ }
590
+ if (func.args.length === 1) {
591
+ const originalArgName = func.args[0].name;
592
+ const argName = toCamelCase(originalArgName);
593
+ const argType = getTypeForArg(func.args[0]);
594
+ const clarityConversion = generateClarityConversion(argName, func.args[0]);
595
+ return `${methodName}(...args: [{ ${argName}: ${argType} }] | [${argType}]) {
596
+ const ${argName} = args.length === 1 && typeof args[0] === 'object' && args[0] !== null && '${argName}' in args[0]
597
+ ? args[0].${argName}
598
+ : args[0] as ${argType}
599
+
600
+ return {
601
+ contractAddress: '${address}',
602
+ contractName: '${contractName}',
603
+ functionName: '${func.name}',
604
+ functionArgs: [${clarityConversion}]
605
+ }
606
+ }`;
607
+ }
608
+ const argsList = func.args.map((arg) => toCamelCase(arg.name)).join(", ");
609
+ const argsTypes = func.args.map((arg) => {
610
+ const camelName = toCamelCase(arg.name);
611
+ return `${camelName}: ${getTypeForArg(arg)}`;
612
+ }).join("; ");
613
+ const argsArray = func.args.map((arg) => {
614
+ const argName = toCamelCase(arg.name);
615
+ return generateClarityConversion(argName, arg);
616
+ }).join(", ");
617
+ const objectAccess = func.args.map((arg) => {
618
+ const camelName = toCamelCase(arg.name);
619
+ return `args[0].${camelName}`;
620
+ }).join(", ");
621
+ const positionTypes = func.args.map((arg) => getTypeForArg(arg)).join(", ");
622
+ return `${methodName}(...args: [{ ${argsTypes} }] | [${positionTypes}]) {
623
+ const [${argsList}] = args.length === 1 && typeof args[0] === 'object' && args[0] !== null
624
+ ? [${objectAccess}]
625
+ : args as [${positionTypes}]
626
+
627
+ return {
628
+ contractAddress: '${address}',
629
+ contractName: '${contractName}',
630
+ functionName: '${func.name}',
631
+ functionArgs: [${argsArray}]
632
+ }
633
+ }`;
634
+ }
635
+ function getTypeForArg(arg) {
636
+ const type = arg.type;
637
+ if (typeof type === "string") {
638
+ switch (type) {
639
+ case "uint128":
640
+ case "int128":
641
+ return "bigint";
642
+ case "bool":
643
+ return "boolean";
644
+ case "principal":
645
+ case "trait_reference":
646
+ return "string";
647
+ default:
648
+ return "any";
649
+ }
650
+ }
651
+ if (type["string-ascii"] || type["string-utf8"]) {
652
+ return "string";
653
+ }
654
+ if (type.buff) {
655
+ return "Uint8Array | string | { type: 'ascii' | 'utf8' | 'hex'; value: string }";
656
+ }
657
+ if (type.optional) {
658
+ const innerType = getTypeForArg({ type: type.optional });
659
+ return `${innerType} | null`;
660
+ }
661
+ if (type.list) {
662
+ const innerType = getTypeForArg({ type: type.list.type });
663
+ return `${innerType}[]`;
664
+ }
665
+ if (type.tuple) {
666
+ const fields = type.tuple.map(
667
+ (field) => `${toCamelCase(field.name)}: ${getTypeForArg({ type: field.type })}`
668
+ ).join("; ");
669
+ return `{ ${fields} }`;
670
+ }
671
+ if (type.response) {
672
+ const okType = getTypeForArg({ type: type.response.ok });
673
+ const errType = getTypeForArg({ type: type.response.error });
674
+ return `{ ok: ${okType} } | { err: ${errType} }`;
675
+ }
676
+ return "any";
677
+ }
678
+ function toCamelCase(str) {
679
+ return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()).replace(/-([A-Z])/g, (_, letter) => letter).replace(/-(\d)/g, (_, digit) => digit).replace(/-/g, "");
680
+ }
681
+ function generateClarityConversion(argName, argType) {
682
+ const type = argType.type;
683
+ if (typeof type === "string") {
684
+ switch (type) {
685
+ case "uint128":
686
+ return `Cl.uint(${argName})`;
687
+ case "int128":
688
+ return `Cl.int(${argName})`;
689
+ case "bool":
690
+ return `Cl.bool(${argName})`;
691
+ case "principal":
692
+ case "trait_reference":
693
+ return `(() => {
694
+ const [address, contractName] = ${argName}.split(".") as [string, string];
695
+ if (!validateStacksAddress(address)) {
696
+ throw new Error("Invalid Stacks address format");
697
+ }
698
+ if (${argName}.includes(".")) {
699
+ return Cl.contractPrincipal(address, contractName);
700
+ } else {
701
+ return Cl.standardPrincipal(${argName});
702
+ }
703
+ })()`;
704
+ default:
705
+ return `${argName}`;
706
+ }
707
+ }
708
+ if (type["string-ascii"]) {
709
+ return `Cl.stringAscii(${argName})`;
710
+ }
711
+ if (type["string-utf8"]) {
712
+ return `Cl.stringUtf8(${argName})`;
713
+ }
714
+ if (type.buff) {
715
+ return `(() => {
716
+ const value = ${argName};
717
+ // Direct Uint8Array
718
+ if (value instanceof Uint8Array) {
719
+ return Cl.buffer(value);
720
+ }
721
+ // Object notation with explicit type
722
+ if (typeof value === 'object' && value !== null && value.type && value.value) {
723
+ switch (value.type) {
724
+ case 'ascii':
725
+ return Cl.bufferFromAscii(value.value);
726
+ case 'utf8':
727
+ return Cl.bufferFromUtf8(value.value);
728
+ case 'hex':
729
+ return Cl.bufferFromHex(value.value);
730
+ default:
731
+ throw new Error(\`Unsupported buffer type: \${value.type}\`);
732
+ }
733
+ }
734
+ // Auto-detect string type
735
+ if (typeof value === 'string') {
736
+ // Check for hex (0x prefix or pure hex pattern)
737
+ if (value.startsWith('0x') || /^[0-9a-fA-F]+$/.test(value)) {
738
+ return Cl.bufferFromHex(value);
739
+ }
740
+ // Check for non-ASCII characters (UTF-8)
741
+ if (!/^[\\x00-\\x7F]*$/.test(value)) {
742
+ return Cl.bufferFromUtf8(value);
743
+ }
744
+ // Default to ASCII for simple ASCII strings
745
+ return Cl.bufferFromAscii(value);
746
+ }
747
+ throw new Error(\`Invalid buffer value: \${value}\`);
748
+ })()`;
749
+ }
750
+ if (type.optional) {
751
+ const innerConversion = generateClarityConversion(argName, {
752
+ type: type.optional
753
+ });
754
+ return `${argName} !== null ? Cl.some(${innerConversion.replace(argName, `${argName}`)}) : Cl.none()`;
755
+ }
756
+ if (type.list) {
757
+ const innerConversion = generateClarityConversion("item", {
758
+ type: type.list.type
759
+ });
760
+ return `Cl.list(${argName}.map(item => ${innerConversion}))`;
761
+ }
762
+ if (type.tuple) {
763
+ const fields = type.tuple.map((field) => {
764
+ const camelFieldName = toCamelCase(field.name);
765
+ const fieldConversion = generateClarityConversion(
766
+ `${argName}.${camelFieldName}`,
767
+ { type: field.type }
768
+ );
769
+ return `"${field.name}": ${fieldConversion}`;
770
+ }).join(", ");
771
+ return `Cl.tuple({ ${fields} })`;
772
+ }
773
+ if (type.response) {
774
+ const okConversion = generateClarityConversion(`${argName}.ok`, {
775
+ type: type.response.ok
776
+ });
777
+ const errConversion = generateClarityConversion(`${argName}.err`, {
778
+ type: type.response.error
779
+ });
780
+ return `'ok' in ${argName} ? Cl.ok(${okConversion.replace(`${argName}.ok`, `${argName}.ok`)}) : Cl.error(${errConversion.replace(`${argName}.err`, `${argName}.err`)})`;
781
+ }
782
+ return `${argName}`;
783
+ }
784
+
785
+ // src/commands/generate.ts
786
+ async function generate(options) {
787
+ const spinner = ora("Processing contracts").start();
788
+ try {
789
+ const config = await loadConfig(options.config);
790
+ const pluginManager = new PluginManager();
791
+ if (config.plugins) {
792
+ for (const plugin of config.plugins) {
793
+ pluginManager.register(plugin);
794
+ }
795
+ }
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
+ );
812
+ if (processedContracts.length === 0) {
813
+ spinner.warn("No contracts found to generate");
814
+ console.log("\nTo get started:");
815
+ console.log(" \u2022 Add contracts to your config file, or");
816
+ console.log(" \u2022 Use plugins like clarinet() for local contracts");
817
+ return;
818
+ }
819
+ const outputs = await pluginManager.executeGeneration(
820
+ processedContracts,
821
+ config
822
+ );
823
+ if (!outputs.has("contracts") && processedContracts.length > 0) {
824
+ const contractsCode = await generateContractInterface(processedContracts);
825
+ outputs.set("contracts", {
826
+ path: config.out,
827
+ content: contractsCode,
828
+ type: "contracts"
829
+ });
830
+ }
831
+ const transformedOutputs = await pluginManager.transformOutputs(outputs);
832
+ await pluginManager.writeOutputs(transformedOutputs);
833
+ const contractCount = processedContracts.length;
834
+ const contractWord = contractCount === 1 ? "contract" : "contracts";
835
+ spinner.succeed(`Generation complete for ${contractCount} ${contractWord}`);
836
+ console.log(`
837
+ \u{1F4C4} ${config.out}`);
838
+ console.log(`
839
+ \u{1F4A1} Import your contracts:`);
840
+ if (processedContracts.length > 0) {
841
+ const exampleContract = processedContracts[0];
842
+ console.log(
843
+ chalk.gray(
844
+ ` import { ${exampleContract.name} } from '${config.out.replace(/\.ts$/, "")}'`
845
+ )
846
+ );
847
+ if (processedContracts.length > 1) {
848
+ console.log(
849
+ chalk.gray(
850
+ ` // Also available: ${processedContracts.slice(1).map((c) => c.name).join(", ")}`
851
+ )
852
+ );
853
+ }
854
+ }
855
+ } catch (error) {
856
+ spinner.fail("Generation failed");
857
+ console.error(chalk.red(`
858
+ ${error.message}`));
859
+ if (process.env.DEBUG) {
860
+ console.error(error.stack);
861
+ }
862
+ process.exit(1);
863
+ }
864
+ }
865
+
866
+ // src/cli.ts
867
+ 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);
869
+ 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));
871
+ await init2();
872
+ });
873
+ program.parse();
874
+ //# sourceMappingURL=cli.js.map