Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Clippy and warning flags are configured in `.cargo/config.toml` `rustflags` and
`init` → `build` → `generate` → `watch`
- `init` — scaffolds a new project; fetches the frontend template from `stellar-scaffold/ui` via degit
- `upgrade` — converts an existing Soroban workspace into a full scaffold project
- `build` — builds contracts and generates TypeScript clients based on `scaffold.yml` (version 2: networks, accounts, contracts); version 1 projects still fall back to `environments.toml`
- `build` — builds contracts and generates TypeScript clients based on `scaffold.yml` (version 2: networks, accounts, contracts); projects with an `environments.toml` and no version 2 `scaffold.yml` still fall back to it (a version 1 `scaffold.yml` beside it only sets directories)
- `config check` / `config show` — validate `scaffold.yml` and print the resolved config for one network
- `generate contract` — adds a new contract to an existing project
- `watch` — rebuilds on changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use crate::config::{self, CONFIG_FILE};
use crate::extension::{ExtensionListStatus, list as list_extensions};

/// Where the schema-version fixes point for migration instructions.
const MIGRATION_URL: &str = "https://github.com/stellar-scaffold/cli/blob/main/CHANGELOG.md";
const MIGRATION_URL: &str =
"https://github.com/stellar-scaffold/cli/blob/main/crates/stellar-scaffold-cli/CHANGELOG.md";

/// Validates `scaffold.yml`: the full config for version 2, otherwise whether
/// the project falls back to `environments.toml`, and the default directories.
Expand Down
4 changes: 2 additions & 2 deletions crates/stellar-scaffold-cli/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ pub enum VersionError {
#[error(
"scaffold.yml is missing or has no 'version' field. \
Add 'version: {SCHEMA_VERSION}' at the top. \
See https://github.com/stellar-scaffold/cli/blob/main/CHANGELOG.md for details."
See https://github.com/stellar-scaffold/cli/blob/main/crates/stellar-scaffold-cli/CHANGELOG.md for details."
)]
MissingVersion,
#[error(
"scaffold.yml uses schema version {found}, but this CLI supports version {SCHEMA_VERSION}. \
See https://github.com/stellar-scaffold/cli/blob/main/CHANGELOG.md for migration instructions."
See https://github.com/stellar-scaffold/cli/blob/main/crates/stellar-scaffold-cli/CHANGELOG.md for migration instructions."
)]
UnsupportedVersion { found: u64 },
}
Expand Down
11 changes: 5 additions & 6 deletions docs/site/docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Options:
- `--template <name>`: Template selector. A bare framework name (`react` or `svelte`) picks an official template; a `user/repo` shorthand (optionally with `#branch` or `#tag`) degits that community repo directly; `none` creates a contracts-only project with no frontend. Omit to choose interactively
- `--no-template`: Create a contracts-only project with no frontend (alias for `--template none`)
- `--tutorial`: Start from the simplified project the [tutorial](./tutorial/00-overview.md) builds on, rather than a full template
- `-p <name>` or `--package-manager <name>`: Package manager to use. Omit to choose interactively
- `-p <name>` or `--package-manager <name>`: Package manager to use, recorded as `project.package-manager` in `scaffold.yml`. Omit to choose interactively
- `-y` or `--yes`: Accept all defaults and skip interactive prompts

`--tutorial` picks the frontend and the package manager for you, so it does not prompt. Pass `--package-manager` alongside it if you want something other than npm.
Expand All @@ -28,9 +28,9 @@ The init command creates:
- `app-lib/`, utility code your app can use for wallet connection, network settings, and formatting, plus the directory your generated contract clients are written to
- Configuration files for both contract and frontend development

