Skip to content

Commit fbefb5e

Browse files
authored
Merge pull request #4 from BootNodeDev/feat/canton-stack
feat: add Canton stack support
2 parents 927cdac + cfbb0fa commit fbefb5e

39 files changed

Lines changed: 2676 additions & 707 deletions

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
20
1+
24

AGENTS.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66

77
## What This Is
88

9-
A CLI installer tool for dAppBooster projects. It supports two modes:
9+
A CLI installer tool for dAppBooster projects. It supports two **stacks** and two **modes**:
1010

11-
- **Interactive** (default): React + Ink TUI that walks users through project naming, repo cloning, installation mode selection, optional packages, and post-install steps.
12-
- **Non-interactive**: Flag-driven mode (`--ni` or auto-detected when not a TTY) for AI agents and CI. Outputs JSON to stdout. Run `--info` for feature discovery, then `--name` + `--mode` [+ `--features`] to install.
11+
- **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.
12+
- **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.
13+
- **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.
1314

1415
## Stack & Conventions
1516

@@ -35,11 +36,12 @@ A CLI installer tool for dAppBooster projects. It supports two modes:
3536

3637
## Working Rules
3738

38-
- Use **pnpm** only (never npm or yarn)
39+
- Use **pnpm** only for this installer (never npm or yarn). The Canton stack scaffolds an npm project; that's a property of the generated project, not this installer.
3940
- Treat `dist/` as build output — never edit directly
4041
- User input (`projectName`) must never be interpolated into shell command strings — use `execFile` (args array) instead
41-
- `source/constants/config.ts` is the single source of truth for feature metadata — all programmatic consumers read from it (CLI `--help` text maintains its own copy)
42-
- Components are presentation-only — business logic lives in `source/operations/`
42+
- `source/constants/config.ts` is the single source of truth for stack and feature metadata — all programmatic consumers read it through `getStackConfig(stack)`. CLI `--help` text maintains its own copy.
43+
- Stack overrides come from env vars `DAPPBOOSTER_<STACK>_REPO_URL` and `DAPPBOOSTER_<STACK>_REF` (read inside `getStackConfig`) — useful for forks and pre-release testing.
44+
- Components are presentation-only — business logic lives in `source/operations/`. Every operation that varies per stack takes `stack` as its first argument.
4345

4446
## Architecture
4547

architecture.md

Lines changed: 26 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Architecture Overview
22

3+
Index for the dAppBooster installer architecture. The detail lives in focused sub-docs under
4+
[`docs/architecture/`](./docs/architecture/) — open only the one you need rather than loading
5+
everything.
6+
7+
| Doc | Read it when you're… | Covers |
8+
|---|---|---|
9+
| [abstractions](./docs/architecture/abstractions.md) | touching the config model, operations, or shell exec | `Stack`/`StackConfig`, `FeatureDefinition` (`paths`, `requires`), operations layer, `exec`/`execFile`, security |
10+
| [data-flow](./docs/architecture/data-flow.md) | changing CLI routing or the step sequence | non-interactive validation/execution order, JSON output, interactive step flow |
11+
| [extending](./docs/architecture/extending.md) | adding a stack, feature, or operation | step-by-step checklists for each |
12+
313
## Tech Stack
414

515
| Category | Technology | Notes |
@@ -15,182 +25,37 @@
1525

