@caatinga/core 2.3.1 → 2.4.1

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.1";
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) {
@@ -838,36 +927,88 @@ function toCurrentWasmTargetPath(wasmPath) {
838
927
  }
839
928
  function wasmNotFoundError(configuredWasmPath, options) {
840
929
  const migratedPath = options?.migratedPath;
841
- const hint = migratedPath === void 0 ? "Run caatinga build before deploy or generate." : [
842
- "Run caatinga build before deploy or generate.",
843
- `Soroban builds use the "${CURRENT_RUST_WASM_TARGET}" target.`,
844
- `Update wasm in caatinga.config.ts to "${toConfigRelativeWasmPath(migratedPath)}" or an equivalent path under target/${CURRENT_RUST_WASM_TARGET}/release/.`
845
- ].join(" ");
930
+ const cargoTargetDir = process.env.CARGO_TARGET_DIR;
931
+ const hintParts = ["Run caatinga build before deploy or generate."];
932
+ if (migratedPath !== void 0) {
933
+ hintParts.push(
934
+ `Soroban builds use the "${CURRENT_RUST_WASM_TARGET}" target.`,
935
+ `Update wasm in caatinga.config.ts to "${toConfigRelativeWasmPath(migratedPath)}" or an equivalent path under target/${CURRENT_RUST_WASM_TARGET}/release/.`
936
+ );
937
+ }
938
+ if (cargoTargetDir) {
939
+ hintParts.push(
940
+ `CARGO_TARGET_DIR is set to "${cargoTargetDir}"; the WASM may be under that directory instead of the configured path. Unset CARGO_TARGET_DIR or update wasm in caatinga.config.ts.`
941
+ );
942
+ }
846
943
  return new CaatingaError(
847
944
  `WASM output was not found at ${configuredWasmPath}.`,
848
945
  CaatingaErrorCode.ARTIFACT_NOT_FOUND,
849
- hint
946
+ hintParts.join(" ")
850
947
  );
851
948
  }
852
949
  function toConfigRelativeWasmPath(absoluteWasmPath) {
853
- const relative = path7.relative(process.cwd(), absoluteWasmPath);
854
- return relative.startsWith("..") ? absoluteWasmPath : `./${relative.split(path7.sep).join("/")}`;
950
+ const relative = path8.relative(process.cwd(), absoluteWasmPath);
951
+ return relative.startsWith("..") ? absoluteWasmPath : `./${relative.split(path8.sep).join("/")}`;
952
+ }
953
+ function wasmFileName(configuredWasmPath) {
954
+ return path8.basename(configuredWasmPath);
955
+ }
956
+ function buildAlternateWasmCandidates(configuredWasmPath, options) {
957
+ const fileName = wasmFileName(configuredWasmPath);
958
+ const candidates = [];
959
+ const seen = /* @__PURE__ */ new Set();
960
+ function addCandidate(candidate) {
961
+ const resolved = path8.resolve(candidate);
962
+ if (seen.has(resolved)) {
963
+ return;
964
+ }
965
+ seen.add(resolved);
966
+ candidates.push(resolved);
967
+ }
968
+ const cargoTargetDir = process.env.CARGO_TARGET_DIR;
969
+ if (cargoTargetDir) {
970
+ addCandidate(path8.join(cargoTargetDir, CURRENT_RUST_WASM_TARGET, "release", fileName));
971
+ addCandidate(path8.join(cargoTargetDir, LEGACY_RUST_WASM_TARGET, "release", fileName));
972
+ }
973
+ if (options?.sourcePath) {
974
+ addCandidate(path8.join(options.sourcePath, "target", CURRENT_RUST_WASM_TARGET, "release", fileName));
975
+ addCandidate(path8.join(options.sourcePath, "target", LEGACY_RUST_WASM_TARGET, "release", fileName));
976
+ }
977
+ return candidates;
855
978
  }
856
- async function resolveWasmArtifactPath(configuredWasmPath) {
979
+ async function firstExistingPath(paths) {
980
+ for (const candidate of paths) {
981
+ try {
982
+ await access2(candidate);
983
+ return candidate;
984
+ } catch {
985
+ continue;
986
+ }
987
+ }
988
+ return void 0;
989
+ }
990
+ async function resolveWasmArtifactPath(configuredWasmPath, options) {
991
+ const resolvedConfiguredPath = path8.resolve(configuredWasmPath);
857
992
  try {
858
- await access2(configuredWasmPath);
859
- return configuredWasmPath;
993
+ await access2(resolvedConfiguredPath);
994
+ return resolvedConfiguredPath;
860
995
  } catch {
861
- const currentTargetPath = toCurrentWasmTargetPath(configuredWasmPath);
862
- if (currentTargetPath === configuredWasmPath) {
863
- throw wasmNotFoundError(configuredWasmPath);
996
+ const currentTargetPath = toCurrentWasmTargetPath(resolvedConfiguredPath);
997
+ if (currentTargetPath !== resolvedConfiguredPath) {
998
+ const migratedPath = await firstExistingPath([currentTargetPath]);
999
+ if (migratedPath) {
1000
+ return migratedPath;
1001
+ }
864
1002
  }
865
- try {
866
- await access2(currentTargetPath);
867
- return currentTargetPath;
868
- } catch {
869
- throw wasmNotFoundError(configuredWasmPath, { migratedPath: currentTargetPath });
1003
+ const alternatePath = await firstExistingPath(
1004
+ buildAlternateWasmCandidates(resolvedConfiguredPath, options)
1005
+ );
1006
+ if (alternatePath) {
1007
+ return alternatePath;
870
1008
  }
1009
+ throw wasmNotFoundError(resolvedConfiguredPath, {
1010
+ migratedPath: currentTargetPath !== resolvedConfiguredPath ? currentTargetPath : void 0
1011
+ });
871
1012
  }
872
1013
  }
873
1014
  async function hashWasm(wasmPath) {
@@ -884,7 +1025,7 @@ async function getNewestMtimeInDirectory(directory) {
884
1025
  async function walk(dir) {
885
1026
  const entries = await readdir2(dir, { withFileTypes: true });
886
1027
  for (const entry of entries) {
887
- const entryPath = path7.join(dir, entry.name);
1028
+ const entryPath = path8.join(dir, entry.name);
888
1029
  if (entry.isDirectory()) {
889
1030
  await walk(entryPath);
890
1031
  continue;
@@ -900,7 +1041,7 @@ async function getNewestMtimeInDirectory(directory) {
900
1041
  return newest > 0 ? newest : void 0;
901
1042
  }
902
1043
  async function isWasmOlderThanSources(input) {
903
- const srcDir = path7.join(input.contractPath, "src");
1044
+ const srcDir = path8.join(input.contractPath, "src");
904
1045
  const newestSourceMtime = await getNewestMtimeInDirectory(srcDir);
905
1046
  if (newestSourceMtime === void 0) {
906
1047
  return false;
@@ -962,7 +1103,9 @@ async function buildContract(options) {
962
1103
  }
963
1104
  throw error;
964
1105
  }
965
- const wasmPath = await resolveWasmArtifactPath(contract.wasmPath);
1106
+ const wasmPath = await resolveWasmArtifactPath(contract.wasmPath, {
1107
+ sourcePath: contract.sourcePath
1108
+ });
966
1109
  return {
967
1110
  contract: {
968
1111
  ...contract,
@@ -973,7 +1116,7 @@ async function buildContract(options) {
973
1116
  }
974
1117
 
975
1118
  // src/contracts/deploy-contract.ts
976
- import path8 from "path";
1119
+ import path9 from "path";
977
1120
 
978
1121
  // src/contracts/dependency-graph.ts
979
1122
  function buildDependencyGraph(contracts) {
@@ -1030,6 +1173,9 @@ function assertSafeSourceAccount(source) {
1030
1173
  }
1031
1174
  return source;
1032
1175
  }
1176
+ function resolveCliSource(explicit) {
1177
+ return assertSafeSourceAccount(explicit ?? process.env.CAATINGA_SOURCE ?? "alice");
1178
+ }
1033
1179
 
1034
1180
  // src/contracts/deploy-contract.ts
1035
1181
  function toSnakeCaseFlag(key) {
@@ -1052,7 +1198,9 @@ async function deployContract(options) {
1052
1198
  const network = resolveNetwork(options.config, options.networkName);
1053
1199
  const source = assertSafeSourceAccount(options.source);
1054
1200
  await checkBinary("stellar", "Install Stellar CLI before running caatinga deploy.");
1055
- const wasmPath = await resolveWasmArtifactPath(contract.wasmPath);
1201
+ const wasmPath = await resolveWasmArtifactPath(contract.wasmPath, {
1202
+ sourcePath: contract.sourcePath
1203
+ });
1056
1204
  const contractWithWasm = {
1057
1205
  ...contract,
1058
1206
  wasmPath
@@ -1074,7 +1222,7 @@ async function deployContract(options) {
1074
1222
  contract: contractWithWasm,
1075
1223
  network,
1076
1224
  contractId: existing.contractId,
1077
- artifactsPath: path8.resolve(cwd, "caatinga.artifacts.json"),
1225
+ artifactsPath: path9.resolve(cwd, "caatinga.artifacts.json"),
1078
1226
  output: "",
1079
1227
  skipped: true,
1080
1228
  staleWasmWarning
@@ -1351,13 +1499,13 @@ async function deployContractGraph(options) {
1351
1499
 
1352
1500
  // src/contracts/generate-bindings.ts
1353
1501
  import { access as access3, mkdir as mkdir2, unlink } from "fs/promises";
1354
- import path9 from "path";
1502
+ import path10 from "path";
1355
1503
  function toBindingImportPath(bindingsOutput, contractName) {
1356
- const normalized = bindingsOutput.replace(/^\.\//, "").split(path9.sep).join("/");
1357
- return `./${path9.posix.join(normalized, contractName, "src", "index.js")}`;
1504
+ const normalized = bindingsOutput.replace(/^\.\//, "").split(path10.sep).join("/");
1505
+ return `./${path10.posix.join(normalized, contractName, "src", "index.js")}`;
1358
1506
  }
1359
1507
  async function removeLegacyBindingStub(cwd, bindingsOutput, contractName) {
1360
- const legacyPath = path9.resolve(cwd, bindingsOutput, `${contractName}.ts`);
1508
+ const legacyPath = path10.resolve(cwd, bindingsOutput, `${contractName}.ts`);
1361
1509
  try {
1362
1510
  await access3(legacyPath);
1363
1511
  await unlink(legacyPath);
@@ -1386,7 +1534,7 @@ async function generateBindings(options) {
1386
1534
  );
1387
1535
  }
1388
1536
  await checkBinary("stellar", "Install Stellar CLI before running caatinga generate.");
1389
- const outputDir = path9.resolve(cwd, options.config.frontend.bindingsOutput, options.contractName);
1537
+ const outputDir = path10.resolve(cwd, options.config.frontend.bindingsOutput, options.contractName);
1390
1538
  await mkdir2(outputDir, { recursive: true });
1391
1539
  const result = await runCommand("stellar", [
1392
1540
  "contract",
@@ -1460,8 +1608,8 @@ async function generateBindingsGraph(options) {
1460
1608
  return { network, results };
1461
1609
  }
1462
1610
 
1463
- // src/contracts/invoke-contract.ts
1464
- var INVOKE_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
1611
+ // src/contracts/invoke-target.ts
1612
+ var READ_CALL_FAILURE_REGEX = /this is a read call|read-only/i;
1465
1613
  function parseInvokeTarget(target) {
1466
1614
  const [contractName, method, extra] = target.split(".");
1467
1615
  if (!contractName || !method || extra) {
@@ -1473,6 +1621,29 @@ function parseInvokeTarget(target) {
1473
1621
  }
1474
1622
  return { contractName, method };
1475
1623
  }
1624
+ function buildReadCallHint(target, networkName) {
1625
+ return [
1626
+ `"${target.contractName}.${target.method}" is a read-only contract method.`,
1627
+ "Simulate without signing instead:",
1628
+ ` npx caatinga read ${target.contractName}.${target.method} --network ${networkName}`,
1629
+ " (--source is optional; Caatinga resolves CAATINGA_SOURCE or defaults to alice)",
1630
+ "In browser code, use:",
1631
+ ` client.contract("${target.contractName}").read("${target.method}")`,
1632
+ ` client.contract("${target.contractName}").simulate("${target.method}")`,
1633
+ "Pass method args as the second argument to read() when the contract method takes parameters.",
1634
+ "Only pass Stellar CLI --force when you intentionally need a signed read simulation."
1635
+ ].join("\n");
1636
+ }
1637
+ function isReadCallFailure(error) {
1638
+ if (!(error instanceof CaatingaError) || error.code !== CaatingaErrorCode.INVOKE_FAILED) {
1639
+ return false;
1640
+ }
1641
+ return READ_CALL_FAILURE_REGEX.test(`${error.message}
1642
+ ${error.hint ?? ""}`);
1643
+ }
1644
+
1645
+ // src/contracts/invoke-contract.ts
1646
+ var INVOKE_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
1476
1647
  async function invokeContract(options) {
1477
1648
  const cwd = options.cwd ?? process.cwd();
1478
1649
  const network = resolveNetwork(options.config, options.networkName);
@@ -1506,6 +1677,14 @@ async function invokeContract(options) {
1506
1677
  failureCode: CaatingaErrorCode.INVOKE_FAILED
1507
1678
  });
1508
1679
  } catch (error) {
1680
+ if (error instanceof CaatingaError && error.code === CaatingaErrorCode.INVOKE_FAILED && isReadCallFailure(error)) {
1681
+ throw new CaatingaError(
1682
+ error.message,
1683
+ error.code,
1684
+ buildReadCallHint(target, network.name),
1685
+ error
1686
+ );
1687
+ }
1509
1688
  if (error instanceof CaatingaError && error.code === CaatingaErrorCode.INVOKE_FAILED && INVOKE_SIGNING_FAILURE_REGEX.test(`${error.message}
1510
1689
  ${error.hint ?? ""}`)) {
1511
1690
  throw new CaatingaError(
@@ -1531,9 +1710,48 @@ ${error.hint ?? ""}`)) {
1531
1710
  };
1532
1711
  }
1533
1712
 
1713
+ // src/contracts/read-contract.ts
1714
+ async function readContract(options) {
1715
+ const cwd = options.cwd ?? process.cwd();
1716
+ const network = resolveNetwork(options.config, options.networkName);
1717
+ const target = parseInvokeTarget(options.target);
1718
+ const artifacts = await readArtifacts(cwd);
1719
+ const contractArtifact = artifacts.networks[network.name]?.contracts[target.contractName];
1720
+ if (!contractArtifact) {
1721
+ throw new CaatingaError(
1722
+ `No deployed artifact found for "${target.contractName}" on "${network.name}".`,
1723
+ CaatingaErrorCode.ARTIFACT_NOT_FOUND,
1724
+ "Run caatinga deploy for this contract and network before reading it."
1725
+ );
1726
+ }
1727
+ await checkBinary("stellar", "Install Stellar CLI before running caatinga read.");
1728
+ const stellarArgs = [
1729
+ "contract",
1730
+ "invoke",
1731
+ "--id",
1732
+ contractArtifact.contractId,
1733
+ "--source-account",
1734
+ resolveCliSource(options.source),
1735
+ "--send=no",
1736
+ ...buildStellarNetworkArgs(network),
1737
+ "--",
1738
+ target.method,
1739
+ ...options.args ?? []
1740
+ ];
1741
+ const result = await runCommand("stellar", stellarArgs, {
1742
+ cwd,
1743
+ failureCode: CaatingaErrorCode.INVOKE_FAILED
1744
+ });
1745
+ return {
1746
+ target,
1747
+ network,
1748
+ result: result.stdout || result.all
1749
+ };
1750
+ }
1751
+
1534
1752
  // src/templates/create-project-from-template.ts
1535
1753
  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";
1754
+ import path11 from "path";
1537
1755
  import { z as z7 } from "zod";
1538
1756
 
1539
1757
  // src/templates/template-manifest.schema.ts
@@ -1609,8 +1827,8 @@ var TEMPLATE_COPY_EXCLUDED_DIRS = /* @__PURE__ */ new Set([
1609
1827
  ".git"
1610
1828
  ]);
1611
1829
  async function createProjectFromTemplate(options) {
1612
- const targetDir = path10.resolve(options.targetDir);
1613
- const templateDir = path10.resolve(options.templateDir);
1830
+ const targetDir = path11.resolve(options.targetDir);
1831
+ const templateDir = path11.resolve(options.templateDir);
1614
1832
  try {
1615
1833
  await stat2(templateDir);
1616
1834
  } catch {
@@ -1648,7 +1866,7 @@ async function ensureArtifacts(targetDir, projectName) {
1648
1866
  }
1649
1867
  }
1650
1868
  async function readTemplateManifest(templateDir) {
1651
- const manifestPath = path10.join(templateDir, "caatinga.template.json");
1869
+ const manifestPath = path11.join(templateDir, "caatinga.template.json");
1652
1870
  try {
1653
1871
  const rawManifest = await readFile4(manifestPath, "utf8");
1654
1872
  const manifest = TemplateManifestSchema.parse(JSON.parse(rawManifest));
@@ -1685,7 +1903,7 @@ async function readTemplateManifest(templateDir) {
1685
1903
  async function replaceTemplateVariables(dir, projectName) {
1686
1904
  const entries = await readdir3(dir);
1687
1905
  await Promise.all(entries.map(async (entry) => {
1688
- const entryPath = path10.join(dir, entry);
1906
+ const entryPath = path11.join(dir, entry);
1689
1907
  const entryStat = await stat2(entryPath);
1690
1908
  if (entryStat.isDirectory()) {
1691
1909
  await replaceTemplateVariables(entryPath, projectName);
@@ -1699,15 +1917,15 @@ async function replaceTemplateVariables(dir, projectName) {
1699
1917
  }));
1700
1918
  }
1701
1919
  function shouldCopyTemplateEntry(templateDir, source, userFilter) {
1702
- const relativePath = path10.relative(templateDir, source);
1920
+ const relativePath = path11.relative(templateDir, source);
1703
1921
  if (!relativePath || relativePath === ".") {
1704
1922
  return true;
1705
1923
  }
1706
- const normalizedPath = relativePath.split(path10.sep).join("/");
1924
+ const normalizedPath = relativePath.split(path11.sep).join("/");
1707
1925
  if (userFilter && !userFilter(normalizedPath)) {
1708
1926
  return false;
1709
1927
  }
1710
- return !relativePath.split(path10.sep).some((segment) => TEMPLATE_COPY_EXCLUDED_DIRS.has(segment));
1928
+ return !relativePath.split(path11.sep).some((segment) => TEMPLATE_COPY_EXCLUDED_DIRS.has(segment));
1711
1929
  }
1712
1930
  function isTextTemplateFile(filePath) {
1713
1931
  return [
@@ -1719,21 +1937,21 @@ function isTextTemplateFile(filePath) {
1719
1937
  ".tsx",
1720
1938
  ".css",
1721
1939
  ".html"
1722
- ].includes(path10.extname(filePath));
1940
+ ].includes(path11.extname(filePath));
1723
1941
  }
1724
1942
 
1725
1943
  // src/scaffold/create-zk-project.ts
1726
1944
  import { cp as cp2, mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
1727
- import { existsSync } from "fs";
1728
- import path11 from "path";
1945
+ import { existsSync as existsSync2 } from "fs";
1946
+ import path12 from "path";
1729
1947
  import { fileURLToPath } from "url";
1730
- var moduleDir = typeof __dirname === "string" ? __dirname : path11.dirname(fileURLToPath(import.meta.url));
1948
+ var moduleDir = typeof __dirname === "string" ? __dirname : path12.dirname(fileURLToPath(import.meta.url));
1731
1949
  function scaffoldRoot() {
1732
1950
  const candidates = [
1733
- path11.resolve(moduleDir, "../../scaffolds"),
1734
- path11.resolve(moduleDir, "../scaffolds")
1951
+ path12.resolve(moduleDir, "../../scaffolds"),
1952
+ path12.resolve(moduleDir, "../scaffolds")
1735
1953
  ];
1736
- const found = candidates.find((candidate) => existsSync(candidate));
1954
+ const found = candidates.find((candidate) => existsSync2(candidate));
1737
1955
  return found ?? candidates[0];
1738
1956
  }
1739
1957
  function configSource(projectName) {
@@ -1777,7 +1995,8 @@ function packageJsonSource(projectName) {
1777
1995
  "zk:build": "caatinga zk build main",
1778
1996
  "zk:prove": "caatinga zk prove main",
1779
1997
  build: "caatinga build verifier",
1780
- deploy: "caatinga deploy verifier"
1998
+ deploy: "caatinga deploy verifier --network testnet --source ${CAATINGA_SOURCE:-alice}",
1999
+ doctor: "caatinga doctor --network testnet"
1781
2000
  },
1782
2001
  devDependencies: {
1783
2002
  "@caatinga/cli": `^${CAATINGA_CORE_VERSION}`,
@@ -1797,7 +2016,7 @@ Minimal Caatinga ZK project.
1797
2016
  npm install
1798
2017
  npx caatinga zk build main
1799
2018
  npx caatinga build verifier
1800
- npx caatinga deploy verifier --network testnet
2019
+ npx caatinga deploy verifier --network testnet --source <identity>
1801
2020
  npx caatinga zk prove main
1802
2021
  \`\`\`
1803
2022
 
@@ -1805,25 +2024,25 @@ Replace \`circuits/main.circom\` with your circuit. Keep the entry point named \
1805
2024
  `;
1806
2025
  }
1807
2026
  async function createZkProject(options) {
1808
- const targetDir = path11.resolve(options.targetDir);
2027
+ const targetDir = path12.resolve(options.targetDir);
1809
2028
  const force = options.force ?? false;
1810
2029
  const projectFiles = options.projectFiles ?? true;
1811
2030
  await mkdir4(targetDir, { recursive: true });
1812
2031
  if (projectFiles) {
1813
2032
  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" })
2033
+ writeFile4(path12.join(targetDir, "caatinga.config.ts"), configSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2034
+ writeFile4(path12.join(targetDir, "package.json"), packageJsonSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2035
+ writeFile4(path12.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", { encoding: "utf8", flag: force ? "w" : "wx" }),
2036
+ writeFile4(path12.join(targetDir, "README.md"), readmeSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" })
1818
2037
  ]);
1819
2038
  }
1820
- await mkdir4(path11.join(targetDir, "contracts"), { recursive: true });
1821
- await cp2(path11.join(scaffoldRoot(), "zk-circuit-stub"), path11.join(targetDir, "circuits"), {
2039
+ await mkdir4(path12.join(targetDir, "contracts"), { recursive: true });
2040
+ await cp2(path12.join(scaffoldRoot(), "zk-circuit-stub"), path12.join(targetDir, "circuits"), {
1822
2041
  recursive: true,
1823
2042
  force,
1824
2043
  errorOnExist: !force
1825
2044
  });
1826
- await cp2(path11.join(scaffoldRoot(), "zk-verifier"), path11.join(targetDir, "contracts", "verifier"), {
2045
+ await cp2(path12.join(scaffoldRoot(), "zk-verifier"), path12.join(targetDir, "contracts", "verifier"), {
1827
2046
  recursive: true,
1828
2047
  force,
1829
2048
  errorOnExist: !force
@@ -1834,6 +2053,109 @@ async function createZkProject(options) {
1834
2053
  return { targetDir };
1835
2054
  }
1836
2055
 
2056
+ // src/scaffold/create-minimal-project.ts
2057
+ import { cp as cp3, mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
2058
+ import { existsSync as existsSync3 } from "fs";
2059
+ import path13 from "path";
2060
+ import { fileURLToPath as fileURLToPath2 } from "url";
2061
+ var moduleDir2 = typeof __dirname === "string" ? __dirname : path13.dirname(fileURLToPath2(import.meta.url));
2062
+ function scaffoldRoot2() {
2063
+ const candidates = [
2064
+ path13.resolve(moduleDir2, "../../scaffolds"),
2065
+ path13.resolve(moduleDir2, "../scaffolds")
2066
+ ];
2067
+ const found = candidates.find((candidate) => existsSync3(candidate));
2068
+ return found ?? candidates[0];
2069
+ }
2070
+ function configSource2(projectName) {
2071
+ return `import { defineConfig } from "@caatinga/core";
2072
+
2073
+ export default defineConfig({
2074
+ project: "${projectName}",
2075
+ defaultNetwork: "testnet",
2076
+ contracts: {
2077
+ app: {
2078
+ path: "./contracts/app",
2079
+ wasm: "./contracts/app/target/wasm32v1-none/release/app.wasm"
2080
+ }
2081
+ },
2082
+ networks: {
2083
+ testnet: {
2084
+ rpcUrl: "https://soroban-testnet.stellar.org",
2085
+ networkPassphrase: "Test SDF Network ; September 2015"
2086
+ }
2087
+ }
2088
+ });
2089
+ `;
2090
+ }
2091
+ function packageJsonSource2(projectName) {
2092
+ return `${JSON.stringify({
2093
+ name: projectName,
2094
+ version: "0.1.0",
2095
+ private: true,
2096
+ type: "module",
2097
+ scripts: {
2098
+ build: "caatinga build app",
2099
+ deploy: "caatinga deploy app --network testnet --source ${CAATINGA_SOURCE:-alice}",
2100
+ doctor: "caatinga doctor --network testnet",
2101
+ "read:hello": "caatinga read app.hello --network testnet --source ${CAATINGA_SOURCE:-alice}",
2102
+ "read:version": "caatinga read app.version --network testnet --source ${CAATINGA_SOURCE:-alice}"
2103
+ },
2104
+ devDependencies: {
2105
+ "@caatinga/cli": `^${CAATINGA_CORE_VERSION}`,
2106
+ "@caatinga/core": `^${CAATINGA_CORE_VERSION}`
2107
+ }
2108
+ }, null, 2)}
2109
+ `;
2110
+ }
2111
+ function readmeSource2(projectName) {
2112
+ return `# ${projectName}
2113
+
2114
+ Minimal Caatinga project with a Soroban contract stub (no frontend template).
2115
+
2116
+ ## Workflow
2117
+
2118
+ \`\`\`bash
2119
+ npm install
2120
+ npx caatinga doctor
2121
+ npx caatinga build app
2122
+ npx caatinga deploy app --network testnet --source <identity>
2123
+ npx caatinga read app.version --network testnet
2124
+ npx caatinga read app.hello --network testnet
2125
+ \`\`\`
2126
+
2127
+ ## Contract
2128
+
2129
+ - \`hello()\` \u2014 read-only; returns Soroban Symbol \`hello\`
2130
+ - \`version()\` \u2014 read-only; returns \`1\`
2131
+
2132
+ Use \`caatinga read\` for read-only methods. Use \`caatinga invoke\` only after you add state-changing methods to the contract.
2133
+
2134
+ 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).
2135
+
2136
+ Edit \`contracts/app/src/lib.rs\` to customize the contract. Add a frontend later with \`@caatinga/client\` and your chosen UI stack.
2137
+ `;
2138
+ }
2139
+ async function createMinimalProject(options) {
2140
+ const targetDir = path13.resolve(options.targetDir);
2141
+ const force = options.force ?? false;
2142
+ await mkdir5(targetDir, { recursive: true });
2143
+ await Promise.all([
2144
+ writeFile5(path13.join(targetDir, "caatinga.config.ts"), configSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2145
+ writeFile5(path13.join(targetDir, "package.json"), packageJsonSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2146
+ writeFile5(path13.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", { encoding: "utf8", flag: force ? "w" : "wx" }),
2147
+ writeFile5(path13.join(targetDir, "README.md"), readmeSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" })
2148
+ ]);
2149
+ await mkdir5(path13.join(targetDir, "contracts"), { recursive: true });
2150
+ await cp3(path13.join(scaffoldRoot2(), "soroban-contract-stub"), path13.join(targetDir, "contracts", "app"), {
2151
+ recursive: true,
2152
+ force,
2153
+ errorOnExist: !force
2154
+ });
2155
+ await writeArtifacts(createInitialArtifacts(options.projectName, { networks: ["testnet"] }), targetDir);
2156
+ return { targetDir };
2157
+ }
2158
+
1837
2159
  // src/ci/is-transient-testnet-smoke-failure.ts
1838
2160
  var NO_RETRY_CAATINGA_SUBSTRINGS = [
1839
2161
  "CAATINGA_UNSUPPORTED_CLI_VERSION",
@@ -1862,16 +2184,19 @@ export {
1862
2184
  CaatingaConfigSchema,
1863
2185
  CaatingaError,
1864
2186
  CaatingaErrorCode,
2187
+ READ_CALL_FAILURE_REGEX,
1865
2188
  STELLAR_CLI_LAST_TESTED_VERSION,
1866
2189
  STELLAR_CLI_MIN_VERSION,
1867
2190
  TemplateManifestSchema,
1868
2191
  WELL_KNOWN_NETWORKS,
1869
2192
  buildContract,
1870
2193
  buildDependencyGraph,
2194
+ buildReadCallHint,
1871
2195
  checkBinary,
1872
2196
  checkStellarCliVersion,
1873
2197
  collectProjectStatus,
1874
2198
  createInitialArtifacts,
2199
+ createMinimalProject,
1875
2200
  createProjectFromTemplate,
1876
2201
  createZkProject,
1877
2202
  defineConfig,
@@ -1884,6 +2209,8 @@ export {
1884
2209
  generateBindings,
1885
2210
  generateBindingsGraph,
1886
2211
  invokeContract,
2212
+ isCargoBinMissingFromPath,
2213
+ isReadCallFailure,
1887
2214
  isTransientTestnetSmokeFailure,
1888
2215
  loadConfig,
1889
2216
  parseContractId,
@@ -1891,11 +2218,13 @@ export {
1891
2218
  parseStellarCliVersion,
1892
2219
  readArtifacts,
1893
2220
  readBindingMarker,
2221
+ readContract,
1894
2222
  resolveContract,
1895
2223
  resolveDefaultContractName,
1896
2224
  resolveDeployArgs,
1897
2225
  resolveDeployOrder,
1898
2226
  resolveNetwork,
2227
+ resolveSubprocessEnv,
1899
2228
  runCommand,
1900
2229
  toCaatingaError,
1901
2230
  updateArtifact,