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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
A CLI installer tool for dAppBooster projects. It supports two **stacks** and two **modes**:

- **Stacks:** `evm` (the original dAppBooster for EVM chains) and `canton` (dAppBooster for Canton: Daml ledger, Carpincho wallet, off-chain services). Each stack declares its own source repository, ref strategy (tag-latest vs branch), package manager, env files, optional `removeAfterClone` paths, and features.
- **Interactive** (default): React + Ink TUI that prompts for stack first, then project name, then clone → installation mode → optional packages → install → cleanup → post-install. The stack prompt is skipped when `--canton`, `--evm`, or `--stack` is supplied.
- **Non-interactive**: Flag-driven (`--ni` or auto-detected when not a TTY) for AI agents and CI. Outputs JSON to stdout. Run `--info` for stack + feature discovery, then `--canton`/`--evm` (or `--stack`) + `--name` + `--mode` [+ `--features`]. Omitting a stack flag in non-interactive mode defaults to `evm` for backward compatibility.
- **Interactive** (default): React + Ink TUI that prompts for stack first, then project name, then installation mode (Canton offers **default** / full / custom; EVM offers full / custom) → optional packages → install → cleanup → post-install. The stack prompt is skipped when `--canton`, `--evm`, or `--stack` is supplied.
- **Non-interactive**: Flag-driven (`--ni` or auto-detected when not a TTY) for AI agents and CI. Outputs JSON to stdout. Run `--info` for stack + feature discovery, then `--canton`/`--evm` (or `--stack`) + `--name` + `--mode` [+ `--features`]. Canton supports `--mode default` (the recommended set: keeps `carpincho` + `llm`, removes `github` + `precommit`); `default` is rejected for EVM. Omitting a stack flag in non-interactive mode defaults to `evm` for backward compatibility.

## Stack & Conventions

