@caatinga/core 2.3.1 → 2.4.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
@@ -1,6 +1,7 @@
1
1
  // src/errors/CaatingaErrorCode.ts
2
2
  var CaatingaErrorCode = {
3
3
  CONFIG_NOT_FOUND: "CAATINGA_CONFIG_NOT_FOUND",
4
+ DEPENDENCIES_NOT_INSTALLED: "CAATINGA_DEPENDENCIES_NOT_INSTALLED",
4
5
  INVALID_CONFIG: "CAATINGA_INVALID_CONFIG",
5
6
  COMMAND_FAILED: "CAATINGA_COMMAND_FAILED",
6
7
  UNEXPECTED_ERROR: "CAATINGA_UNEXPECTED_ERROR",
@@ -105,7 +106,7 @@ function formatCause(cause) {
105
106
  }
106
107
 
107
108
  // src/version.ts
108
- var CAATINGA_CORE_VERSION = "2.3.1";
109
+ var CAATINGA_CORE_VERSION = "2.4.0";
109
110
 
110
111
  // src/config/config.schema.ts
111
112
  import { z } from "zod";
@@ -157,6 +158,28 @@ import { access } from "fs/promises";
157
158
  import path from "path";
158
159
  import { createJiti } from "jiti";
159
160
  import { z as z2 } from "zod";
161
+
162
+ // src/config/is-dependencies-not-installed-error.ts
163
+ function isDependenciesNotInstalledError(error) {
164
+ if (!error || typeof error !== "object") {
165
+ return false;
166
+ }
167
+ const candidate = error;
168
+ const code = candidate.code;
169
+ const message = String(candidate.message ?? error);
170
+ if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
171
+ return /@caatinga\/core/.test(message);
172
+ }
173
+ if (/Cannot find (module|package).+@caatinga\/core/i.test(message)) {
174
+ return true;
175
+ }
176
+ if (candidate.cause !== void 0) {
177
+ return isDependenciesNotInstalledError(candidate.cause);
178
+ }
179
+ return false;
180
+ }
181
+
182
+ // src/config/load-config.ts
160
183
  async function loadConfig(options = {}) {
161
184
  const cwd = options.cwd ?? process.cwd();
162
185
  const configPath = path.resolve(cwd, options.configPath ?? "caatinga.config.ts");
@@ -181,6 +204,14 @@ async function loadConfig(options = {}) {
181
204
  error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
182
205
  );
183
206
  }
207
+ if (isDependenciesNotInstalledError(error)) {
208
+ throw new CaatingaError(
209
+ "Project dependencies are not installed.",
210
+ CaatingaErrorCode.DEPENDENCIES_NOT_INSTALLED,
211
+ "Run npm install (or pnpm install) in the project root, then retry.",
212
+ error
213
+ );
214
+ }
184
215
  throw error;
185
216
  }
186
217
  }