1626
```
1727
source/
18-
cli.tsx Entry point: meow arg parsing, mode routing
19-
app.tsx Interactive TUI: step-based state machine
28+
cli.tsx Entry point: meow arg parsing, stack resolution, mode routing
29+
app.tsx Interactive TUI: step-based state machine, threads `stack` through every step
2030
nonInteractive.ts Non-interactive: validate flags → run operations → JSON
21-
info.ts --info JSON output for agent discovery
31+
info.ts --info JSON output for agent discovery (optionally filtered by stack)
2232
constants/
23-
config.ts Single source of truth: feature definitions, repo URL
33+
config.ts Single source of truth: Stack type, stackDefinitions, env-var overrides
2434
operations/
2535
exec.ts exec (shell) and execFile (no shell) helpers
26-
cloneRepo.ts Shallow clone, checkout latest tag, reinit git
27-
createEnvFile.ts Copy .env.example → .env.local
28-
installPackages.ts pnpm install / remove based on mode and features
29-
cleanupFiles.ts Remove files for deselected features, patch package.json
36+
cloneRepo.ts Clone (tag-latest OR branch), apply stack.removeAfterClone, rm .git, git init
37+
createEnvFile.ts Copy each stack's envFiles (with optional ifFeature gate)
38+
installPackages.ts Stack-aware: uses stack.packageManager (pnpm or npm)
39+
cleanupFiles.ts Dispatches to per-stack cleanup (cleanupEvmFiles / cleanupCantonFiles)
40+
installGuard.ts Removes the partial project dir if interrupted mid-scaffold
3041
index.ts Barrel export
3142
components/
3243
steps/ TUI step components (presentation-only)
44+
StackSelection.tsx First step: pick a stack (skipped if preselectedStack is passed)
3345
ProjectName.tsx Prompt for project name
34-
CloneRepo/CloneRepo.tsx Clone progress display
46+
CloneRepo/CloneRepo.tsx Clone progress display (receives stack)
3547
InstallationMode.tsx Full / Custom selection
36-
OptionalPackages.tsx Feature multiselect
37-
Install/Install.tsx Install progress display
38-
FileCleanup.tsx Cleanup progress display
39-
PostInstall.tsx Post-install instructions
48+
OptionalPackages.tsx Feature multiselect (per-stack, enforces feature dependencies)
49+
Install/Install.tsx Install progress display (receives stack)
50+
FileCleanup.tsx Cleanup progress display (receives stack)
51+
PostInstall.tsx Post-install instructions, stack-specific
4052
Ask.tsx Text input with validation
4153
Divider.tsx Section divider
4254
MainTitle.tsx Gradient title banner
4355
Multiselect/ Checkbox multiselect component
4456
types/
4557
types.ts Shared TypeScript types
4658
utils/
47-
utils.ts Validation, path helpers, package resolution
59+
utils.ts Stack-aware helpers, feature-dependency resolution, validation, path helpers
4860
__tests__/ Mirrors source/ layout
49-
nonInteractive.test.ts
50-
info.test.ts
51-
utils.test.ts
52-
operations/
53-
exec.test.ts
54-
cloneRepo.test.ts
55-
createEnvFile.test.ts
56-
installPackages.test.ts
57-
cleanupFiles.test.ts
58-
```
59-
60-
## Key Abstractions
61-
62-
### Feature Definitions (`source/constants/config.ts`)
63-
64-
Single source of truth for feature metadata. All programmatic consumers (`--info`, validation, TUI multiselect, operations) read from here. CLI `--help` text maintains its own copy.
65-
66-
```ts
67-
featureDefinitions: Record<FeatureName, {
68-
description: string // --info output
69-
label: string // TUI multiselect display
70-
packages: string[] // pnpm packages to remove when deselected
71-
default: boolean // --info output
72-
postInstall?: string[] // post-install instructions for non-interactive JSON output
73-
}>
74-
```
75-
76-
`featureNames` is derived as `Object.keys(featureDefinitions)`.
77-
78-
When adding a new feature, add it here. Programmatic consumers (validation, info output, TUI selection) pick it up automatically — except `cleanupFiles.ts` (which needs explicit cleanup rules) and the CLI `--help` text in `cli.tsx` (which maintains its own copy).
79-
80-
### Operations Layer (`source/operations/`)
81-
82-
Plain async functions with no UI dependencies. Each operation receives explicit arguments (project folder, mode, features) and performs file system or shell work. Multi-step operations accept an optional `onProgress` callback that the TUI uses to render per-step progress; the non-interactive path omits it.
83-
84-
| Function | What it does |
85-
|---|---|
86-
| `cloneRepo(projectName, onProgress?)` | Shallow clone, fetch tags, checkout latest tag, rm .git, git init. Uses `execFile` (no shell) for git commands except `git checkout $(...)` which needs shell substitution. Uses `fs.rm` for .git removal. |
87-
| `createEnvFile(projectFolder)` | Copy .env.example to .env.local via `fs.copyFile` |
88-
| `installPackages(projectFolder, mode, features, onProgress?)` | Full: `pnpm i`. Custom with packages to remove: `pnpm remove` + postinstall. Custom with all features: `pnpm i`. Uses `execFile` exclusively (no shell). |
89-
| `cleanupFiles(projectFolder, mode, features, onProgress?)` | Remove files/folders for deselected features, patch package.json scripts, remove .install-files. Uses `node:fs/promises` (`rm`, `mkdir`, `copyFile`) for async operations; `patchPackageJson` uses sync `node:fs`. |
90-
91-
### Shell Execution (`source/operations/exec.ts`)
92-
93-
Two helpers with different security profiles:
94-
95-
- **`execFile(file, args, options)`** — wraps `child_process.spawn` without a shell. Arguments are passed as an array, so user input cannot be interpreted as shell metacharacters. Use this whenever user-provided values (e.g., `projectName`) appear in the command.
96-
- **`exec(command, options)`** — wraps `child_process.spawn` to run `/bin/sh -c <command>` (spawns a shell). Only for commands that require shell features like `$(...)` substitution. Never interpolate user input into the command string.
97-
98-
Both helpers use `spawn` with stdout ignored and stderr piped. They do not capture or return stdout — output is not buffered for the caller. They throw on non-zero exit codes with the stderr message, or report the signal name when the process is killed by a signal.
99-
100-
## Data Flow
101-
102-
### Non-interactive (agent)
103-
104-
```
105-
CLI flags (string)
106-
→ meow parses to typed flags
107-
→ validate() converts to { name, mode, features: FeatureName[] }
108-
→ operations receive typed args
109-
→ JSON output to stdout
110-
```
111-
112-
**Routing:** `source/cli.tsx`
113-
114-
```
115-
--info → source/info.ts → print JSON → exit 0
116-
--ni / !isTTY → source/nonInteractive.ts → validate → operations → JSON
117-
default → dynamic import ink + App → TUI
118-
```
119-
120-
**Non-interactive validation order:**
121-
1. `--name` required
122-
2. `--mode` required
123-
3. `--name` matches `/^[a-zA-Z0-9_]+$/`
124-
4. `--mode` is `full` or `custom`
125-
5. Full mode: skip to step 9 (features ignored, all installed)
126-
6. `--features` required for custom mode
127-
7. Parsed features list is non-empty (rejects trailing commas, whitespace-only entries)
128-
8. All feature names are valid keys in `featureDefinitions`
129-
9. Project directory does not already exist
130-
131-
**Non-interactive execution order:**
132-
`cloneRepo``createEnvFile``installPackages``cleanupFiles` → success JSON
133-
134-
Any error produces `{ "success": false, "error": "..." }` and exit code 1. Errors set `process.exitCode = 1` and throw rather than calling `process.exit()` directly, ensuring stdout flushes before the process terminates when piped.
135-
136-
**Success output:**
137-
```json
138-
{
139-
"success": true,
140-
"projectName": "...",
141-
"mode": "full|custom",
142-
"features": ["..."],
143-
"path": "/absolute/path",
144-
"postInstall": ["..."]
145-
}
14661
```
147-
148-
For full mode, `features` lists all feature names. For custom mode, only the selected ones.
149-
150-
### Interactive (human)
151-
152-
```
153-
User input via Ink components
154-
→ useState in App.tsx
155-
→ passed as props to step components
156-
→ components convert MultiSelectItem[] → FeatureName[]
157-
→ operations receive typed args
158-
→ Ink renders progress/status
159-
```
160-
161-
Steps: ProjectName → CloneRepo → InstallationMode → OptionalPackages → Install → FileCleanup → PostInstall
162-
163-
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.
164-
165-
## How to Add a New Feature
166-
167-
1. **`source/constants/config.ts`** — add entry to `featureDefinitions` with description, label, packages, default, and optional postInstall. Add the name to the `FeatureName` union type.
168-
169-
2. **`source/operations/cleanupFiles.ts`** — add a cleanup function and call it from `cleanupFiles()` when the feature is deselected. If the feature has scripts in package.json, add removal to `patchPackageJson`.
170-
171-
3. **`source/components/steps/PostInstall.tsx`** — if the feature has post-install instructions, add TUI rendering here. The component hardcodes its own display (richer than the `postInstall` strings in config), so new features with post-install steps need manual JSX.
172-
173-
4. **`source/cli.tsx`** — update the `--help` text to include the new feature name and description.
174-
175-
5. **Tests** — add test cases in `source/__tests__/operations/cleanupFiles.test.ts` for the new cleanup rules. The nonInteractive, info, installPackages, and utils tests pick up new features automatically since they read from `featureDefinitions`.
176-
177-
6. **Verify**`pnpm build && pnpm lint && pnpm test`
178-
179-
Steps 1 and 6 are always required. Steps 2-5 depend on whether the feature has cleanup rules, post-install instructions, or descriptions for `--help`.
180-
181-
## How to Add a New Operation
182-
183-
1. Create `source/operations/newOperation.ts` — export an async function. Use `execFile` for commands with user input, `exec` only when shell features are needed.
184-
185-
2. Export from `source/operations/index.ts`.
186-
187-
3. Call from `source/nonInteractive.ts` (in the execution sequence) and from the relevant TUI component.
188-
189-
4. Add tests in `source/__tests__/operations/newOperation.test.ts` — mock `exec`/`execFile` to verify correct commands.
190-
191-
## Security
192-
193-
- User input (`projectName`) is validated against `/^[a-zA-Z0-9_]+$/` before any use
194-
- Operations use `execFile` (no shell) for commands that include user input
195-
- `exec` (shell) is reserved for commands needing shell substitution, and never receives user input in the command string
196-
- Child process stdout is ignored and stderr is piped (captured for error diagnostics only), guaranteeing clean JSON on the parent's stdout

demo.svg

Lines changed: 1 addition & 1 deletion
Loading

0 commit comments

Comments
 (0)