Expand Down
4 changes: 2 additions & 2 deletions architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ source/
StackSelection.tsx First step: pick a stack (skipped if preselectedStack is passed)
ProjectName.tsx Prompt for project name
CloneRepo/CloneRepo.tsx Clone progress display (receives stack)
InstallationMode.tsx Full / Custom selection
OptionalPackages.tsx Feature multiselect (per-stack, enforces feature dependencies)
InstallationMode.tsx Mode selection (Canton: Default/Full/Custom; EVM: Full/Custom)
OptionalPackages.tsx Feature multiselect (per-stack; pre-checks default:true features)
Install/Install.tsx Install progress display (receives stack)
FileCleanup.tsx Cleanup progress display (receives stack)
PostInstall.tsx Post-install instructions, stack-specific
Expand Down
9 changes: 6 additions & 3 deletions docs/architecture/abstractions.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ type StackConfig = {
ref?: string // required when refType === 'branch'
packageManager: 'pnpm' | 'npm'
removeAfterClone: string[] // paths nuked between clone and `git init` (empty for both stacks today)
postInstall?: string[] // stack-level post-install guidance, shown for every scaffold (Canton run steps)
envFiles: Array<{ from: string; to: string; ifFeature?: string }>
features: Record<string, FeatureDefinition>
}
```

Installation modes are stack-aware via `getInstallationModes(stack)` — Canton offers `default` / `full` / `custom`, EVM offers `full` / `custom`. The `default: boolean` flag on a feature has two roles: it pre-checks the feature in the custom multiselect **and** defines membership in `default` mode (`getDefaultFeatureNames(stack)` = the `default: true` set). `default` mode is Canton-only because EVM has no `default: false` features (there it would equal `full`).

`getStackConfig(stack)` reads the base config and overlays the env-var overrides `DAPPBOOSTER_<STACK>_REPO_URL` and `DAPPBOOSTER_<STACK>_REF` before returning — that's the single hook for retargeting either stack at a fork or pre-release branch without editing code.

`getFeatureNames(stack)` and `isFeatureNameValid(stack, name)` are the per-stack feature accessors. There is no global `featureDefinitions` export — that would imply a single stack.
Expand All @@ -43,7 +46,7 @@ type FeatureDefinition = {

When adding a new feature, add it to the relevant stack's `features` map. Programmatic consumers pick it up automatically. Canton feature cleanup is fully data-driven from `paths` (see the Operations Layer below), so a new Canton feature needs no cleanup code — only its `paths`. EVM features still need an explicit per-feature cleanup function. The CLI `--help` text in `cli.tsx` maintains its own copy in both cases.

**Feature dependencies (`requires`)** are resolved by pure helpers in `utils.ts`. `resolveSelectedFeatures(stack, selected)` expands a selection to include every transitive requirement (used by the non-interactive path, so `--features e2e` yields `[counter, e2e]`). `applyFeatureToggle(stack, selection, toggled, action)` keeps the interactive multiselect consistent: selecting a feature pulls its requirements in, deselecting one cascades its dependents out. `e2e requires counter` is the only dependency today. `--info` surfaces each feature's `requires` so agents can resolve dependencies themselves.
**Feature dependencies (`requires`)** are resolved by pure helpers in `utils.ts`. `resolveSelectedFeatures(stack, selected)` expands a selection to include every transitive requirement; `resolveModeFeatures(stack, mode, customSelection)` maps a mode to its kept-feature list (full → all, default → the `default: true` set, custom → the resolved selection) and is shared by the non-interactive path and the interactive Install/FileCleanup/PostInstall steps. `applyFeatureToggle(stack, selection, toggled, action)` keeps the interactive multiselect consistent: selecting a feature pulls its requirements in, deselecting one cascades its dependents out. No feature declares `requires` today (the machinery remains for future use); `--info` surfaces each feature's `requires` so agents can resolve dependencies themselves.

## Operations Layer (`source/operations/`)

Expand All @@ -53,8 +56,8 @@ Plain async functions, no UI dependencies. Each operation that varies per stack
|---|---|
| `cloneRepo(stack, projectName, onProgress?)` | Reads `stack.refType`. **tag-latest**: shallow clone with `--no-checkout`, `git fetch --tags`, then `git checkout $(git describe --tags …)` (shell required for `$()`). **branch**: shallow clone with `--branch <stack.ref> --single-branch` (no shell). After that, runs `fs.rm` for every entry in `stack.removeAfterClone` (empty for both stacks today), removes `.git`, and reinitializes with `git init`. Uses `execFile` everywhere except the tag-latest shell substitution. |
| `createEnvFile(stack, projectFolder, features?)` | Copies every entry from `stack.envFiles`. Entries with `ifFeature` are skipped unless the named feature is in the selection (e.g. Canton's `carpincho-wallet/.env.local` only when `carpincho` is selected). |
| `installPackages(stack, projectFolder, mode, features, onProgress?)` | Uses `stack.packageManager`. Full: `<pm> install`. Custom with packages to remove: `<pm> remove` (pnpm) or `<pm> uninstall` (npm) + `<pm> run postinstall`. Custom with all features: `<pm> install`. `execFile` only — never shell. |
| `cleanupFiles(stack, projectFolder, mode, features, onProgress?)` | First runs **repository hygiene** (every stack/mode): both stacks always remove `.github` (CI) and the husky/commitlint automation (`.husky`, `.lintstagedrc.mjs`, `commitlint.config.js`) and sanitize tooling deps/scripts from `package.json`; **EVM additionally** always removes its own agent metadata (`.claude`, `AGENTS.md`, `CLAUDE.md`, `architecture.md`), whereas **Canton keeps that metadata** under the optional `llm` feature. Then dispatches to `cleanupEvmFiles` or `cleanupCantonFiles`. EVM removes deselected feature files via per-feature functions plus the `.install-files` staging directory, and patches `package.json` by feature name. Canton cleanup is **data-driven**: it loops the stack's features and, in custom mode, removes each deselected feature's `paths` (e.g. `counter/`, `e2e/`, `carpincho-wallet`, the `llm` artifact paths). The removed directories then drive `package.json` script stripping by **command target** — any script whose command invokes a removed directory is dropped (so deselecting `carpincho` strips `wallet:dev` / `carpincho:build:extension`). Command-based matching keeps cleanup correct as the upstream repo renames or adds scripts. In `full` mode no feature paths are removed, so a full Canton scaffold keeps `carpincho-wallet`, the agent docs, and every script. Canton then makes an initial `git` commit of the scaffold. |
| `installPackages(stack, projectFolder, mode, features, onProgress?)` | Uses `stack.packageManager`. Full: `<pm> install`. `default`/`custom` with packages to remove: `<pm> remove` (pnpm) or `<pm> uninstall` (npm) + `<pm> run postinstall`; with nothing to remove: `<pm> install`. Canton features all carry `packages: []`, so Canton always runs a plain `npm install` (husky-dep removal happens in cleanup, not here — the Canton template has no `postinstall` script). `execFile` only — never shell. |
| `cleanupFiles(stack, projectFolder, mode, features, onProgress?)` | **EVM** runs **repository hygiene** first (always): removes `.github` (CI), the husky/commitlint automation (`.husky`, `.lintstagedrc.mjs`, `commitlint.config.js`), and its own agent metadata (`.claude`, `AGENTS.md`, `CLAUDE.md`, `architecture.md`), and sanitizes tooling deps/scripts from `package.json`; then `cleanupEvmFiles` removes deselected feature files via per-feature functions plus the `.install-files` staging directory. **Canton** runs **no forced hygiene** — `.github` and the pre-commit automation are the optional `github` and `precommit` features. `cleanupCantonFiles` is **data-driven**: for `default` and `custom` modes (not `full`) it loops the stack's features and removes each deselected feature's `paths` (`github` → `.github`; `precommit` → the husky files; `carpincho` → `carpincho-wallet`; `llm` → the agent/LLM artifacts). Removed directories drive `package.json` script stripping by **command target** — any script whose command invokes a removed directory is dropped (so deselecting `carpincho` strips `wallet:dev` / `carpincho:build:extension`). When `precommit` is removed it additionally strips the `prepare`/commitlint scripts and the husky/lint-staged/commitlint dev-dependencies. In `full` mode nothing is removed, so a full Canton scaffold keeps `.github`, the hooks, `carpincho-wallet`, and the agent docs. Canton then makes an initial `git` commit of the scaffold. |

### Interrupt safety (`installGuard`)

Expand Down
28 changes: 15 additions & 13 deletions docs/architecture/data-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,18 @@ default → dynamic import ink + App (preselectedStack passed if resolved) →
2. `--name` required
3. `--mode` required
4. `--name` matches `/^[a-zA-Z0-9_]+$/`
5. `--mode` is `full` or `custom`
6. Full mode: skip to step 10 (features ignored, all stack features installed)
7. `--features` required for custom mode
8. Parsed features list is non-empty (rejects trailing commas, whitespace-only entries)
9. Every feature name is valid **for the selected stack**
10. Project directory does not already exist

Custom-mode selections are then expanded with `resolveSelectedFeatures` (see
[abstractions](./abstractions.md#feature-definitions)), so feature dependencies are pulled in
before the operations run and before the result is reported.
5. `--mode` is `full`, `default`, or `custom`
6. `default` mode is rejected for the `evm` stack (Canton-only)
7. Full / default mode: skip to step 11 (features come from the mode, `--features` ignored — `full` = all, `default` = the `default: true` set)
8. `--features` required for custom mode
9. Parsed features list is non-empty (rejects trailing commas, whitespace-only entries)
10. Every feature name is valid **for the selected stack**
11. Project directory does not already exist

The kept-feature list is derived from the mode via `resolveModeFeatures` (see
[abstractions](./abstractions.md#feature-definitions)); for custom mode this expands the selection
with `resolveSelectedFeatures` so any feature dependencies are pulled in before the operations run
and before the result is reported.

**Non-interactive execution order:**
`cloneRepo` → `createEnvFile` → `installPackages` → `cleanupFiles` → success JSON
Expand All @@ -50,14 +52,14 @@ Any error produces `{ "success": false, "error": "..." }` and exit code 1. Error
"success": true,
"stack": "evm|canton",
"projectName": "...",
"mode": "full|custom",
"mode": "full|default|custom",
"features": ["..."],
"path": "/absolute/path",
"postInstall": ["..."]
}
```