@@ -587,6 +618,64 @@ function defaultEmitWarning(warning) {
587
618
  `);
588
619
  }
589
620
 
621
+ // src/shell/resolve-subprocess-env.ts
622
+ import { existsSync } from "fs";
623
+ import os from "os";
624
+ import path6 from "path";
625
+ function uniquePaths(entries) {
626
+ const seen = /* @__PURE__ */ new Set();
627
+ const ordered = [];
628
+ for (const entry of entries) {
629
+ if (!entry || seen.has(entry)) {
630
+ continue;
631
+ }
632
+ seen.add(entry);
633
+ ordered.push(entry);
634
+ }
635
+ return ordered;
636
+ }
637
+ function hasExecutable(binDir, name) {
638
+ return existsSync(path6.join(binDir, name));
639
+ }
640
+ function toolchainBinDirs(home, env) {
641
+ const candidates = [
642
+ path6.join(home, ".cargo", "bin"),
643
+ env.CARGO_HOME ? path6.join(env.CARGO_HOME, "bin") : void 0
644
+ ];
645
+ return candidates.filter((entry) => Boolean(entry && existsSync(entry)));
646
+ }
647
+ function buildToolchainPrepend(existingPath, toolchainBins, executableExists = hasExecutable) {
648
+ const prepend = [];
649
+ for (const binDir of toolchainBins) {
650
+ const externalStellarDir = existingPath.find(
651
+ (entry) => entry !== binDir && executableExists(entry, "stellar")
652
+ );
653
+ if (externalStellarDir && executableExists(binDir, "stellar")) {
654
+ prepend.push(externalStellarDir);
655
+ }
656
+ prepend.push(binDir);
657
+ }
658
+ return prepend;
659
+ }
660
+ function resolveSubprocessEnv(overrides = {}) {
661
+ const env = { ...process.env, ...overrides };
662
+ const home = env.HOME ?? os.homedir();
663
+ const existingPath = (env.PATH ?? "").split(path6.delimiter).filter(Boolean);
664
+ const toolchainBins = toolchainBinDirs(home, env);
665
+ const prepend = buildToolchainPrepend(existingPath, toolchainBins);
666
+ env.PATH = uniquePaths([...prepend, ...existingPath]).join(path6.delimiter);
667
+ return env;
668
+ }
669
+ function isCargoBinMissingFromPath(baseEnv = process.env) {
670
+ const home = baseEnv.HOME ?? os.homedir();
671
+ const cargoBin = path6.join(home, ".cargo", "bin", "cargo");
672
+ if (!existsSync(cargoBin)) {
673
+ return false;
674
+ }
675
+ const pathEntries = (baseEnv.PATH ?? "").split(path6.delimiter);
676
+ return !pathEntries.some((entry) => entry === path6.join(home, ".cargo", "bin"));
677
+ }
678
+
590
679
  // src/shell/run-command.ts
591
680
  async function runCommand(command, args, options = {}) {
592
681
  try {
@@ -595,7 +684,7 @@ async function runCommand(command, args, options = {}) {
595
684
  }
596
685
  const result = await execa(command, args, {
597
686
  cwd: options.cwd,
598
- env: options.env,
687
+ env: resolveSubprocessEnv(options.env ?? {}),
599
688
  input: options.input,
600
689
  all: true,
601
690
  reject: true
@@ -793,7 +882,7 @@ function validateSourceShape(source) {
793
882
  }
794
883
 
795
884
  // src/contracts/resolve-contract.ts
796
- import path6 from "path";
885
+ import path7 from "path";
797
886
  function resolveContract(config, contractName, cwd = process.cwd()) {
798
887
  const contract = config.contracts[contractName];
799
888
  if (!contract) {
@@ -806,8 +895,8 @@ function resolveContract(config, contractName, cwd = process.cwd()) {
806
895
  return {
807
896
  name: contractName,
808
897
  config: contract,
809
- sourcePath: path6.resolve(cwd, contract.path),
810
- wasmPath: path6.resolve(cwd, contract.wasm)
898
+ sourcePath: path7.resolve(cwd, contract.path),
899
+ wasmPath: path7.resolve(cwd, contract.wasm)
811
900
  };
812
901
  }
813
902
 
@@ -827,7 +916,7 @@ function resolveDefaultContractName(config) {
827
916
  // src/contracts/wasm.ts
828
917
  import { createHash } from "crypto";
829
918
  import { access as access2, readdir as readdir2, readFile as readFile3, stat } from "fs/promises";
830
- import path7 from "path";
919
+ import path8 from "path";
831
920
  var LEGACY_RUST_WASM_TARGET = "wasm32-unknown-unknown";
832
921
  var CURRENT_RUST_WASM_TARGET = "wasm32v1-none";
833
922
  function toCurrentWasmTargetPath(wasmPath) {
@@ -850,8 +939,8 @@ function wasmNotFoundError(configuredWasmPath, options) {
850
939
  );
851
940
  }
852
941
  function toConfigRelativeWasmPath(absoluteWasmPath) {
853
- const relative = path7.relative(process.cwd(), absoluteWasmPath);
854
- return relative.startsWith("..") ? absoluteWasmPath : `./${relative.split(path7.sep).join("/")}`;
942
+ const relative = path8.relative(process.cwd(), absoluteWasmPath);
943
+ return relative.startsWith("..") ? absoluteWasmPath : `./${relative.split(path8.sep).join("/")}`;
855
944
  }
856
945
  async function resolveWasmArtifactPath(configuredWasmPath) {
857
946
  try {
@@ -884,7 +973,7 @@ async function getNewestMtimeInDirectory(directory) {
884
973
  async function walk(dir) {
885
974
  const entries = await readdir2(dir, { withFileTypes: true });
886
975
  for (const entry of entries) {
887
- const entryPath = path7.join(dir, entry.name);
976
+ const entryPath = path8.join(dir, entry.name);
888
977
  if (entry.isDirectory()) {
889
978
  await walk(entryPath);
890
979
  continue;
@@ -900,7 +989,7 @@ async function getNewestMtimeInDirectory(directory) {
900
989
  return newest > 0 ? newest : void 0;
901
990
  }
902
991
  async function isWasmOlderThanSources(input) {
903
- const srcDir = path7.join(input.contractPath, "src");
992
+ const srcDir = path8.join(input.contractPath, "src");
904
993
  const newestSourceMtime = await getNewestMtimeInDirectory(srcDir);
905
994
  if (newestSourceMtime === void 0) {
906
995
  return false;
@@ -973,7 +1062,7 @@ async function buildContract(options) {
973
1062
  }
974
1063
 
975
1064
  // src/contracts/deploy-contract.ts
976
- import path8 from "path";
1065
+ import path9 from "path";
977
1066
 
978
1067
  // src/contracts/dependency-graph.ts
979
1068
  function buildDependencyGraph(contracts) {
@@ -1030,6 +1119,9 @@ function assertSafeSourceAccount(source) {
1030
1119
  }
1031
1120
  return source;
1032
1121
  }
1122
+ function resolveCliSource(explicit) {
1123
+ return assertSafeSourceAccount(explicit ?? process.env.CAATINGA_SOURCE ?? "alice");
1124
+ }
1033
1125
 
1034
1126
  // src/contracts/deploy-contract.ts
1035
1127
  function toSnakeCaseFlag(key) {
@@ -1074,7 +1166,7 @@ async function deployContract(options) {
1074
1166
  contract: contractWithWasm,
1075
1167
  network,
1076
1168
  contractId: existing.contractId,
1077
- artifactsPath: path8.resolve(cwd, "caatinga.artifacts.json"),
1169
+ artifactsPath: path9.resolve(cwd, "caatinga.artifacts.json"),
1078
1170
  output: "",
1079
1171
  skipped: true,
1080
1172
  staleWasmWarning
@@ -1351,13 +1443,13 @@ async function deployContractGraph(options) {
1351
1443
 
1352
1444
  // src/contracts/generate-bindings.ts
1353
1445
  import { access as access3, mkdir as mkdir2, unlink } from "fs/promises";
1354
- import path9 from "path";
1446
+ import path10 from "path";
1355
1447
  function toBindingImportPath(bindingsOutput, contractName) {
1356
- const normalized = bindingsOutput.replace(/^\.\//, "").split(path9.sep).join("/");
1357
- return `./${path9.posix.join(normalized, contractName, "src", "index.js")}`;
1448
+ const normalized = bindingsOutput.replace(/^\.\//, "").split(path10.sep).join("/");
1449
+ return `./${path10.posix.join(normalized, contractName, "src", "index.js")}`;
1358
1450
  }
1359
1451
  async function removeLegacyBindingStub(cwd, bindingsOutput, contractName) {
1360
- const legacyPath = path9.resolve(cwd, bindingsOutput, `${contractName}.ts`);
1452
+ const legacyPath = path10.resolve(cwd, bindingsOutput, `${contractName}.ts`);
1361
1453
  try {
1362
1454
  await access3(legacyPath);
1363
1455
  await unlink(legacyPath);
@@ -1386,7 +1478,7 @@ async function generateBindings(options) {
1386
1478
  );
1387
1479
  }
1388
1480
  await checkBinary("stellar", "Install Stellar CLI before running caatinga generate.");
1389
- const outputDir = path9.resolve(cwd, options.config.frontend.bindingsOutput, options.contractName);
1481
+ const outputDir = path10.resolve(cwd, options.config.frontend.bindingsOutput, options.contractName);
1390
1482
  await mkdir2(outputDir, { recursive: true });
1391
1483
  const result = await runCommand("stellar", [
1392
1484
  "contract",
@@ -1460,8 +1552,8 @@ async function generateBindingsGraph(options) {
1460
1552
  return { network, results };
1461
1553
  }
1462
1554
 
1463
- // src/contracts/invoke-contract.ts
1464
- var INVOKE_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
1555
+ // src/contracts/invoke-target.ts
1556
+ var READ_CALL_FAILURE_REGEX = /this is a read call|read-only/i;
1465
1557
  function parseInvokeTarget(target) {
1466
1558
  const [contractName, method, extra] = target.split(".");
1467
1559
  if (!contractName || !method || extra) {
@@ -1473,6 +1565,29 @@ function parseInvokeTarget(target) {
1473
1565
  }
1474
1566
  return { contractName, method };
1475
1567
  }
1568
+ function buildReadCallHint(target, networkName) {
1569
+ return [
1570
+ `"${target.contractName}.${target.method}" is a read-only contract method.`,
1571
+ "Simulate without signing instead:",
1572
+ ` npx caatinga read ${target.contractName}.${target.method} --network ${networkName}`,
1573
+ " (--source is optional; Caatinga resolves CAATINGA_SOURCE or defaults to alice)",
1574
+ "In browser code, use:",
1575
+ ` client.contract("${target.contractName}").read("${target.method}")`,
1576
+ ` client.contract("${target.contractName}").simulate("${target.method}")`,
1577
+ "Pass method args as the second argument to read() when the contract method takes parameters.",
1578
+ "Only pass Stellar CLI --force when you intentionally need a signed read simulation."
1579
+ ].join("\n");
1580
+ }
1581
+ function isReadCallFailure(error) {
1582
+ if (!(error instanceof CaatingaError) || error.code !== CaatingaErrorCode.INVOKE_FAILED) {
1583
+ return false;
1584
+ }
1585
+ return READ_CALL_FAILURE_REGEX.test(`${error.message}
1586
+ ${error.hint ?? ""}`);
1587
+ }
1588
+
1589
+ // src/contracts/invoke-contract.ts
1590
+ var INVOKE_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
1476
1591
  async function invokeContract(options) {
1477
1592
  const cwd = options.cwd ?? process.cwd();
1478
1593
  const network = resolveNetwork(options.config, options.networkName);
@@ -1506,6 +1621,14 @@ async function invokeContract(options) {
1506
1621
  failureCode: CaatingaErrorCode.INVOKE_FAILED
1507
1622
  });
1508
1623
  } catch (error) {
1624
+ if (error instanceof CaatingaError && error.code === CaatingaErrorCode.INVOKE_FAILED && isReadCallFailure(error)) {
1625
+ throw new CaatingaError(
1626
+ error.message,
1627
+ error.code,
1628
+ buildReadCallHint(target, network.name),
1629
+ error
1630
+ );
1631
+ }
1509
1632
  if (error instanceof CaatingaError && error.code === CaatingaErrorCode.INVOKE_FAILED && INVOKE_SIGNING_FAILURE_REGEX.test(`${error.message}
1510
1633
  ${error.hint ?? ""}`)) {
1511
1634
  throw new CaatingaError(
@@ -1531,9 +1654,48 @@ ${error.hint ?? ""}`)) {
1531
1654
  };
