@secondlayer/cli 14.3.0 → 15.0.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/index.js CHANGED
@@ -46,13 +46,8 @@ var __export = (target, all) => {
46
46
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
47
47
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
48
48
 
49
- // src/types/plugin.ts
50
- function isClarinetContract(c) {
51
- return "_clarinetSource" in c && c._clarinetSource === true;
52
- }
53
- function isDirectFileContract(c) {
54
- return "_directFile" in c && c._directFile === true;
55
- }
49
+ // src/utils/constants.ts
50
+ var DEFAULT_SENDER_ADDRESS = "SP000000000000000000002Q6VF78";
56
51
 
57
52
  // src/utils/contract-id.ts
58
53
  function parseContractId(contractId) {
@@ -66,339 +61,137 @@ function parseContractId(contractId) {
66
61
  };
67
62
  }
68
63
 
69
- // src/utils/format.ts
70
- var exports_format = {};
71
- __export(exports_format, {
72
- formatCode: () => formatCode
73
- });
74
- import { Biome, Distribution } from "@biomejs/js-api";
75
- async function getBiome() {
76
- if (!biome) {
77
- biome = await Biome.create({
78
- distribution: Distribution.NODE
79
- });
80
- biome.applyConfiguration({
81
- formatter: {
82
- enabled: true,
83
- indentStyle: "tab",
84
- lineWidth: 80
85
- },
86
- javascript: {
87
- formatter: {
88
- semicolons: "always",
89
- quoteStyle: "single"
90
- }
91
- },
92
- organizeImports: {
93
- enabled: true
94
- },
95
- linter: {
96
- enabled: true
97
- },
98
- assists: {
99
- enabled: true
100
- }
101
- });
102
- }
103
- return biome;
104
- }
105
- async function formatCode(code) {
106
- const b = await getBiome();
107
- const linted = b.lintContent(code, {
108
- filePath: "generated.ts",
109
- fixFileMode: "SafeFixes"
110
- });
111
- const formatted = b.formatContent(linted.content, {
112
- filePath: "generated.ts"
113
- });
114
- return formatted.content;
115
- }
116
- var biome = null;
117
- var init_format = () => {};
118
-
119
- // src/core/plugin-manager.ts
120
- import { promises as fs } from "node:fs";
121
- import path from "node:path";
122
- import { isValidAddress as _validateStacksAddress } from "@secondlayer/stacks";
123
- import { getErrorMessage } from "@secondlayer/shared";
64
+ // src/plugins/clarinet/index.ts
124
65
  import { toCamelCase } from "@secondlayer/stacks/clarity";
125
-
126
- class PluginManager {
127
- plugins = [];
128
- logger;
129
- utils;
130
- executionContext;
131
- constructor() {
132
- this.logger = this.createLogger();
133
- this.utils = this.createUtils();
134
- this.executionContext = {
135
- phase: "config",
136
- startTime: Date.now(),
137
- results: new Map
138
- };
66
+ import { normalizeAbi } from "@secondlayer/stacks/clarity";
67
+ import { initSimnet } from "@stacks/clarinet-sdk";
68
+ function sanitizeContractName(name) {
69
+ return toCamelCase(name);
70
+ }
71
+ function matchesContractFilters(name, options) {
72
+ if (options.include && !options.include.includes(name)) {
73
+ return false;
139
74
  }
140
- register(plugin) {
141
- if (!plugin.name || !plugin.version) {
142
- throw new Error("Plugin must have a name and version");
75
+ if (options.exclude?.includes(name)) {
76
+ return false;
77
+ }
78
+ return true;
79
+ }
80
+ async function readManifestInfo(manifestPath) {
81
+ try {
82
+ const { promises: fs } = await import("node:fs");
83
+ const tomlContent = await fs.readFile(manifestPath, "utf-8");
84
+ const projectContracts = new Set;
85
+ const contractSectionRegex = /^\[contracts\.([^\]]+)\]/gm;
86
+ let match = contractSectionRegex.exec(tomlContent);
87
+ while (match !== null) {
88
+ projectContracts.add(match[1]);
89
+ match = contractSectionRegex.exec(tomlContent);
143
90
  }
144
- const existing = this.plugins.find((p) => p.name === plugin.name);
145
- if (existing) {
146
- throw new Error(`Plugin "${plugin.name}" is already registered (version ${existing.version})`);
91
+ const requirementIds = new Set;
92
+ const requirementRegex = /contract_id\s*=\s*["']([^"']+)["']/g;
93
+ match = requirementRegex.exec(tomlContent);
94
+ while (match !== null) {
95
+ requirementIds.add(match[1]);
96
+ match = requirementRegex.exec(tomlContent);
147
97
  }
148
- this.plugins.push(plugin);
149
- this.logger.debug(`Registered plugin: ${plugin.name}@${plugin.version}`);
98
+ return { projectContracts, requirementIds };
99
+ } catch {
100
+ return null;
150
101
  }
151
- getPlugins() {
152
- return [...this.plugins];
102
+ }
103
+ function classifyContract(contractId, manifest) {
104
+ const { address, contractName } = parseContractId(contractId);
105
+ if (manifest) {
106
+ if (manifest.projectContracts.has(contractName))
107
+ return "project";
108
+ if (manifest.requirementIds.has(contractId))
109
+ return "requirement";
110
+ return "system";
153
111
  }
154
- async transformConfig(config) {
155
- this.executionContext.phase = "config";
156
- let transformedConfig = { ...config };
157
- for (const plugin of this.plugins) {
158
- if (plugin.transformConfig) {
159
- this.executionContext.currentPlugin = plugin;
160
- try {
161
- const result = await plugin.transformConfig(transformedConfig);
162
- transformedConfig = result;
163
- this.recordHookResult(plugin.name, "transformConfig", {
164
- success: true
165
- });
166
- } catch (error) {
167
- this.recordHookResult(plugin.name, "transformConfig", {
168
- success: false,
169
- error: error instanceof Error ? error : new Error(getErrorMessage(error))
170
- });
171
- throw new Error(`Plugin "${plugin.name}" failed during config transformation: ${getErrorMessage(error)}`);
172
- }
173
- }
174
- }
175
- const resolvedConfig = {
176
- ...transformedConfig,
177
- plugins: this.plugins
178
- };
179
- return resolvedConfig;
112
+ const systemContractPatterns = [
113
+ /^pox-\d+$/,
114
+ /^bns$/,
115
+ /^costs-\d+$/,
116
+ /^lockup$/,
117
+ /^signer-manager$/
118
+ ];
119
+ if (systemContractPatterns.some((pattern) => pattern.test(contractName))) {
120
+ return "system";
180
121
  }
181
- async transformContracts(contracts, _config) {
182
- const processedContracts = [];
183
- for (let contract of contracts) {
184
- if (isClarinetContract(contract) && contract.abi) {
185
- processedContracts.push(this.contractToProcessed(contract, "clarinet"));
186
- continue;
187
- }
188
- if (isDirectFileContract(contract) && contract.abi) {
189
- processedContracts.push(this.contractToProcessed(contract, "direct"));
190
- continue;
191
- }
192
- for (const plugin of this.plugins) {
193
- if (plugin.transformContract) {
194
- this.executionContext.currentPlugin = plugin;
195
- try {
196
- contract = await plugin.transformContract(contract);
197
- this.recordHookResult(plugin.name, "transformContract", {
198
- success: true
199
- });
200
- } catch (error) {
201
- this.recordHookResult(plugin.name, "transformContract", {
202
- success: false,
203
- error: error instanceof Error ? error : new Error(getErrorMessage(error))
204
- });
205
- this.logger.warn(`Plugin "${plugin.name}" failed to transform contract: ${getErrorMessage(error)}`);
206
- }
207
- }
208
- }
209
- if (contract.abi) {
210
- processedContracts.push(this.contractToProcessed(contract, "api"));
211
- }
212
- }
213
- return processedContracts;
122
+ const systemAddresses = [
123
+ DEFAULT_SENDER_ADDRESS,
124
+ "ST000000000000000000002AMW42H"
125
+ ];
126
+ if (systemAddresses.includes(address)) {
127
+ return "system";
214
128
  }
215
- async executeHook(hookName, context) {
216
- for (const plugin of this.plugins) {
217
- const hook = plugin[hookName];
218
- if (typeof hook === "function") {
219
- this.executionContext.currentPlugin = plugin;
220
- try {
221
- await hook.call(plugin, context);
222
- this.recordHookResult(plugin.name, hookName, {
223
- success: true
224
- });
225
- } catch (error) {
226
- this.recordHookResult(plugin.name, hookName, {
227
- success: false,
228
- error: error instanceof Error ? error : new Error(getErrorMessage(error))
229
- });
230
- this.logger.error(`Plugin "${plugin.name}" failed during ${hookName}: ${getErrorMessage(error)}`);
129
+ return "project";
130
+ }
131
+ async function loadClarinetContracts(options = {}) {
132
+ const manifestPath = options.path || "./Clarinet.toml";
133
+ try {
134
+ const simnet = await initSimnet(manifestPath);
135
+ const contractInterfaces = simnet.getContractsInterfaces();
136
+ const contracts = [];
137
+ const manifest = await readManifestInfo(manifestPath);
138
+ const includeRequirements = options.includeRequirements ?? true;
139
+ for (const [contractId, abi] of contractInterfaces) {
140
+ const { contractName } = parseContractId(contractId);
141
+ const kind = classifyContract(contractId, manifest);
142
+ if (kind === "system" || kind === "requirement" && !includeRequirements) {
143
+ if (options.debug) {
144
+ console.log(`\uD83D\uDEAB Skipping ${kind} contract: ${contractId}`);
231
145
  }
146
+ continue;
232
147
  }
233
- }
234
- }
235
- async executeGeneration(contracts, config) {
236
- this.executionContext.phase = "generate";
237
- const outputs = new Map;
238
- const context = {
239
- config,
240
- logger: this.logger,
241
- utils: this.utils,
242
- contracts,
243
- outputs,
244
- augment: (outputKey, contractName, content) => {
245
- this.augmentOutput(outputs, outputKey, contractName, content);
246
- },
247
- addOutput: (key, output) => {
248
- outputs.set(key, output);
249
- }
250
- };
251
- await this.executeHook("beforeGenerate", context);
252
- await this.executeHook("generate", context);
253
- await this.executeHook("afterGenerate", context);
254
- return outputs;
255
- }
256
- async transformOutputs(outputs) {
257
- this.executionContext.phase = "output";
258
- const transformedOutputs = new Map;
259
- for (const [key, output] of outputs) {
260
- let transformedContent = output.content;
261
- for (const plugin of this.plugins) {
262
- if (plugin.transformOutput) {
263
- this.executionContext.currentPlugin = plugin;
264
- try {
265
- transformedContent = await plugin.transformOutput(transformedContent, output.type || "other");
266
- this.recordHookResult(plugin.name, "transformOutput", {
267
- success: true
268
- });
269
- } catch (error) {
270
- this.recordHookResult(plugin.name, "transformOutput", {
271
- success: false,
272
- error: error instanceof Error ? error : new Error(getErrorMessage(error))
273
- });
274
- this.logger.warn(`Plugin "${plugin.name}" failed to transform output: ${getErrorMessage(error)}`);
275
- }
276
- }
148
+ if (!matchesContractFilters(contractName, options)) {
149
+ continue;
277
150
  }
278
- transformedOutputs.set(key, {
279
- ...output,
280
- content: transformedContent
151
+ contracts.push({
152
+ name: sanitizeContractName(contractName),
153
+ address: contractId,
154
+ abi: normalizeAbi(abi),
155
+ _clarinetSource: true
281
156
  });
282
157
  }
283
- return transformedOutputs;
284
- }
285
- async writeOutputs(outputs) {
286
- for (const [, output] of outputs) {
287
- try {
288
- const resolvedPath = path.resolve(process.cwd(), output.path);
289
- await this.utils.ensureDir(path.dirname(resolvedPath));
290
- await this.utils.writeFile(resolvedPath, output.content);
291
- } catch (error) {
292
- this.logger.error(`Failed to write ${output.path}: ${getErrorMessage(error)}`);
293
- throw error;
294
- }
158
+ if (options.debug) {
159
+ console.log(`\uD83D\uDD0D Clarinet found ${contracts.length} user-defined contracts`);
295
160
  }
296
- }
297
- getExecutionResults() {
298
- return new Map(this.executionContext.results);
299
- }
300
- contractToProcessed(contract, source) {
301
- const address = typeof contract.address === "string" ? contract.address : "";
302
- const parsed = parseContractId(address);
303
- return {
304
- name: contract.name || parsed.contractName || "unknown",
305
- address: parsed.address || "unknown",
306
- contractName: parsed.contractName || contract.name || "unknown",
307
- abi: contract.abi,
308
- source: source === "api" ? "api" : "local",
309
- metadata: contract.metadata ?? { source }
310
- };
311
- }
312
- augmentOutput(outputs, outputKey, contractName, content) {
313
- const existing = outputs.get(outputKey);
314
- if (!existing) {
315
- this.logger.warn(`Cannot augment non-existent output: ${outputKey}`);
316
- return;
161
+ return contracts;
162
+ } catch (error) {
163
+ const err = error;
164
+ if (await hasClarinetProject(manifestPath)) {
165
+ console.warn(`⚠️ Clarinet: found ${manifestPath} but failed to load contracts: ${err.message}`);
166
+ } else if (options.debug) {
167
+ console.warn(`⚠️ Clarinet: no manifest at ${manifestPath}, skipping`);
317
168
  }
318
- const augmentedContent = `${existing.content}
319
-
320
- // Augmented by plugin for ${contractName}
321
- ${JSON.stringify(content, null, 2)}`;
322
- outputs.set(outputKey, {
323
- ...existing,
324
- content: augmentedContent
325
- });
326
- }
327
- recordHookResult(pluginName, hookName, result) {
328
- const key = `${pluginName}:${hookName}`;
329
- const existing = this.executionContext.results.get(key) || [];
330
- existing.push({ ...result, plugin: pluginName });
331
- this.executionContext.results.set(key, existing);
332
- }
333
- createLogger() {
334
- return {
335
- info: (message) => console.log(`ℹ️ ${message}`),
336
- warn: (message) => console.warn(`⚠️ ${message}`),
337
- error: (message) => console.error(`❌ ${message}`),
338
- debug: (message) => {
339
- if (process.env.DEBUG) {
340
- console.log(`\uD83D\uDC1B ${message}`);
341
- }
342
- },
343
- success: (message) => console.log(`✅ ${message}`)
344
- };
169
+ return [];
345
170
  }
346
- createUtils() {
347
- return {
348
- toCamelCase,
349
- toKebabCase: (str) => {
350
- return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
351
- },
352
- validateAddress: (address) => {
353
- return validateStacksAddress(parseContractId(address).address);
354
- },
355
- parseContractId: (contractId) => {
356
- return parseContractId(contractId);
357
- },
358
- formatCode: async (code) => {
359
- const { formatCode: formatCode2 } = await Promise.resolve().then(() => (init_format(), exports_format));
360
- return formatCode2(code);
361
- },
362
- resolvePath: (relativePath) => {
363
- return path.resolve(process.cwd(), relativePath);
364
- },
365
- fileExists: async (filePath) => {
366
- try {
367
- await fs.access(filePath);
368
- return true;
369
- } catch {
370
- return false;
371
- }
372
- },
373
- readFile: async (filePath) => {
374
- return fs.readFile(filePath, "utf-8");
375
- },
376
- writeFile: async (filePath, content) => {
377
- await fs.writeFile(filePath, content, "utf-8");
378
- },
379
- ensureDir: async (dirPath) => {
380
- await fs.mkdir(dirPath, { recursive: true });
381
- }
382
- };
171
+ }
172
+ async function hasClarinetProject(path = "./Clarinet.toml") {
173
+ try {
174
+ const { promises: fs } = await import("node:fs");
175
+ await fs.access(path);
176
+ return true;
177
+ } catch {
178
+ return false;
383
179
  }
384
180
  }
385
- var validateStacksAddress;
386
- var init_plugin_manager = __esm(() => {
387
- validateStacksAddress = _validateStacksAddress;
388
- });
181
+ var init_clarinet = () => {};
389
182
 
390
183
  // src/utils/config.ts
391
184
  import { randomBytes } from "node:crypto";
392
- import { promises as fs2 } from "node:fs";
185
+ import { promises as fs } from "node:fs";
393
186
  import { createRequire as createRequire2 } from "node:module";
394
187
  import { tmpdir } from "node:os";
395
- import path2 from "node:path";
188
+ import path from "node:path";
396
189
  import { pathToFileURL } from "node:url";
397
190
  async function findConfigFile(cwd) {
398
191
  for (const fileName of CONFIG_FILE_NAMES) {
399
- const filePath = path2.join(cwd, fileName);
192
+ const filePath = path.join(cwd, fileName);
400
193
  try {
401
- await fs2.access(filePath);
194
+ await fs.access(filePath);
402
195
  return filePath;
403
196
  } catch {}
404
197
  }
@@ -406,21 +199,21 @@ async function findConfigFile(cwd) {
406
199
  }
407
200
  async function loadConfig(configPath) {
408
201
  const cwd = process.cwd();
409
- const resolvedPath = configPath ? path2.resolve(cwd, configPath) : await findConfigFile(cwd);
202
+ const resolvedPath = configPath ? path.resolve(cwd, configPath) : await findConfigFile(cwd);
410
203
  if (!resolvedPath) {
411
204
  throw new Error("No config file found. Create a secondlayer.config.ts file or specify a path with --config");
412
205
  }
413
206
  let config;
414
207
  if (resolvedPath.endsWith(".ts")) {
415
- const code = await fs2.readFile(resolvedPath, "utf-8");
208
+ const code = await fs.readFile(resolvedPath, "utf-8");
416
209
  let replacementPath;
417
210
  try {
418
211
  const require2 = createRequire2(import.meta.url);
419
212
  const packagePath = require2.resolve("@secondlayer/cli");
420
213
  replacementPath = pathToFileURL(packagePath).href;
421
214
  } catch {
422
- const currentModuleDir = path2.dirname(new URL(import.meta.url).pathname);
423
- const indexPath = path2.resolve(currentModuleDir, "../index");
215
+ const currentModuleDir = path.dirname(new URL(import.meta.url).pathname);
216
+ const indexPath = path.resolve(currentModuleDir, "../index");
424
217
  replacementPath = pathToFileURL(indexPath).href;
425
218
  }
426
219
  const transformedCode = code.replace(/from\s+["']@secondlayer\/cli["']/g, `from '${replacementPath}'`);
@@ -430,14 +223,14 @@ async function loadConfig(configPath) {
430
223
  target: "node18",
431
224
  loader: "ts"
432
225
  });
433
- const tempPath = path2.join(tmpdir(), `secondlayer-config-${randomBytes(4).toString("hex")}.mjs`);
434
- await fs2.writeFile(tempPath, result.code);
226
+ const tempPath = path.join(tmpdir(), `secondlayer-config-${randomBytes(4).toString("hex")}.mjs`);
227
+ await fs.writeFile(tempPath, result.code);
435
228
  try {
436
229
  const fileUrl = pathToFileURL(tempPath).href;
437
230
  const module = await import(fileUrl);
438
231
  config = module.default;
439
232
  } finally {
440
- await fs2.unlink(tempPath).catch(() => {});
233
+ await fs.unlink(tempPath).catch(() => {});
441
234
  }
442
235
  } else {
443
236
  const fileUrl = pathToFileURL(resolvedPath).href;
@@ -451,14 +244,15 @@ async function loadConfig(configPath) {
451
244
  config = config({});
452
245
  }
453
246
  validateConfig(config);
454
- const pluginManager = new PluginManager;
455
- if (config.plugins && Array.isArray(config.plugins)) {
456
- for (const plugin of config.plugins) {
457
- pluginManager.register(plugin);
458
- }
247
+ if (config.clarinet === true || config.clarinet && typeof config.clarinet === "object") {
248
+ const options = config.clarinet === true ? {} : config.clarinet;
249
+ const loaded = await loadClarinetContracts(options);
250
+ config = {
251
+ ...config,
252
+ contracts: [...config.contracts || [], ...loaded]
253
+ };
459
254
  }
460
- const resolvedConfig = await pluginManager.transformConfig(config);
461
- return resolvedConfig;
255
+ return config;
462
256
  }
463
257
  function validateConfig(config) {
464
258
  if (!config || typeof config !== "object") {
@@ -473,13 +267,13 @@ function validateConfig(config) {
473
267
  }
474
268
  if (c.contracts) {
475
269
  for (const contract of c.contracts) {
476
- if (!contract.address && !contract.source) {
270
+ if (!contract.address && !contract.source && !contract.abi) {
477
271
  throw new Error("Each contract must have either an address or source");
478
272
  }
479
273
  }
480
274
  }
481
- if (c.plugins && !Array.isArray(c.plugins)) {
482
- throw new Error("Config plugins must be an array");
275
+ if (c.clarinet !== undefined && typeof c.clarinet !== "boolean" && (typeof c.clarinet !== "object" || c.clarinet === null)) {
276
+ throw new Error("Config clarinet must be true or an options object");
483
277
  }
484
278
  }
485
279
  function defineConfig(configOrDefiner) {
@@ -487,7 +281,7 @@ function defineConfig(configOrDefiner) {
487
281
  }
488
282
  var CONFIG_FILE_NAMES;
489
283
  var init_config = __esm(() => {
490
- init_plugin_manager();
284
+ init_clarinet();
491
285
  CONFIG_FILE_NAMES = [
492
286
  "secondlayer.config.ts",
493
287
  "secondlayer.config",
@@ -497,11 +291,9 @@ var init_config = __esm(() => {
497
291
 
498
292
  // src/index.ts
499
293
  init_config();
500
- init_plugin_manager();
501
294
  export {
502
- defineConfig,
503
- PluginManager
295
+ defineConfig
504
296
  };
505
297
 
506
- //# debugId=30EC1633862357D564756E2164756E21
298
+ //# debugId=0B67AD9AFC5A2DC564756E2164756E21
507
299
  //# sourceMappingURL=index.js.map