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