Official templates come from the [Stellar Scaffold templates repo](https://github.com/stellar-scaffold/ui).
Official templates come from the [Stellar Scaffold templates repo](https://github.com/stellar-scaffold/ui). To build your own, see [Community Templates](./community-templates.md).

With `--no-template` (or `--template none`), the frontend layer is omitted entirely: no `app/`, no `app-lib/`, no JS workspaces, no dependency install. Use plain `stellar scaffold build` to compile the contracts. Adding `--build-clients` still deploys and generates a client for every contract listed in [`scaffold.yml`](./configuration.md), even though there is no app to consume them.
With `--no-template` (or `--template none`), the frontend layer is omitted entirely: no `app/`, no `app-lib/`, no JS workspaces, no dependency install. Every contract in [`scaffold.yml`](./configuration.md) is written with `client: false`, so `stellar scaffold build --build-clients` and `watch` deploy contracts without generating TypeScript clients. Remove it from a contract if you later need one.

## Generate Command

Expand Down Expand Up @@ -166,7 +166,7 @@ Findings that have a single-command remedy print it on a `fix:` line beneath.
- `rustc` is installed, and matches `rust-toolchain.toml` if the project pins a version
- The `wasm32v1-none` target contracts compile to is installed
- The `stellar` CLI is installed, and its major version matches the one this release was built against
- Node and the package manager named by your `package.json` `packageManager` field are installed
- Node and the package manager named by `project.package-manager` in `scaffold.yml` (or a legacy `package.json` `packageManager` field) are installed

**Project** — needs a Cargo workspace:

Expand All @@ -192,8 +192,7 @@ $ stellar scaffold doctor
✅ wasm-target wasm32v1-none
✅ stellar-cli 27.0.0
✅ node v24.16.0
⚠️ package-manager package.json pins npm@11.7.0, found 11.13.0
fix: corepack use npm@11.7.0
✅ package-manager npm 11.13.0
Project
✅ scaffold-yml scaffold.yml schema version 2
✅ contracts-dir contracts
Expand Down
82 changes: 82 additions & 0 deletions docs/site/docs/community-templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Community Templates

Anyone can publish a frontend template for Scaffold. A community template is a public Git repository that `stellar scaffold init` uses as the starting point of a new project.

```bash
stellar scaffold init my-app --template your-org/your-template
stellar scaffold init my-app --template your-org/your-template#v1.2.0
```

Any `--template` value containing a `/` is treated as a community repo, with an optional `#branch` or `#tag` suffix.

---

## Repo shape

Your repo is used as-is, so it must already be a complete, runnable Scaffold project. At the root it needs:

| Path | Purpose |
| -------------- | -------------------------------------------------------- |
| `scaffold.yml` | Project config starting with `version: 2` |
| `Cargo.toml` | Rust workspace containing your contracts |
| `.env.example` | _Optional._ Default environment values, copied to `.env` |

Everything else is up to you: where contracts live, and which language, framework, and task runner your frontend uses.

### Project settings

The `project:` section of `scaffold.yml` tells the CLI where things live and which package manager to use:

```yaml
version: 2
project:
contracts-dir: contracts # Rust contract crates
clients-dir: app-lib/clients # Generated TypeScript clients
package-manager: pnpm # npm, pnpm, yarn, bun, or deno; pin with pnpm@9.6.0
```

All three are optional. Directories default to the values above and the package manager defaults to npm. `init` uses your template's `package-manager` unless the user passes `--package-manager`. See [Configuration](/docs/configuration) for every `scaffold.yml` key.

### Generated clients

During `init` and `stellar scaffold build --build-clients`, the CLI writes one TypeScript package per contract into `clients-dir`, plus an `index.ts` exporting a ready-to-use client for each. Import clients from that file wherever your frontend lives.

`index.ts` only imports the packages beside it, so it works from any `clients-dir`. Network settings are written in at build time, so rebuild to target a different network.

By default, plain HTTP RPC URLs are allowed only on the local network. To allow them elsewhere, opt in on that network:

```yaml
networks:
devnet:
rpc-url: http://devnet.internal:8000/rpc
network-passphrase: Devnet ; 2026
allow-http: true
```

If your frontend doesn't use a contract's client, for example a library contract other contracts depend on, or a frontend that isn't JavaScript, turn it off. The contract still builds and deploys:

```yaml
contracts:
token:
type: workspace
source: token
client: false
networks:
local:
```

`client` applies to every network, so it can't be set under a contract's `networks:` entries.

Building clients needs Node and your package manager installed. A template whose contracts all set `client: false` needs neither.

---

## Maintaining a template

- **Start from an official template.** `stellar scaffold init my-template --template react`, then swap in your own frontend.
- **Pin the CLI version.** If your template has a root `package.json`, set `engines.stellar-scaffold` (e.g. `">=0.0.27 <0.1.0"`). Users with an incompatible CLI get a clear error instead of a broken project.
- **Tag releases.** Let users pin `your-org/your-template#v1.2.0` so your `main` branch can move without breaking them.
- **Keep it public.** `init` downloads the repo without credentials, so private repos won't work.
- **Track upstream.** Watch [`stellar-scaffold/ui`](https://github.com/stellar-scaffold/ui) and the [CLI changelog](https://github.com/stellar-scaffold/cli/blob/main/crates/stellar-scaffold-cli/CHANGELOG.md) for changes to `scaffold.yml` or generated client output.
- **Test in CI.** Run `stellar scaffold init tmp --template your-org/your-template#<branch> -y` and then your build and test scripts against each CLI release you support.
- **Document setup.** Note any required env vars, Docker usage, or third-party extensions in your README.
19 changes: 13 additions & 6 deletions docs/site/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ contracts:

Machine-local settings and secrets stay out of this file. They go in `.env`, which is gitignored and also read by your frontend.

:::note Projects created before `scaffold.yml` version 2 use `environments.toml` instead. `build` and `watch` still read it as long as `scaffold.yml` says `version: 1`. See [Legacy: `environments.toml`](#legacy-environmentstoml) below. :::
:::note Projects created before `scaffold.yml` version 2 use `environments.toml` instead. `build` and `watch` still read it as long as the project has an `environments.toml` and no version 2 `scaffold.yml`. See [Legacy: `environments.toml`](#legacy-environmentstoml) below. :::

## Choosing a network

Expand All @@ -44,6 +44,8 @@ Machine-local settings and secrets stay out of this file. They go in `.env`, whi

The name must be declared under `networks:`. New projects set `STELLAR_NETWORK=local` in `.env`; switch it to `testnet` to build against testnet. Keep the `PUBLIC_STELLAR_*` values in `.env` pointed at the same network, since those are what your frontend connects to.

Generated clients are bound to the network they were built for: its passphrase and RPC URL are written into `<clients-dir>/index.ts`. Rebuild after switching networks.

`STELLAR_SCAFFOLD_ENV` is ignored with a version 2 `scaffold.yml`. The CLI warns if it is set.

## `version`
Expand All @@ -52,12 +54,13 @@ Required, and must be `2`. The CLI checks it before anything else, so a file wri

## `project`

Where things live. Both keys are optional.
Where things live and which package manager builds them. All keys are optional.

| Key | Default | Meaning |
| --- | --- | --- |
| `contracts-dir` | `contracts` | Where your Rust contract crates live |
| `clients-dir` | `app-lib/clients` | Where generated clients are written: one package per contract in `<clients-dir>/<name>/`, plus `<clients-dir>/index.ts`, which your app imports as `@stellar-scaffold/app-lib/clients` |
| `package-manager` | `npm` | Installs and builds the generated clients: `npm`, `pnpm`, `yarn`, `bun` or `deno`, optionally pinned to a version (`pnpm@9.6.0`) that `doctor` checks. `init` records the one you choose, with its installed version |

Everything in `clients-dir` is regenerated on each build, so hand edits are overwritten. To customize a client, import it into your own code instead.

Expand Down Expand Up @@ -98,9 +101,10 @@ A network with no settings can be written as a bare `testnet:`, which is the sam
| `default-account` | first entry in `accounts` | Which account signs deploys when a contract sets no `signer`. Must be listed in `accounts` |
| `start-container` | `true` only for the local (standalone) passphrase | Whether `build` starts a `stellar/quickstart` container. Set `false` if a local chain is already running at `rpc-url` |
| `allow-deploy` | `false` for the testnet, futurenet and mainnet passphrases; `true` otherwise | Whether Scaffold may deploy `workspace` contracts here |
| `allow-http` | `true` only for the local (standalone) passphrase | Whether generated clients may use a plain `http://` `rpc-url`. `config check` warns when it is set on a public network |
| `extends` | none | Another declared network to inherit settings from |

`allow-deploy` and `start-container` defaults come from the network's passphrase, not its name. A custom network pointed at testnet is treated like testnet, and a custom network with the standalone passphrase gets a local container.
`allow-deploy`, `allow-http` and `start-container` defaults come from the network's passphrase, not its name. A custom network pointed at testnet is treated like testnet, and a custom network with the standalone passphrase gets a local container.

### `extends`

Expand Down Expand Up @@ -150,6 +154,7 @@ Keys directly under a contract are defaults for every network it lists. Entries
| `after-deploy` | List of contract methods to call, with no arguments, right after deploying |
| `after-deploy-script` | Path to an executable to run after deploying (not yet supported by `build`) |
| `from-network` | Copy the Wasm of a contract deployed on another network and deploy it here (not yet supported by `build`) |
| `client` | Whether to generate a TypeScript client. Defaults to `true`. With `false` the contract is still built and deployed, for example as a dependency of other contracts. Only valid at the top level of a contract, so a client is the same on every network |
| `networks` | Per-network entries. Only valid at the top level of a contract |

`signer`, `args`, `after-deploy` and `after-deploy-script` only apply when Scaffold deploys the contract, which means `type: workspace` (or `from-network`). Setting them on a contract that only references an existing deployment is an error.
Expand Down Expand Up @@ -258,7 +263,9 @@ stellar scaffold config show --network testnet # print the fully resolved conf

## Legacy: `environments.toml`

Projects whose `scaffold.yml` says `version: 1` keep their networks, accounts and contracts in `environments.toml`, keyed by environment (`development`, `testing`, `staging`, `production`) and selected with `STELLAR_SCAFFOLD_ENV`. In those projects `scaffold.yml` only holds directories:
Projects with an `environments.toml` and no version 2 `scaffold.yml` keep their networks, accounts and contracts in `environments.toml`, keyed by environment (`development`, `testing`, `staging`, `production`) and selected with `STELLAR_SCAFFOLD_ENV`.

Contracts are read from `contracts/` and clients are written to `app-lib/clients/`, unless a version 1 `scaffold.yml` beside `environments.toml` moves them. Only its `config:` section is read:

```yaml
version: 1
Expand All @@ -284,9 +291,9 @@ id = "C..."

`build` and `watch` still read this format, and `--network` is ignored for it. To move to version 2:

- Replace `config:` with `project:`, renaming `contracts_dir`/`clients_dir` to `contracts-dir`/`clients-dir`, and set `version: 2`.
- Set `version: 2`. If your old `scaffold.yml` has a `config:` section, replace it with `project:`, renaming `contracts_dir`/`clients_dir` to `contracts-dir`/`clients-dir`.
- Turn each environment into an entry under `networks:`. `run_locally` becomes `start-container`; an explicit `default = true` account becomes `default-account`.
- Turn each contract into an entry under `contracts:` with `type: workspace` and its crate name as `source`, or `type: contract` and the `id` as `source`, and list the networks it belongs on.
- Turn each contract into an entry under `contracts:` with `type: workspace` and its crate name as `source`, or `type: contract` and the `id` as `source`, and list the networks it belongs on. `client = false` becomes `client: false`.
- Rewrite `constructor_args` as an `args` map, `$(stellar keys address x)` as `${account.x}`, and a `STELLAR_ACCOUNT=` prefix as `signer`.
- Rewrite `after_deploy` as a list of method names. Calls that take arguments are not supported by `after-deploy` yet.
- Move `extensions` into the top-level `extensions:` map.
Expand Down
17 changes: 9 additions & 8 deletions docs/site/docs/tutorial/01-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,19 +351,20 @@ import { guessTheNumber } from "@stellar-scaffold/app-lib/clients";
`guessTheNumber` is a ready-to-use client for our contract, and Stellar Scaffold generated it. Look at `app-lib/clients/index.ts` and you'll find it:

```ts
// AUTO-GENERATED by `stellar scaffold` — do not edit.
// AUTO-GENERATED by `stellar scaffold` - DO NOT EDIT.

import { network } from "@stellar-scaffold/app-lib";
import { Client as GuessTheNumber } from "guess-the-number";
import { Client as GuessTheNumber } from "./guess-the-number/dist/index.js";

const { passphrase: networkPassphrase, rpcUrl, id } = network;
export const options = {
networkPassphrase: "Standalone Network ; February 2017",
rpcUrl: "http://localhost:8000/rpc",
allowHttp: true,
publicKey: undefined,
};

export const guessTheNumber = new GuessTheNumber({
networkPassphrase,
rpcUrl,
allowHttp: id === "local",
...options,
contractId: "CBPAPSB7SXM3MNJVLXPSD6BRQ2ZN33OQVYWO45332TOP4PQLMCHJV4QN",
publicKey: undefined,
});
```

Expand Down
Loading
Loading