1532
1655
  }
1533
1656
 
1657
+ // src/contracts/read-contract.ts
1658
+ async function readContract(options) {
1659
+ const cwd = options.cwd ?? process.cwd();
1660
+ const network = resolveNetwork(options.config, options.networkName);
1661
+ const target = parseInvokeTarget(options.target);
1662
+ const artifacts = await readArtifacts(cwd);
1663
+ const contractArtifact = artifacts.networks[network.name]?.contracts[target.contractName];
1664
+ if (!contractArtifact) {
1665
+ throw new CaatingaError(
1666
+ `No deployed artifact found for "${target.contractName}" on "${network.name}".`,
1667
+ CaatingaErrorCode.ARTIFACT_NOT_FOUND,
1668
+ "Run caatinga deploy for this contract and network before reading it."
1669
+ );
1670
+ }
1671
+ await checkBinary("stellar", "Install Stellar CLI before running caatinga read.");
1672
+ const stellarArgs = [
1673
+ "contract",
1674
+ "invoke",
1675
+ "--id",
1676
+ contractArtifact.contractId,
1677
+ "--source-account",
1678
+ resolveCliSource(options.source),
1679
+ "--send=no",
1680
+ ...buildStellarNetworkArgs(network),
1681
+ "--",
1682
+ target.method,
1683
+ ...options.args ?? []
1684
+ ];
1685
+ const result = await runCommand("stellar", stellarArgs, {
1686
+ cwd,
1687
+ failureCode: CaatingaErrorCode.INVOKE_FAILED
1688
+ });
1689
+ return {
1690
+ target,
1691
+ network,
1692
+ result: result.stdout || result.all
1693
+ };
1694
+ }
1695
+
1534
1696
  // src/templates/create-project-from-template.ts