For full mode, `features` lists all of the stack's feature names. For custom mode, the selected ones plus any dependencies they pulled in.
For full mode, `features` lists all of the stack's feature names; for default mode, the `default: true` set (Canton: `carpincho`, `llm`); for custom mode, the selected ones plus any dependencies they pulled in.

## Interactive (human)

Expand All @@ -81,4 +83,4 @@ Operations (disk): CloneRepo → Install → FileCleanup → PostInstall

Once operations begin, `CloneRepo` calls `beginInstall` (see [abstractions → installGuard](./abstractions.md#interrupt-safety-installguard)) and `FileCleanup` calls `completeInstall` on success, so a Ctrl+C mid-scaffold removes the partial directory while a finished project is left intact.

Components are presentation-only — they call operations via `useEffect` and render status. Components receive `MultiSelectItem[]` for feature selection (TUI concern) and convert to `FeatureName[]` before calling operations. The `OptionalPackages` multiselect enforces feature dependencies live via `applyFeatureToggle`. `PostInstall` renders stack-specific instructions; the EVM branch shows the subgraph warning when applicable, the Canton branch shows the `canton:up`/`app:dev` commands and — when the `carpincho` feature is selected (or full mode) — the Carpincho extension build/load instructions.
Components are presentation-only — they call operations via `useEffect` and render status. Components receive `MultiSelectItem[]` for feature selection (TUI concern), then derive the kept-feature `FeatureName[]` via `resolveModeFeatures(stack, mode, selected)` before calling operations — so `full`/`default` resolve correctly even though the multiselect is skipped. The `OptionalPackages` multiselect pre-checks `default: true` features and enforces feature dependencies live via `applyFeatureToggle`. `PostInstall` renders stack-specific instructions; the EVM branch shows the subgraph warning when applicable, the Canton branch always shows the `canton:up`/`app:dev` commands and — when the `carpincho` feature is in the resolved set — the Carpincho extension build/load instructions.
2 changes: 1 addition & 1 deletion docs/architecture/extending.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

## How to Add a New Feature to an Existing Stack

1. **`source/constants/config.ts`** — add an entry to the stack's `features` map. For **Canton**, also list the feature's `paths`: cleanup is data-driven, so no cleanup code is needed and scripts that target a removed directory are stripped automatically. If it ships an env file, add an `ifFeature`-gated `envFiles` entry. If it depends on another feature, add `requires` — resolution is automatic in both the interactive and non-interactive paths.
1. **`source/constants/config.ts`** — add an entry to the stack's `features` map. The `default` flag governs both the custom-mode pre-check and `default`-mode membership: set `default: true` for "kept by the recommended install", `default: false` for "removed by default / opt-in" (Canton's `github` and `precommit`). For **Canton**, also list the feature's `paths`: cleanup is data-driven, so no cleanup code is needed and scripts that target a removed directory are stripped automatically. If it ships an env file, add an `ifFeature`-gated `envFiles` entry. If it depends on another feature, add `requires` — resolution is automatic in both the interactive and non-interactive paths.
2. **`source/operations/cleanupFiles.ts`** — **EVM only**: add a cleanup function for the feature and call it from `cleanupEvmFiles` when deselected; if it has scripts, add removal to `patchPackageJsonEvm`. Canton needs no change here.
3. **`source/components/steps/PostInstall.tsx`** — extend stack-specific instructions if needed.
4. **`source/cli.tsx`** — update the `--help` text.
Expand Down
6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dappbooster",
"version": "3.2.0",
"version": "3.3.0",
"description": "Agent-friendly dAppBooster installer that scaffolds Web3 dApps via TUI or non-interactive CLI/CI.",
"keywords": [
"dappbooster",
Expand Down Expand Up @@ -39,9 +39,7 @@
"lint": "pnpm biome check",
"lint:fix": "pnpm biome check --write"
},
"files": [
"dist"
],
"files": ["dist"],
"dependencies": {
"figures": "^6.1.0",
"ink": "^5.2.1",
Expand Down
Loading