@caatinga/cli 0.2.1 → 0.2.3

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.
Files changed (30) hide show
  1. package/README.md +11 -4
  2. package/dist/index.js +325 -38
  3. package/package.json +3 -3
  4. package/templates/marketplace-with-token/README.md +0 -19
  5. package/templates/marketplace-with-token/caatinga.artifacts.json +0 -10
  6. package/templates/marketplace-with-token/caatinga.config.ts +0 -30
  7. package/templates/marketplace-with-token/caatinga.template.json +0 -21
  8. package/templates/marketplace-with-token/contracts/marketplace/Cargo.toml +0 -23
  9. package/templates/marketplace-with-token/contracts/marketplace/src/lib.rs +0 -13
  10. package/templates/marketplace-with-token/contracts/token/Cargo.toml +0 -23
  11. package/templates/marketplace-with-token/contracts/token/src/lib.rs +0 -13
  12. package/templates/marketplace-with-token/package.json +0 -17
  13. package/templates/marketplace-with-token/src/main.ts +0 -10
  14. package/templates/marketplace-with-token/tsconfig.json +0 -11
  15. package/templates/react-vite-counter/README.md +0 -58
  16. package/templates/react-vite-counter/caatinga.config.ts +0 -26
  17. package/templates/react-vite-counter/caatinga.template.json +0 -21
  18. package/templates/react-vite-counter/contracts/counter/Cargo.toml +0 -23
  19. package/templates/react-vite-counter/contracts/counter/src/lib.rs +0 -36
  20. package/templates/react-vite-counter/index.html +0 -12
  21. package/templates/react-vite-counter/package.json +0 -29
  22. package/templates/react-vite-counter/public/.gitkeep +0 -1
  23. package/templates/react-vite-counter/src/App.tsx +0 -18
  24. package/templates/react-vite-counter/src/components/CounterCard.tsx +0 -29
  25. package/templates/react-vite-counter/src/components/WalletButton.tsx +0 -12
  26. package/templates/react-vite-counter/src/contracts/generated/.gitkeep +0 -1
  27. package/templates/react-vite-counter/src/main.tsx +0 -10
  28. package/templates/react-vite-counter/src/styles.css +0 -155
  29. package/templates/react-vite-counter/tsconfig.json +0 -21
  30. package/templates/react-vite-counter/vite.config.ts +0 -6
package/README.md CHANGED
@@ -14,12 +14,12 @@ Inside a generated project, prefer `npx caatinga` so the project-local workflow
14
14
  ## Requirements
15
15
 
16
16
  - Node.js `>=20`