1535
1697
  import { cp, mkdir as mkdir3, readFile as readFile4, readdir as readdir3, stat as stat2, writeFile as writeFile3 } from "fs/promises";
1536
- import path10 from "path";
1698
+ import path11 from "path";
1537
1699
  import { z as z7 } from "zod";
1538
1700
 
1539
1701
  // src/templates/template-manifest.schema.ts
@@ -1609,8 +1771,8 @@ var TEMPLATE_COPY_EXCLUDED_DIRS = /* @__PURE__ */ new Set([
1609
1771
  ".git"
1610
1772
  ]);
1611
1773
  async function createProjectFromTemplate(options) {
1612
- const targetDir = path10.resolve(options.targetDir);
1613
- const templateDir = path10.resolve(options.templateDir);
1774
+ const targetDir = path11.resolve(options.targetDir);
1775
+ const templateDir = path11.resolve(options.templateDir);
1614
1776
  try {
1615
1777
  await stat2(templateDir);
1616
1778
  } catch {
@@ -1648,7 +1810,7 @@ async function ensureArtifacts(targetDir, projectName) {
1648
1810
  }
1649
1811
  }
1650
1812
  async function readTemplateManifest(templateDir) {
1651
- const manifestPath = path10.join(templateDir, "caatinga.template.json");
1813
+ const manifestPath = path11.join(templateDir, "caatinga.template.json");
1652
1814
  try {
1653
1815
  const rawManifest = await readFile4(manifestPath, "utf8");
1654
1816
  const manifest = TemplateManifestSchema.parse(JSON.parse(rawManifest));
@@ -1685,7 +1847,7 @@ async function readTemplateManifest(templateDir) {
1685
1847
  async function replaceTemplateVariables(dir, projectName) {
1686
1848
  const entries = await readdir3(dir);
1687
1849
  await Promise.all(entries.map(async (entry) => {
1688
- const entryPath = path10.join(dir, entry);
1850
+ const entryPath = path11.join(dir, entry);
1689
1851
  const entryStat = await stat2(entryPath);
1690
1852
  if (entryStat.isDirectory()) {
1691
1853
  await replaceTemplateVariables(entryPath, projectName);
@@ -1699,15 +1861,15 @@ async function replaceTemplateVariables(dir, projectName) {
1699
1861
  }));
1700
1862
  }
1701
1863
  function shouldCopyTemplateEntry(templateDir, source, userFilter) {
1702
- const relativePath = path10.relative(templateDir, source);
1864
+ const relativePath = path11.relative(templateDir, source);
1703
1865
  if (!relativePath || relativePath === ".") {
1704
1866
  return true;
1705
1867
  }
1706
- const normalizedPath = relativePath.split(path10.sep).join("/");
1868
+ const normalizedPath = relativePath.split(path11.sep).join("/");
1707
1869
  if (userFilter && !userFilter(normalizedPath)) {
1708
1870
  return false;
1709
1871
  }
1710
- return !relativePath.split(path10.sep).some((segment) => TEMPLATE_COPY_EXCLUDED_DIRS.has(segment));
1872
+ return !relativePath.split(path11.sep).some((segment) => TEMPLATE_COPY_EXCLUDED_DIRS.has(segment));
1711
1873
  }
1712
1874
  function isTextTemplateFile(filePath) {
1713
1875
  return [
@@ -1719,21 +1881,21 @@ function isTextTemplateFile(filePath) {
1719
1881
  ".tsx",
1720
1882
  ".css",
1721
1883
  ".html"
1722
- ].includes(path10.extname(filePath));
1884
+ ].includes(path11.extname(filePath));
1723
1885
  }
1724
1886
 
1725
1887
  // src/scaffold/create-zk-project.ts
1726
1888
  import { cp as cp2, mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
1727
- import { existsSync } from "fs";
1728
- import path11 from "path";
1889
+ import { existsSync as existsSync2 } from "fs";
1890
+ import path12 from "path";
1729
1891
  import { fileURLToPath } from "url";
1730
- var moduleDir = typeof __dirname === "string" ? __dirname : path11.dirname(fileURLToPath(import.meta.url));
1892
+ var moduleDir = typeof __dirname === "string" ? __dirname : path12.dirname(fileURLToPath(import.meta.url));
1731
1893
  function scaffoldRoot() {
1732
1894
  const candidates = [
1733
- path11.resolve(moduleDir, "../../scaffolds"),
1734
- path11.resolve(moduleDir, "../scaffolds")
1895
+ path12.resolve(moduleDir, "../../scaffolds"),
1896
+ path12.resolve(moduleDir, "../scaffolds")
1735
1897
  ];
1736
- const found = candidates.find((candidate) => existsSync(candidate));
1898
+ const found = candidates.find((candidate) => existsSync2(candidate));
1737
1899
  return found ?? candidates[0];
1738
1900
  }
1739
1901
  function configSource(projectName) {
@@ -1805,25 +1967,25 @@ Replace \`circuits/main.circom\` with your circuit. Keep the entry point named \
1805
1967
  `;
1806
1968
  }
1807
1969
  async function createZkProject(options) {
1808
- const targetDir = path11.resolve(options.targetDir);
1970
+ const targetDir = path12.resolve(options.targetDir);
1809
1971
  const force = options.force ?? false;
1810
1972
  const projectFiles = options.projectFiles ?? true;
1811
1973
  await mkdir4(targetDir, { recursive: true });
1812
1974
  if (projectFiles) {
1813
1975
  await Promise.all([
1814
- writeFile4(path11.join(targetDir, "caatinga.config.ts"), configSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
1815
- writeFile4(path11.join(targetDir, "package.json"), packageJsonSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
1816
- writeFile4(path11.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", { encoding: "utf8", flag: force ? "w" : "wx" }),
1817
- writeFile4(path11.join(targetDir, "README.md"), readmeSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" })
1976
+ writeFile4(path12.join(targetDir, "caatinga.config.ts"), configSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
1977
+ writeFile4(path12.join(targetDir, "package.json"), packageJsonSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
1978
+ writeFile4(path12.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", { encoding: "utf8", flag: force ? "w" : "wx" }),
1979
+ writeFile4(path12.join(targetDir, "README.md"), readmeSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" })
1818
1980
  ]);
1819
1981
  }
1820
- await mkdir4(path11.join(targetDir, "contracts"), { recursive: true });
1821
- await cp2(path11.join(scaffoldRoot(), "zk-circuit-stub"), path11.join(targetDir, "circuits"), {
1982
+ await mkdir4(path12.join(targetDir, "contracts"), { recursive: true });
1983
+ await cp2(path12.join(scaffoldRoot(), "zk-circuit-stub"), path12.join(targetDir, "circuits"), {
1822
1984
  recursive: true,
1823
1985
  force,
1824
1986
  errorOnExist: !force
1825
1987
  });
1826
- await cp2(path11.join(scaffoldRoot(), "zk-verifier"), path11.join(targetDir, "contracts", "verifier"), {
1988
+ await cp2(path12.join(scaffoldRoot(), "zk-verifier"), path12.join(targetDir, "contracts", "verifier"), {
1827
1989
  recursive: true,
1828
1990
  force,
1829
1991
  errorOnExist: !force
@@ -1834,6 +1996,109 @@ async function createZkProject(options) {
1834
1996
  return { targetDir };
1835
1997
  }
1836
1998
 
1999
+ // src/scaffold/create-minimal-project.ts
2000
+ import { cp as cp3, mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
2001
+ import { existsSync as existsSync3 } from "fs";
2002
+ import path13 from "path";
2003
+ import { fileURLToPath as fileURLToPath2 } from "url";
2004
+ var moduleDir2 = typeof __dirname === "string" ? __dirname : path13.dirname(fileURLToPath2(import.meta.url));
2005
+ function scaffoldRoot2() {
2006
+ const candidates = [
2007
+ path13.resolve(moduleDir2, "../../scaffolds"),
2008
+ path13.resolve(moduleDir2, "../scaffolds")
2009
+ ];
2010
+ const found = candidates.find((candidate) => existsSync3(candidate));
2011
+ return found ?? candidates[0];
2012
+ }
2013
+ function configSource2(projectName) {
2014
+ return `import { defineConfig } from "@caatinga/core";
2015
+
2016
+ export default defineConfig({
2017
+ project: "${projectName}",
2018
+ defaultNetwork: "testnet",
2019
+ contracts: {
2020
+ app: {
2021
+ path: "./contracts/app",
2022
+ wasm: "./contracts/app/target/wasm32v1-none/release/app.wasm"
2023
+ }
2024
+ },
2025
+ networks: {
2026
+ testnet: {
2027
+ rpcUrl: "https://soroban-testnet.stellar.org",
2028
+ networkPassphrase: "Test SDF Network ; September 2015"
2029
+ }
2030
+ }
2031
+ });
2032
+ `;
2033
+ }
2034
+ function packageJsonSource2(projectName) {
2035
+ return `${JSON.stringify({
2036
+ name: projectName,
2037
+ version: "0.1.0",
2038
+ private: true,
2039
+ type: "module",
2040
+ scripts: {
2041
+ build: "caatinga build app",
2042
+ deploy: "caatinga deploy app --network testnet --source ${CAATINGA_SOURCE:-alice}",
2043
+ doctor: "caatinga doctor",
2044
+ "read:hello": "caatinga read app.hello --network testnet --source ${CAATINGA_SOURCE:-alice}",
2045
+ "read:version": "caatinga read app.version --network testnet --source ${CAATINGA_SOURCE:-alice}"
2046
+ },
2047
+ devDependencies: {
2048
+ "@caatinga/cli": `^${CAATINGA_CORE_VERSION}`,
2049
+ "@caatinga/core": `^${CAATINGA_CORE_VERSION}`
2050
+ }
2051
+ }, null, 2)}
2052
+ `;
2053
+ }
2054
+ function readmeSource2(projectName) {
2055
+ return `# ${projectName}
2056
+
2057
+ Minimal Caatinga project with a Soroban contract stub (no frontend template).
2058
+
2059
+ ## Workflow
2060
+
2061
+ \`\`\`bash
2062
+ npm install
2063
+ npx caatinga doctor
2064
+ npx caatinga build app
2065
+ npx caatinga deploy app --network testnet --source <identity>
2066
+ npx caatinga read app.version --network testnet
2067
+ npx caatinga read app.hello --network testnet
2068
+ \`\`\`
2069
+
2070
+ ## Contract
2071
+
2072
+ - \`hello()\` \u2014 read-only; returns Soroban Symbol \`hello\`
2073
+ - \`version()\` \u2014 read-only; returns \`1\`
2074
+
2075
+ Use \`caatinga read\` for read-only methods. Use \`caatinga invoke\` only after you add state-changing methods to the contract.
2076
+
2077
+ Soroban \`Symbol\` parameters are generated as TypeScript \`string\` values with host-specific restrictions \u2014 see the Caatinga docs on [Soroban types](https://github.com/caatinga/caatinga/blob/main/docs/soroban-types.md).
2078
+
2079
+ Edit \`contracts/app/src/lib.rs\` to customize the contract. Add a frontend later with \`@caatinga/client\` and your chosen UI stack.
2080
+ `;
2081
+ }
2082
+ async function createMinimalProject(options) {
2083
+ const targetDir = path13.resolve(options.targetDir);
2084
+ const force = options.force ?? false;
2085
+ await mkdir5(targetDir, { recursive: true });
2086
+ await Promise.all([
2087
+ writeFile5(path13.join(targetDir, "caatinga.config.ts"), configSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2088
+ writeFile5(path13.join(targetDir, "package.json"), packageJsonSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2089
+ writeFile5(path13.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", { encoding: "utf8", flag: force ? "w" : "wx" }),
2090
+ writeFile5(path13.join(targetDir, "README.md"), readmeSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" })
2091
+ ]);
2092
+ await mkdir5(path13.join(targetDir, "contracts"), { recursive: true });
2093
+ await cp3(path13.join(scaffoldRoot2(), "soroban-contract-stub"), path13.join(targetDir, "contracts", "app"), {
2094
+ recursive: true,
2095
+ force,
2096
+ errorOnExist: !force
2097
+ });
2098
+ await writeArtifacts(createInitialArtifacts(options.projectName, { networks: ["testnet"] }), targetDir);
2099
+ return { targetDir };
2100
+ }
2101
+
1837
2102
  // src/ci/is-transient-testnet-smoke-failure.ts
1838
2103
  var NO_RETRY_CAATINGA_SUBSTRINGS = [
1839
2104
  "CAATINGA_UNSUPPORTED_CLI_VERSION",
@@ -1862,16 +2127,19 @@ export {
1862
2127
  CaatingaConfigSchema,
1863
2128
  CaatingaError,
1864
2129
  CaatingaErrorCode,
2130
+ READ_CALL_FAILURE_REGEX,
1865
2131
  STELLAR_CLI_LAST_TESTED_VERSION,
1866
2132
  STELLAR_CLI_MIN_VERSION,
1867
2133
  TemplateManifestSchema,
1868
2134
  WELL_KNOWN_NETWORKS,
1869
2135
  buildContract,
1870
2136
  buildDependencyGraph,
2137
+ buildReadCallHint,
1871
2138
  checkBinary,
1872
2139
  checkStellarCliVersion,
1873
2140
  collectProjectStatus,
1874
2141
  createInitialArtifacts,
2142
+ createMinimalProject,
1875
2143
  createProjectFromTemplate,
1876
2144
  createZkProject,
1877
2145
  defineConfig,
@@ -1884,6 +2152,8 @@ export {
1884
2152
  generateBindings,
1885
2153
  generateBindingsGraph,
1886
2154
  invokeContract,
2155
+ isCargoBinMissingFromPath,
2156
+ isReadCallFailure,
1887
2157
  isTransientTestnetSmokeFailure,
1888
2158
  loadConfig,
1889
2159
  parseContractId,
@@ -1891,11 +2161,13 @@ export {
1891
2161
  parseStellarCliVersion,
1892
2162
  readArtifacts,
1893
2163
  readBindingMarker,
2164
+ readContract,
1894
2165
  resolveContract,
1895
2166
  resolveDefaultContractName,
1896
2167
  resolveDeployArgs,
1897
2168
  resolveDeployOrder,
1898
2169
  resolveNetwork,
2170
+ resolveSubprocessEnv,
1899
2171
  runCommand,
1900
2172
  toCaatingaError,
1901
2173
  updateArtifact,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caatinga/core",
3
- "version": "2.3.1",
3
+ "version": "2.4.0",
4
4
  "description": "Core config, artifacts, command orchestration, and error primitives for Caatinga/Soroban toolkit",
5
5
  "keywords": [
6
6
  "stellar",
@@ -0,0 +1,24 @@
1
+ [package]
2
+ name = "app"
3
+ version = "0.1.0"
4
+ rust-version = "1.84.0"
5
+ edition = "2021"
6
+
7
+ [lib]
8
+ crate-type = ["cdylib"]
9
+
10
+ [dependencies]
11
+ soroban-sdk = "22.0.1"
12
+
13
+ [dev-dependencies]
14
+ soroban-sdk = { version = "22.0.1", features = ["testutils"] }
15
+
16
+ [profile.release]
17
+ opt-level = "z"
18
+ overflow-checks = true
19
+ debug = 0
20
+ strip = "symbols"
21
+ debug-assertions = false
22
+ panic = "abort"
23
+ codegen-units = 1
24
+ lto = true
@@ -0,0 +1,42 @@
1
+ #![no_std]
2
+
3
+ use soroban_sdk::{contract, contractimpl, Env, Symbol};
4
+
5
+ #[contract]
6
+ pub struct AppContract;
7
+
8
+ #[contractimpl]
9
+ impl AppContract {
10
+ pub fn hello(env: Env) -> Symbol {
11
+ Symbol::new(&env, "hello")
12
+ }
13
+
14
+ pub fn version(env: Env) -> u32 {
15
+ let _ = env;
16
+ 1
17
+ }
18
+ }
19
+
20
+ #[cfg(test)]
21
+ mod test {
22
+ use super::*;
23
+ use soroban_sdk::Env;
24
+
25
+ #[test]
26
+ fn returns_hello_symbol() {
27
+ let env = Env::default();
28
+ let contract_id = env.register(AppContract, ());
29
+ let client = AppContractClient::new(&env, &contract_id);
30
+
31
+ assert_eq!(client.hello(), Symbol::new(&env, "hello"));
32
+ }
33
+
34
+ #[test]
35
+ fn returns_version() {
36
+ let env = Env::default();
37
+ let contract_id = env.register(AppContract, ());
38
+ let client = AppContractClient::new(&env, &contract_id);
39
+
40
+ assert_eq!(client.version(), 1);
41
+ }
42
+ }