17
- - [Stellar CLI](https://developers.stellar.org/docs/tools/developer-tools/cli/stellar-cli) `>=22.0.0` and `<=25.2.0` on `PATH`
18
- - Rust stable and the `wasm32-unknown-unknown` target (contract builds)
17
+ - [Stellar CLI](https://developers.stellar.org/docs/tools/developer-tools/cli/stellar-cli) `>=23.0.0` and `<=25.2.0` on `PATH` (22.x breaks `caatinga invoke` signing)
18
+ - Rust 1.84.0 or newer with the `wasm32v1-none` target (contract builds)
19
19
  - A funded Stellar CLI identity for `deploy` and `invoke` (for example `alice`)
20
20
 
21
21
  ```bash
22
- rustup target add wasm32-unknown-unknown
22
+ rustup target add wasm32v1-none
23
23
  stellar keys generate alice --fund --network testnet
24
24
  ```
25
25
 
@@ -45,6 +45,7 @@ npx caatinga invoke counter.increment --network testnet --source alice
45
45
  | Command | What it does |
46
46
  | --- | --- |
47
47
  | `caatinga init <projectName>` | Create a project from a bundled template and write `caatinga.artifacts.json` |
48
+ | `caatinga doctor [--network <network>] [--source <identity>]` | Check local Node, Stellar CLI, Rust, config, artifacts, network, and source identity setup |
48
49
  | `caatinga build [contract]` | Compile contract WASM through Stellar CLI (default contract: `counter`) |
49
50
  | `caatinga deploy [contract]` | Deploy one contract or the full configured graph; record IDs in artifacts |
50
51
  | `caatinga generate <contract>` | Generate TypeScript bindings from a deployed contract ID |
@@ -63,6 +64,12 @@ The supported CLI flow is `init -> build -> deploy -> generate -> invoke`.
63
64
  - `[contract]` defaults to `counter` when omitted
64
65
  - `--allow-untested-stellar-cli` allows a Stellar CLI newer than Caatinga's tested maximum (local only)
65
66
 
67
+ ### `doctor`
68
+
69
+ - `-n, --network <network>` validates that the network exists in `caatinga.config.ts`
70
+ - `-s, --source <identity>` validates that the local Stellar CLI identity exists
71
+ - exits `0` when all diagnostics pass and non-zero when a blocking diagnostic fails
72
+
66
73
  ### `deploy`
67
74
 
68
75
  - Omit `[contract]` to deploy the full configured dependency graph
@@ -84,7 +91,7 @@ Dependencies listed in `dependsOn` deploy first unless `--no-deps` is set. Deplo
84
91
 
85
92
  ## Supported inputs
86
93
 
87
- - `--source` accepts a Stellar CLI identity alias that can sign transactions; public `G...` addresses and secret keys are rejected
94
+ - `--source` accepts a local Stellar CLI identity alias that can sign transactions; public `G...` addresses and secret keys are rejected
88
95
  - `--network` must match a network defined in `caatinga.config.ts`
89
96
  - Project commands require `caatinga.config.ts` in the working directory
90
97
 
package/dist/index.js CHANGED
@@ -1,5 +1,34 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/utils/preflight.ts
4
+ import chalk from "chalk";
5
+ var NODE_MIN_MAJOR = 20;
6
+ function checkNodeVersion() {
7
+ const major = parseInt(process.versions.node.split(".")[0] ?? "0", 10);
8
+ if (major < NODE_MIN_MAJOR) {
9
+ return `Node.js ${process.versions.node} is below the required minimum v${NODE_MIN_MAJOR}.
10
+ Install Node.js ${NODE_MIN_MAJOR} or newer: https://nodejs.org/`;
11
+ }
12
+ return null;
13
+ }
14
+ function runPreflight() {
15
+ const failures = [];
16
+ const nodeFailure = checkNodeVersion();
17
+ if (nodeFailure) failures.push(nodeFailure);
18
+ if (failures.length > 0) return { ok: false, failures };
19
+ return { ok: true };
20
+ }
21
+ function assertPreflight() {
22
+ const result = runPreflight();
23
+ if (result.ok) return;
24
+ console.error(chalk.red.bold("\n\u2716 Caatinga preflight check failed\n"));
25
+ for (const failure of result.failures) {
26
+ console.error(chalk.red(` \u2022 ${failure}
27
+ `));
28
+ }
29
+ process.exit(1);
30
+ }
31
+
3
32
  // src/program.ts
4
33
  import { Command } from "commander";
5
34
 
@@ -7,47 +36,54 @@ import { Command } from "commander";
7
36
  import { buildContract, loadConfig } from "@caatinga/core";
8
37
 
9
38
  // src/utils/errors.ts
39
+ import chalk2 from "chalk";
10
40
  import { toCaatingaError } from "@caatinga/core";
41
+ var SEPARATOR = chalk2.gray("\u2500".repeat(50));
42
+ function printError(error) {
43
+ const caatingaError = toCaatingaError(error);
44
+ console.error("");
45
+ console.error(SEPARATOR);
46
+ console.error(chalk2.red.bold(" \u2716 Error"));
47
+ console.error(SEPARATOR);
48
+ console.error(chalk2.red(` ${caatingaError.message}`));
49
+ console.error("");
50
+ console.error(chalk2.gray(` Code: ${caatingaError.code}`));
51
+ if (caatingaError.hint) {
52
+ console.error("");
53
+ console.error(chalk2.yellow(` Hint: ${caatingaError.hint}`));
54
+ }
55
+ console.error(SEPARATOR);
56
+ console.error("");
57
+ }
58
+ async function runCliAction(action) {
59
+ try {
60
+ await action();
61
+ } catch (error) {
62
+ printError(error);
63
+ process.exitCode = 1;
64
+ }
65
+ }
11
66
 
12
67
  // src/utils/logger.ts
13
- import chalk from "chalk";
68
+ import chalk3 from "chalk";
14
69
  var logger = {
15
70
  info(message) {
16
71
  console.log(message);
17
72
  },
18
73
  success(message) {
19
- console.log(chalk.green(message));
74
+ console.log(chalk3.green(message));
20
75
  },
21
76
  warn(message) {
22
- console.warn(chalk.yellow(message));
77
+ console.warn(chalk3.yellow(message));
23
78
  },
24
79
  error(message) {
25
- console.error(chalk.red(message));
80
+ console.error(chalk3.red(message));
26
81
  },
27
82
  muted(message) {
28
- console.log(chalk.gray(message));
83
+ console.log(chalk3.gray(message));
29
84
  }
30
85
  };
31
86
 
32
- // src/utils/errors.ts
33
- function printError(error) {
34
- const caatingaError = toCaatingaError(error);
35
- logger.error(`Error: ${caatingaError.message}`);
36
- logger.info("");
37
- logger.info(`Code: ${caatingaError.code}`);
38
- if (caatingaError.hint) {
39
- logger.info(`Hint: ${caatingaError.hint}`);
40
- }
41
- }
42
- async function runCliAction(action) {
43
- try {
44
- await action();
45
- } catch (error) {
46
- printError(error);
47
- process.exitCode = 1;
48
- }
49
- }
50
-
51
87
  // src/commands/build.command.ts
52
88
  function registerBuildCommand(program2) {
53
89
  program2.command("build").description("Build a configured Soroban contract").argument("[contract]", "Contract name", "counter").option("--allow-untested-stellar-cli", "Allow local use of a Stellar CLI version newer than Caatinga's tested maximum").action((contractName, options) => runCliAction(async () => {
@@ -67,7 +103,7 @@ function registerBuildCommand(program2) {
67
103
  // src/commands/deploy.command.ts
68
104
  import { deployContractGraph, CaatingaError, CaatingaErrorCode, loadConfig as loadConfig2 } from "@caatinga/core";
69
105
  function registerDeployCommand(program2) {
70
- program2.command("deploy").description("Deploy one or all configured Soroban contracts").argument("[contract]", "Contract name").option("-n, --network <network>", "Configured network name").requiredOption("-s, --source <source>", "Stellar CLI identity alias that can sign (for example alice)").option("--force", "Redeploy contracts even if artifacts already contain contract IDs").option("--no-deps", "Do not deploy missing dependencies for a selected contract").option("--allow-untested-stellar-cli", "Allow local use of a Stellar CLI version newer than Caatinga's tested maximum").action((contractName, options) => runCliAction(async () => {
106
+ program2.command("deploy").description("Deploy one or all configured Soroban contracts").argument("[contract]", "Contract name").option("-n, --network <network>", "Configured network name").requiredOption("-s, --source <source>", "Stellar CLI identity alias that can sign (for example alice)").option("--force", "Redeploy contracts even if artifacts already contain contract IDs").option("--no-deps", "Do not deploy missing dependencies for a selected contract").option("--no-stale-check", "Do not warn when WASM may be older than contract sources").option("--allow-untested-stellar-cli", "Allow local use of a Stellar CLI version newer than Caatinga's tested maximum").option("--verify-deps", "Verify dependency contract IDs exist on-chain before deploy").action((contractName, options) => runCliAction(async () => {
71
107
  if (options.deps === false && !contractName) {
72
108
  throw new CaatingaError(
73
109
  "`--no-deps` requires a contract name.",
@@ -83,14 +119,23 @@ function registerDeployCommand(program2) {
83
119
  source: options.source,
84
120
  includeDependencies: options.deps !== false,
85
121
  force: options.force === true,
122
+ checkStaleWasm: options.staleCheck !== false,
123
+ verifyDeps: options.verifyDeps === true,
86
124
  allowUntestedStellarCli: options.allowUntestedStellarCli === true
87
125
  });
126
+ for (const warning of result.staleWasmWarnings) {
127
+ logger.warn(warning.message);
128
+ }
88
129
  logger.success("Deploy complete");
89
130
  logger.info("");
90
131
  logger.info(`Network: ${result.network.name}`);
132
+ for (const skipped of result.skippedContracts) {
133
+ logger.info(`[skipped] ${skipped.name} \u2014 already deployed on ${result.network.name}`);
134
+ logger.info(` Contract ID: ${skipped.contractId}`);
135
+ }
91
136
  for (const contract of result.deployedContracts) {
92
- logger.info(`Contract: ${contract.name}`);
93
- logger.info(`Contract ID: ${contract.contractId}`);
137
+ logger.info(`[deployed] ${contract.name}`);
138
+ logger.info(` Contract ID: ${contract.contractId}`);
94
139
  }
95
140
  logger.info("Artifacts updated: caatinga.artifacts.json");
96
141
  }));
@@ -108,11 +153,248 @@ function registerDevCommand(program2) {
108
153
  dev.configureHelp({ visibleOptions: () => [] });
109
154
  }
110
155
 
156
+ // src/commands/doctor.command.ts
157
+ import { access } from "fs/promises";
158
+ import {
159
+ assertSupportedStellarCliVersion,
160
+ CaatingaError as CaatingaError2,
161
+ CaatingaErrorCode as CaatingaErrorCode2,
162
+ loadConfig as loadConfig4,
163
+ parseStellarCliVersion,
164
+ readArtifacts as readArtifacts2,
165
+ resolveNetwork as resolveNetwork2,
166
+ runCommand,
167
+ validateSourceShape
168
+ } from "@caatinga/core";
169
+
170
+ // src/commands/doctor-deploy-coverage.ts
171
+ import { loadConfig as loadConfig3, readArtifacts, resolveNetwork } from "@caatinga/core";
172
+ async function evaluateDeployCoverage(options) {
173
+ const cwd = options.cwd;
174
+ const config = await loadConfig3({ cwd });
175
+ const network = resolveNetwork(config, options.networkName);
176
+ const artifacts = await readArtifacts(cwd);
177
+ const lines = [];
178
+ for (const name of Object.keys(config.contracts)) {
179
+ const contractId = artifacts.networks[network.name]?.contracts[name]?.contractId;
180
+ if (contractId) {
181
+ lines.push({ name, ok: true, contractId });
182
+ } else {
183
+ lines.push({
184
+ name,
185
+ ok: false,
186
+ fix: `Run: caatinga deploy ${name} --network ${network.name}`
187
+ });
188
+ }
189
+ }
190
+ return { lines, complete: lines.every((line) => line.ok) };
191
+ }
192
+
193
+ // src/commands/doctor.command.ts
194
+ var NODE_MIN_MAJOR2 = 20;
195
+ var WASM_TARGET = "wasm32v1-none";
196
+ function nodeDiagnostic() {
197
+ const version = process.versions.node;
198
+ const major = Number.parseInt(version.split(".")[0] ?? "0", 10);
199
+ if (major < NODE_MIN_MAJOR2) {
200
+ return {
201
+ ok: false,
202
+ label: `Node.js ${version} is below the required minimum ${NODE_MIN_MAJOR2}.0.0`,
203
+ fix: `Install Node.js ${NODE_MIN_MAJOR2} or newer.`
204
+ };
205
+ }
206
+ return { ok: true, label: `Node.js ${version}` };
207
+ }
208
+ async function stellarDiagnostic(allowUntested) {
209
+ try {
210
+ const result = await runCommand("stellar", ["--version"], {
211
+ skipStellarVersionCheck: true
212
+ });
213
+ const version = assertSupportedStellarCliVersion({
214
+ version: parseStellarCliVersion(result.all || result.stdout || result.stderr),
215
+ allowUntested
216
+ });
217
+ return { ok: true, label: `Stellar CLI ${version}` };
218
+ } catch (error) {
219
+ const hint = error instanceof CaatingaError2 ? error.hint : void 0;
220
+ return {
221
+ ok: false,
222
+ label: "Stellar CLI not ready",
223
+ fix: hint ?? "Install Stellar CLI: cargo install --locked stellar-cli --version 25.2.0"
224
+ };
225
+ }
226
+ }
227
+ async function rustDiagnostic() {
228
+ try {
229
+ const result = await runCommand("rustc", ["--version"]);
230
+ return { ok: true, label: result.stdout || result.all || "Rust installed" };
231
+ } catch {
232
+ return {
233
+ ok: false,
234
+ label: "Rust not found",
235
+ fix: "Install Rust, then run: rustup target add wasm32v1-none"
236
+ };
237
+ }
238
+ }
239
+ async function wasmTargetDiagnostic() {
240
+ try {
241
+ const result = await runCommand("rustup", ["target", "list", "--installed"]);
242
+ const installedTargets = result.stdout || result.all;
243
+ if (installedTargets.split(/\r?\n/).includes(WASM_TARGET)) {
244
+ return { ok: true, label: `${WASM_TARGET} target installed` };
245
+ }
246
+ return {
247
+ ok: false,
248
+ label: `${WASM_TARGET} target not installed`,
249
+ fix: `Run: rustup target add ${WASM_TARGET}`
250
+ };
251
+ } catch {
252
+ return {
253
+ ok: false,
254
+ label: "rustup not found",
255
+ fix: "Install rustup, then run: rustup target add wasm32v1-none"
256
+ };
257
+ }
258
+ }
259
+ async function configDiagnostic() {
260
+ try {
261
+ await loadConfig4();
262
+ return { ok: true, label: "caatinga.config.ts found" };
263
+ } catch (error) {
264
+ const hint = error instanceof CaatingaError2 ? error.hint : void 0;
265
+ return {
266
+ ok: false,
267
+ label: "caatinga.config.ts not ready",
268
+ fix: hint ?? "Run this command from a Caatinga project root."
269
+ };
270
+ }
271
+ }
272
+ async function artifactsDiagnostic() {
273
+ try {
274
+ await access("caatinga.artifacts.json");
275
+ await readArtifacts2();
276
+ return { ok: true, label: "caatinga.artifacts.json found" };
277
+ } catch {
278
+ return {
279
+ ok: false,
280
+ label: "caatinga.artifacts.json not found or invalid",
281
+ fix: "Run caatinga init, or restore a valid caatinga.artifacts.json file."
282
+ };
283
+ }
284
+ }
285
+ async function networkDiagnostic(networkName) {
286
+ if (!networkName) return void 0;
287
+ try {
288
+ const config = await loadConfig4();
289
+ const network = resolveNetwork2(config, networkName);
290
+ return { ok: true, label: `network ${network.name} found` };
291
+ } catch (error) {
292
+ const hint = error instanceof CaatingaError2 ? error.hint : void 0;
293
+ return {
294
+ ok: false,
295
+ label: `network ${networkName} not found`,
296
+ fix: hint ?? `Add "${networkName}" to caatinga.config.ts networks.`
297
+ };
298
+ }
299
+ }
300
+ async function sourceDiagnostic(source) {
301
+ if (!source) return void 0;
302
+ const unsafeSource = validateSourceShape(source);
303
+ if (unsafeSource) {
304
+ return {
305
+ ok: false,
306
+ label: `source identity ${source} rejected (${unsafeSource.code})`,
307
+ fix: unsafeSource.hint
308
+ };
309
+ }
310
+ try {
311
+ await runCommand("stellar", ["keys", "public-key", source]);
312
+ return { ok: true, label: `source identity ${source} found` };
313
+ } catch {
314
+ return {
315
+ ok: false,
316
+ label: `source identity ${source} not found`,
317
+ fix: `Create and fund it: stellar keys generate ${source} --fund --network testnet`
318
+ };
319
+ }
320
+ }
321
+ function printDiagnostic(diagnostic) {
322
+ logger.info(`${diagnostic.ok ? "\u2713" : "\u2717"} ${diagnostic.label}`);
323
+ }
324
+ function printFixes(diagnostics) {
325
+ const failures = diagnostics.filter((diagnostic) => !diagnostic.ok);
326
+ if (failures.length === 0) return;
327
+ logger.info("");
328
+ logger.info("Fix:");
329
+ for (const failure of failures) {
330
+ if (failure.fix) logger.info(failure.fix);
331
+ }
332
+ }
333
+ function printDeployCoverageLine(line) {
334
+ if (line.ok) {
335
+ logger.info(`\u2713 ${line.name} \u2014 ${line.contractId}`);
336
+ return;
337
+ }
338
+ logger.info(`\u2717 ${line.name}`);
339
+ if (line.fix) logger.info(` ${line.fix}`);
340
+ }
341
+ async function reportDeployCoverage(networkName) {
342
+ const coverage = await evaluateDeployCoverage({ networkName });
343
+ logger.info("");
344
+ logger.info(`Deploy coverage (${networkName}):`);
345
+ for (const line of coverage.lines) {
346
+ printDeployCoverageLine(line);
347
+ }
348
+ if (!coverage.complete) {
349
+ const missing = coverage.lines.filter((line) => !line.ok).map((line) => line.name);
350
+ throw new CaatingaError2(
351
+ `Not all configured contracts are deployed on ${networkName}.`,
352
+ CaatingaErrorCode2.DOCTOR_PARTIAL_DEPLOY,
353
+ `Deploy missing contracts: ${missing.join(", ")}. See the commands above.`
354
+ );
355
+ }
356
+ return true;
357
+ }
358
+ function registerDoctorCommand(program2) {
359
+ program2.command("doctor").description("Check local Caatinga, Stellar CLI, Rust, config, and source identity setup").option("-n, --network <network>", "Configured network name to validate").option("-s, --source <source>", "Stellar CLI identity alias to validate").option("--allow-untested-stellar-cli", "Allow local use of a Stellar CLI version newer than Caatinga's tested maximum").action((options) => runCliAction(async () => {
360
+ logger.info("Caatinga Doctor");
361
+ logger.info("");
362
+ const diagnostics = [
363
+ nodeDiagnostic(),
364
+ await stellarDiagnostic(options.allowUntestedStellarCli === true),
365
+ await rustDiagnostic(),
366
+ await wasmTargetDiagnostic(),
367
+ await configDiagnostic(),
368
+ await artifactsDiagnostic(),
369
+ await networkDiagnostic(options.network),
370
+ await sourceDiagnostic(options.source)
371
+ ].filter((diagnostic) => diagnostic !== void 0);
372
+ for (const diagnostic of diagnostics) {
373
+ printDiagnostic(diagnostic);
374
+ }
375
+ printFixes(diagnostics);
376
+ let ready = diagnostics.every((diagnostic) => diagnostic.ok);
377
+ if (options.network && ready) {
378
+ try {
379
+ await reportDeployCoverage(options.network);
380
+ } catch (error) {
381
+ ready = false;
382
+ throw error;
383
+ }
384
+ }
385
+ logger.info("");
386
+ logger.info(`Status: ${ready ? "ready" : "blocked"}`);
387
+ if (!ready) {
388
+ process.exitCode = 1;
389
+ }
390
+ }));
391
+ }
392
+
111
393
  // src/commands/generate.command.ts
112
- import { generateBindings, loadConfig as loadConfig3 } from "@caatinga/core";
394
+ import { generateBindings, loadConfig as loadConfig5 } from "@caatinga/core";
113
395
  function registerGenerateCommand(program2) {
114
396
  program2.command("generate").description("Generate TypeScript bindings for a deployed contract").argument("<contract>", "Contract name").option("-n, --network <network>", "Configured network name").option("--allow-untested-stellar-cli", "Allow local use of a Stellar CLI version newer than Caatinga's tested maximum").action((contractName, options) => runCliAction(async () => {
115
- const config = await loadConfig3();
397
+ const config = await loadConfig5();
116
398
  const result = await generateBindings({
117
399
  config,
118
400
  contractName,
@@ -132,10 +414,10 @@ import path2 from "path";
132
414
  import { createProjectFromTemplate } from "@caatinga/core";
133
415
 
134
416
  // src/utils/template-path.ts
135
- import { access } from "fs/promises";
417
+ import { access as access2 } from "fs/promises";
136
418
  import path from "path";
137
419
  import { fileURLToPath } from "url";
138
- import { CaatingaError as CaatingaError2, CaatingaErrorCode as CaatingaErrorCode2 } from "@caatinga/core";
420
+ import { CaatingaError as CaatingaError3, CaatingaErrorCode as CaatingaErrorCode3 } from "@caatinga/core";
139
421
  async function resolveTemplateDir(templateName) {
140
422
  const envTemplatesDir = process.env.CAATINGA_TEMPLATES_DIR;
141
423
  const candidates = [
@@ -145,14 +427,14 @@ async function resolveTemplateDir(templateName) {
145
427
  ].filter((candidate) => Boolean(candidate));
146
428
  for (const candidate of candidates) {
147
429
  try {
148
- await access(candidate);
430
+ await access2(candidate);
149
431
  return candidate;
150
432
  } catch {
151
433
  }
152
434
  }
153
- throw new CaatingaError2(
435
+ throw new CaatingaError3(
154
436
  `Template "${templateName}" was not found.`,
155
- CaatingaErrorCode2.TEMPLATE_NOT_FOUND,
437
+ CaatingaErrorCode3.TEMPLATE_NOT_FOUND,
156
438
  "Set CAATINGA_TEMPLATES_DIR or run from a Caatinga checkout that includes packages/templates."
157
439
  );
158
440
  }
@@ -191,15 +473,18 @@ function registerInitCommand(program2) {
191
473
  logger.info("Next steps:");
192
474
  logger.info(` cd ${projectDirectory}`);
193
475
  logger.info(" npm install");
194
- logger.info(" npx caatinga build counter");
476
+ const defaultContract = result.template.contracts.default;
477
+ logger.info(
478
+ defaultContract ? ` npx caatinga build ${defaultContract}` : " npx caatinga build"
479
+ );
195
480
  }));
196
481
  }
197
482
 
198
483
  // src/commands/invoke.command.ts
199
- import { invokeContract, loadConfig as loadConfig4 } from "@caatinga/core";
484
+ import { invokeContract, loadConfig as loadConfig6 } from "@caatinga/core";
200
485
  function registerInvokeCommand(program2) {
201
486
  program2.command("invoke").description("Invoke a deployed contract function").argument("<target>", "Invoke target in contract.method format").argument("[args...]", "Arguments forwarded to Stellar CLI after the method name").option("-n, --network <network>", "Configured network name").requiredOption("-s, --source <source>", "Stellar CLI identity alias that can sign (for example alice)").option("--allow-untested-stellar-cli", "Allow local use of a Stellar CLI version newer than Caatinga's tested maximum").allowUnknownOption(true).allowExcessArguments(true).action((target, args, options) => runCliAction(async () => {
202
- const config = await loadConfig4();
487
+ const config = await loadConfig6();
203
488
  const result = await invokeContract({
204
489
  config,
205
490
  target,
@@ -221,7 +506,7 @@ function registerInvokeCommand(program2) {
221
506
  }
222
507
 
223
508
  // src/version.ts
224
- var CAATINGA_CLI_VERSION = "0.2.1";
509
+ var CAATINGA_CLI_VERSION = "0.2.3";
225
510
 
226
511
  // src/program.ts
227
512
  function createProgram() {
@@ -229,6 +514,7 @@ function createProgram() {
229
514
  program2.name("caatinga").description("Developer toolkit for Stellar/Soroban dApps").version(CAATINGA_CLI_VERSION);
230
515
  registerInitCommand(program2);
231
516
  registerDevCommand(program2);
517
+ registerDoctorCommand(program2);
232
518
  registerBuildCommand(program2);
233
519
  registerDeployCommand(program2);
234
520
  registerGenerateCommand(program2);
@@ -237,6 +523,7 @@ function createProgram() {
237
523
  }
238
524
 
239
525
  // src/index.ts
526
+ assertPreflight();
240
527
  var program = createProgram();
241
528
  void program.parseAsync(process.argv).catch((error) => {
242
529
  console.error(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caatinga/cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Caatinga CLI for building dApps on Stellar/Soroban",
5
5
  "keywords": [
6
6
  "stellar",
@@ -43,9 +43,9 @@
43
43
  "LICENSE"
44
44
  ],
45
45
  "dependencies": {
46
+ "@caatinga/core": "^0.2.3",
46
47
  "chalk": "^5.4.1",
47
- "commander": "^12.1.0",
48
- "@caatinga/core": "^0.2.1"
48
+ "commander": "^12.1.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "tsup": "^8.3.5",
@@ -1,19 +0,0 @@
1
- # __PROJECT_NAME__
2
-
3
- Experimental Caatinga multi-contract template.
4
-
5
- ## Deploy
6
-
7
- ```bash
8
- npm install
9
- npx caatinga build token
10
- npx caatinga build marketplace
11
- npx caatinga deploy --network testnet --source alice
12
- ```
13
-
14
- Deploy order:
15
-
16
- 1. `token`
17
- 2. `marketplace`
18
-
19
- `marketplace.deployArgs.tokenContractId` resolves from `${contracts.token.contractId}` after the token deploy writes `caatinga.artifacts.json`.
@@ -1,10 +0,0 @@
1
- {
2
- "project": "__PROJECT_NAME__",
3
- "version": 1,
4
- "networks": {
5
- "testnet": {
6
- "contracts": {},
7
- "dependencyGraph": {}
8
- }
9
- }
10
- }
@@ -1,30 +0,0 @@
1
- import { defineConfig } from "@caatinga/core";
2
-
3
- export default defineConfig({
4
- project: "__PROJECT_NAME__",
5
- defaultNetwork: "testnet",
6
- contracts: {
7
- token: {
8
- path: "./contracts/token",
9
- wasm: "./contracts/token/target/wasm32-unknown-unknown/release/token.wasm"
10
- },
11
- marketplace: {
12
- path: "./contracts/marketplace",
13
- wasm: "./contracts/marketplace/target/wasm32-unknown-unknown/release/marketplace.wasm",
14
- dependsOn: ["token"],
15
- deployArgs: {
16
- tokenContractId: "${contracts.token.contractId}"
17
- }
18
- }
19
- },
20
- networks: {
21
- testnet: {
22
- rpcUrl: "https://soroban-testnet.stellar.org",
23
- networkPassphrase: "Test SDF Network ; September 2015"
24
- }
25
- },
26
- frontend: {
27
- framework: "vite-react",
28
- bindingsOutput: "./src/contracts/generated"
29
- }
30
- });
@@ -1,21 +0,0 @@
1
- {
2
- "name": "marketplace-with-token",
3
- "version": "0.1.0",
4
- "description": "Experimental multi-contract Soroban template with token dependency injection.",
5
- "caatinga": {
6
- "compatibleCore": "^0.2.1",
7
- "templateVersion": 1
8
- },
9
- "frontend": {
10
- "framework": "vite-react",
11
- "packageManager": "npm"
12
- },
13
- "contracts": {
14
- "path": "contracts",
15
- "default": "marketplace"
16
- },
17
- "files": {
18
- "config": "caatinga.config.ts",
19
- "artifacts": "caatinga.artifacts.json"
20
- }
21
- }
@@ -1,23 +0,0 @@
1
- [package]
2
- name = "marketplace"
3
- version = "0.1.0"
4
- edition = "2021"
5
-
6
- [lib]
7
- crate-type = ["cdylib"]
8
-
9
- [dependencies]
10
- soroban-sdk = "22.0.1"
11
-
12
- [dev-dependencies]
13
- soroban-sdk = { version = "22.0.1", features = ["testutils"] }
14
-
15
- [profile.release]
16
- opt-level = "z"
17
- overflow-checks = true
18
- debug = 0
19
- strip = "symbols"
20
- debug-assertions = false
21
- panic = "abort"
22
- codegen-units = 1
23
- lto = true
@@ -1,13 +0,0 @@
1
- #![no_std]
2
-
3
- use soroban_sdk::{contract, contractimpl, Env};
4
-
5
- #[contract]
6
- pub struct MarketplaceContract;
7
-
8
- #[contractimpl]
9
- impl MarketplaceContract {
10
- pub fn version(_env: Env) -> u32 {
11
- 1
12
- }
13
- }
@@ -1,23 +0,0 @@
1
- [package]
2
- name = "token"
3
- version = "0.1.0"
4
- edition = "2021"
5
-
6
- [lib]
7
- crate-type = ["cdylib"]
8
-
9
- [dependencies]
10
- soroban-sdk = "22.0.1"
11
-
12
- [dev-dependencies]
13
- soroban-sdk = { version = "22.0.1", features = ["testutils"] }
14
-
15
- [profile.release]
16
- opt-level = "z"
17
- overflow-checks = true
18
- debug = 0
19
- strip = "symbols"
20
- debug-assertions = false
21
- panic = "abort"
22
- codegen-units = 1
23
- lto = true
@@ -1,13 +0,0 @@
1
- #![no_std]
2
-
3
- use soroban_sdk::{contract, contractimpl, Env};
4
-
5
- #[contract]
6
- pub struct TokenContract;
7
-
8
- #[contractimpl]
9
- impl TokenContract {
10
- pub fn version(_env: Env) -> u32 {
11
- 1
12
- }
13
- }
@@ -1,17 +0,0 @@
1
- {
2
- "name": "__PROJECT_NAME__",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "caatinga:build": "caatinga build token && caatinga build marketplace",
8
- "caatinga:deploy": "caatinga deploy --network testnet"
9
- },
10
- "dependencies": {
11
- "@caatinga/core": "^0.2.1"
12
- },
13
- "devDependencies": {
14
- "@caatinga/cli": "^0.2.1",
15
- "typescript": "^5.7.2"
16
- }
17
- }
@@ -1,10 +0,0 @@
1
- export const templateId = "marketplace-with-token";
2
-
3
- export function describeDeployFlow(): string {
4
- return [
5
- "1. caatinga build token",
6
- "2. caatinga build marketplace",
7
- "3. caatinga deploy --network testnet --source <identity>",
8
- "marketplace.deployArgs.tokenContractId resolves from caatinga.artifacts.json"
9
- ].join("\n");
10
- }
@@ -1,11 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "ESNext",
5
- "moduleResolution": "Bundler",
6
- "strict": true,
7
- "skipLibCheck": true,
8
- "noEmit": true
9
- },
10
- "include": ["src", "caatinga.config.ts"]
11
- }
@@ -1,58 +0,0 @@
1
- # __PROJECT_NAME__
2
-
3
- Caatinga counter dApp for Stellar/Soroban.
4
-
5
- ## CLI Flow
6
-
7
- ```bash
8
- npm install
9
- npx caatinga build counter
10
- npx caatinga deploy counter --network testnet --source alice
11
- npx caatinga generate counter --network testnet
12
- npx caatinga invoke counter.increment --network testnet --source alice
13
- npm run dev
14
- ```
15
-
16
- Use a Stellar CLI identity alias or public account address for `--source`; do not pass seed phrases or secret keys.
17
-
18
- ## Client Smoke Path
19
-
20
- After `caatinga generate`, wire generated bindings to the client:
21
-
22
- ```ts
23
- import { createCaatingaClient } from "@caatinga/client";
24
- import { freighterWalletAdapter } from "@caatinga/client/freighter";
25
- import * as Counter from "./contracts/generated/counter";
26
- import artifacts from "../caatinga.artifacts.json";
27
-
28
- export const caatingaClient = createCaatingaClient({
29
- network: {
30
- name: "testnet",
31
- rpcUrl: "https://soroban-testnet.stellar.org",
32
- networkPassphrase: "Test SDF Network ; September 2015"
33
- },
34
- artifacts,
35
- wallet: freighterWalletAdapter,
36
- contracts: {
37
- counter: {
38
- binding: Counter
39
- }
40
- }
41
- });
42
- ```
43
-
44
- Build XDR without wallet signing:
45
-
46
- ```ts
47
- const tx = await caatingaClient.contract("counter").buildXdr("increment");
48
- console.log(tx.preparedXdr);
49
- ```
50
-
51
- Invoke through Freighter:
52
-
53
- ```ts
54
- const result = await caatingaClient.contract("counter").invoke("increment", {
55
- debugXdr: true
56
- });
57
- console.log(result.transactionHash);
58
- ```
@@ -1,26 +0,0 @@
1
- import { defineConfig } from "@caatinga/core";
2
-
3
- export default defineConfig({
4
- project: "__PROJECT_NAME__",
5
- defaultNetwork: "testnet",
6
- contracts: {
7
- counter: {
8
- path: "./contracts/counter",
9
- wasm: "./contracts/counter/target/wasm32-unknown-unknown/release/counter.wasm"
10
- }
11
- },
12
- networks: {
13
- testnet: {
14
- rpcUrl: "https://soroban-testnet.stellar.org",
15
- networkPassphrase: "Test SDF Network ; September 2015"
16
- },
17
- mainnet: {
18
- rpcUrl: "https://mainnet.sorobanrpc.com",
19
- networkPassphrase: "Public Global Stellar Network ; September 2015"
20
- }
21
- },
22
- frontend: {
23
- framework: "vite-react",
24
- bindingsOutput: "./src/contracts/generated"
25
- }
26
- });
@@ -1,21 +0,0 @@
1
- {
2
- "name": "react-vite-counter",
3
- "version": "0.1.0",
4
- "description": "Minimal Vite + React + Soroban counter dApp.",
5
- "caatinga": {
6
- "compatibleCore": "^0.2.1",
7
- "templateVersion": 1
8
- },
9
- "frontend": {
10
- "framework": "vite-react",
11
- "packageManager": "npm"
12
- },
13
- "contracts": {
14
- "path": "contracts",
15
- "default": "counter"
16
- },
17
- "files": {
18
- "config": "caatinga.config.ts",
19
- "artifacts": "caatinga.artifacts.json"
20
- }
21
- }
@@ -1,23 +0,0 @@
1
- [package]
2
- name = "counter"
3
- version = "0.1.0"
4
- edition = "2021"
5
-
6
- [lib]
7
- crate-type = ["cdylib"]
8
-
9
- [dependencies]
10
- soroban-sdk = "22.0.1"
11
-
12
- [dev-dependencies]
13
- soroban-sdk = { version = "22.0.1", features = ["testutils"] }
14
-
15
- [profile.release]
16
- opt-level = "z"
17
- overflow-checks = true
18
- debug = 0
19
- strip = "symbols"
20
- debug-assertions = false
21
- panic = "abort"
22
- codegen-units = 1
23
- lto = true
@@ -1,36 +0,0 @@
1
- #![no_std]
2
-
3
- use soroban_sdk::{contract, contractimpl, Env};
4
-
5
- #[contract]
6
- pub struct CounterContract;
7
-
8
- #[contractimpl]
9
- impl CounterContract {
10
- pub fn get(env: Env) -> u32 {
11
- env.storage().instance().get(&"count").unwrap_or(0)
12
- }
13
-
14
- pub fn increment(env: Env) -> u32 {
15
- let count = Self::get(env.clone()) + 1;
16
- env.storage().instance().set(&"count", &count);
17
- count
18
- }
19
- }
20
-
21
- #[cfg(test)]
22
- mod test {
23
- use super::*;
24
- use soroban_sdk::Env;
25
-
26
- #[test]
27
- fn increments_counter() {
28
- let env = Env::default();
29
- let contract_id = env.register(CounterContract, ());
30
- let client = CounterContractClient::new(&env, &contract_id);
31
-
32
- assert_eq!(client.get(), 0);
33
- assert_eq!(client.increment(), 1);
34
- assert_eq!(client.get(), 1);
35
- }
36
- }
@@ -1,12 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>__PROJECT_NAME__</title>
7
- </head>
8
- <body>
9
- <div id="root"></div>
10
- <script type="module" src="/src/main.tsx"></script>
11
- </body>
12
- </html>
@@ -1,29 +0,0 @@
1
- {
2
- "name": "__PROJECT_NAME__",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "dev": "vite",
8
- "build": "tsc && vite build",
9
- "preview": "vite preview",
10
- "caatinga:build": "caatinga build counter",
11
- "caatinga:deploy": "caatinga deploy counter",
12
- "caatinga:generate": "caatinga generate counter"
13
- },
14
- "dependencies": {
15
- "@caatinga/client": "^0.2.1",
16
- "@caatinga/core": "^0.2.1",
17
- "@stellar/freighter-api": "^4.0.0",
18
- "@vitejs/plugin-react": "^4.3.4",
19
- "vite": "^6.0.6",
20
- "react": "^18.3.1",
21
- "react-dom": "^18.3.1"
22
- },
23
- "devDependencies": {
24
- "@caatinga/cli": "^0.2.1",
25
- "@types/react": "^18.3.18",
26
- "@types/react-dom": "^18.3.5",
27
- "typescript": "^5.7.2"
28
- }
29
- }
@@ -1,18 +0,0 @@
1
- import { CounterCard } from "./components/CounterCard";
2
- import { WalletButton } from "./components/WalletButton";
3
-
4
- export default function App() {
5
- return (
6
- <main className="app-shell">
7
- <header className="topbar">
8
- <div>
9
- <p className="eyebrow">Caatinga</p>
10
- <h1>__PROJECT_NAME__</h1>
11
- </div>
12
- <WalletButton />
13
- </header>
14
-
15
- <CounterCard />
16
- </main>
17
- );
18
- }
@@ -1,29 +0,0 @@
1
- import { useMemo, useState } from "react";
2
-
3
- export function CounterCard() {
4
- const [count, setCount] = useState(0);
5
- const formattedCount = useMemo(() => new Intl.NumberFormat().format(count), [count]);
6
-
7
- return (
8
- <section className="counter-panel" aria-labelledby="counter-title">
9
- <div className="counter-panel__header">
10
- <div>
11
- <p className="eyebrow">Counter Contract</p>
12
- <h2 id="counter-title">Counter</h2>
13
- </div>
14
- <span className="network-pill">testnet</span>
15
- </div>
16
-
17
- <div className="counter-value">{formattedCount}</div>
18
-
19
- <div className="counter-actions">
20
- <button type="button" onClick={() => setCount((value) => value + 1)}>
21
- Increment
22
- </button>
23
- <button className="secondary-button" type="button" onClick={() => setCount(0)}>
24
- Reset
25
- </button>
26
- </div>
27
- </section>
28
- );
29
- }
@@ -1,12 +0,0 @@
1
- import { useState } from "react";
2
-
3
- export function WalletButton() {
4
- const [connected, setConnected] = useState(false);
5
-
6
- return (
7
- <button className="wallet-button" type="button" onClick={() => setConnected((value) => !value)}>
8
- <span className={connected ? "status-dot status-dot--on" : "status-dot"} />
9
- {connected ? "Connected" : "Connect"}
10
- </button>
11
- );
12
- }
@@ -1,10 +0,0 @@
1
- import React from "react";
2
- import ReactDOM from "react-dom/client";
3
- import App from "./App";
4
- import "./styles.css";
5
-
6
- ReactDOM.createRoot(document.getElementById("root")!).render(
7
- <React.StrictMode>
8
- <App />
9
- </React.StrictMode>
10
- );
@@ -1,155 +0,0 @@
1
- :root {
2
- color: #20232a;
3
- background: #f4f2ec;
4
- font-family:
5
- Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
6
- font-synthesis: none;
7
- text-rendering: optimizeLegibility;
8
- }
9
-
10
- * {
11
- box-sizing: border-box;
12
- }
13
-
14
- body {
15
- min-width: 320px;
16
- min-height: 100vh;
17
- margin: 0;
18
- }
19
-
20
- button {
21
- min-height: 44px;
22
- border: 0;
23
- border-radius: 8px;
24
- padding: 0 16px;
25
- background: #1d6154;
26
- color: #ffffff;
27
- font: inherit;
28
- font-weight: 700;
29
- cursor: pointer;
30
- }
31
-
32
- button:hover {
33
- background: #174d44;
34
- }
35
-
36
- .app-shell {
37
- width: min(960px, calc(100vw - 32px));
38
- margin: 0 auto;
39
- padding: 24px 0;
40
- }
41
-
42
- .topbar {
43
- display: flex;
44
- align-items: center;
45
- justify-content: space-between;
46
- gap: 16px;
47
- margin-bottom: 32px;
48
- }
49
-
50
- .topbar h1,
51
- .counter-panel h2 {
52
- margin: 0;
53
- color: #16181d;
54
- letter-spacing: 0;
55
- }
56
-
57
- .topbar h1 {
58
- font-size: clamp(2rem, 8vw, 4rem);
59
- line-height: 1;
60
- }
61
-
62
- .counter-panel h2 {
63
- font-size: 1.35rem;
64
- }
65
-
66
- .eyebrow {
67
- margin: 0 0 8px;
68
- color: #697076;
69
- font-size: 0.78rem;
70
- font-weight: 800;
71
- letter-spacing: 0.08em;
72
- text-transform: uppercase;
73
- }
74
-
75
- .wallet-button,
76
- .network-pill {
77
- display: inline-flex;
78
- align-items: center;
79
- gap: 8px;
80
- white-space: nowrap;
81
- }
82
-
83
- .status-dot {
84
- width: 8px;
85
- height: 8px;
86
- border-radius: 999px;
87
- background: #a9afb4;
88
- }
89
-
90
- .status-dot--on {
91
- background: #66c887;
92
- }
93
-
94
- .counter-panel {
95
- border: 1px solid #d9d5ca;
96
- border-radius: 8px;
97
- padding: clamp(20px, 5vw, 40px);
98
- background: #fffdf7;
99
- box-shadow: 0 18px 60px rgba(32, 35, 42, 0.08);
100
- }
101
-
102
- .counter-panel__header {
103
- display: flex;
104
- align-items: flex-start;
105
- justify-content: space-between;
106
- gap: 16px;
107
- }
108
-
109
- .network-pill {
110
- min-height: 32px;
111
- border: 1px solid #d9d5ca;
112
- border-radius: 999px;
113
- padding: 0 12px;
114
- color: #45515a;
115
- font-weight: 700;
116
- }
117
-
118
- .counter-value {
119
- margin: 48px 0;
120
- color: #111318;
121
- font-size: clamp(5rem, 24vw, 12rem);
122
- font-weight: 900;
123
- line-height: 0.9;
124
- letter-spacing: 0;
125
- }
126
-
127
- .counter-actions {
128
- display: flex;
129
- flex-wrap: wrap;
130
- gap: 12px;
131
- }
132
-
133
- .secondary-button {
134
- border: 1px solid #d9d5ca;
135
- background: #ffffff;
136
- color: #20232a;
137
- }
138
-
139
- .secondary-button:hover {
140
- background: #f4f2ec;
141
- }
142
-
143
- @media (max-width: 560px) {
144
- .topbar,
145
- .counter-panel__header {
146
- align-items: stretch;
147
- flex-direction: column;
148
- }
149
-
150
- .wallet-button,
151
- .counter-actions button {
152
- width: 100%;
153
- justify-content: center;
154
- }
155
- }
@@ -1,21 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "useDefineForClassFields": true,
5
- "lib": ["DOM", "DOM.Iterable", "ES2020"],
6
- "allowJs": false,
7
- "skipLibCheck": true,
8
- "esModuleInterop": true,
9
- "allowSyntheticDefaultImports": true,
10
- "strict": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "module": "ESNext",
13
- "moduleResolution": "Node",
14
- "resolveJsonModule": true,
15
- "isolatedModules": true,
16
- "noEmit": true,
17
- "jsx": "react-jsx"
18
- },
19
- "include": ["src"],
20
- "references": []
21
- }
@@ -1,6 +0,0 @@
1
- import { defineConfig } from "vite";
2
- import react from "@vitejs/plugin-react";
3
-
4
- export default defineConfig({
5
- plugins: [react()]
6
- });