Skip to content

Commit 4baea82

Browse files
committed
docs: add architecture.md with full agent guide
Covers tech stack, project structure, key abstractions (feature definitions, operations layer, shell execution), data flow for both interactive and non-interactive paths, how to add features, how to add operations, and security patterns. Link from AGENTS.md.
1 parent 675349b commit 4baea82

2 files changed

Lines changed: 192 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ A CLI installer tool for dAppBooster projects. It supports two modes:
4343

4444
## Architecture
4545

46+
See [architecture.md](./architecture.md) for the full architecture guide, including data flow, how to add features, and security patterns.
47+
4648
Entry: `source/cli.tsx` — parses args with `meow`, routes between interactive and non-interactive paths.
4749

4850
- **Interactive path**: `source/app.tsx` — step-based state machine that renders each installer step in sequence via React + Ink

architecture.md

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# Architecture Overview
2+
3+
## Tech Stack
4+
5+
| Category | Technology | Notes |
6+
|----------|-----------|-------|
7+
| Framework | React + Ink | Terminal UI for interactive mode |
8+
| Language | TypeScript (strict mode) | Extends `@sindresorhus/tsconfig` |
9+
| Arg parsing | meow | CLI flag parsing, non-interactive mode |
10+
| Styling | Ink primitives | `<Box>`, `<Text>`, ink-gradient, ink-big-text |
11+
| Testing | Vitest + @vitest/coverage-v8 | |
12+
| Node | v20+ | See `.nvmrc` |
13+
14+
## Project Structure
15+
16+
```
17+
source/
18+
cli.tsx Entry point: meow arg parsing, mode routing
19+
app.tsx Interactive TUI: step-based state machine
20+
nonInteractive.ts Non-interactive: validate flags → run operations → JSON
21+
info.ts --info JSON output for agent discovery
22+
constants/
23+
config.ts Single source of truth: feature definitions, repo URL
24+
operations/
25+
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
30+
index.ts Barrel export
31+
components/
32+
steps/ TUI step components (presentation-only)
33+
ProjectName.tsx Prompt for project name
34+
CloneRepo/CloneRepo.tsx Clone progress display
35+
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
40+
Ask.tsx Text input with validation
41+
Divider.tsx Section divider
42+
MainTitle.tsx Gradient title banner
43+
Multiselect/ Checkbox multiselect component
44+
types/
45+
types.ts Shared TypeScript types
46+
utils/
47+
utils.ts Validation, path helpers, package resolution
48+
__tests__/ Mirrors source/ layout
49+
nonInteractive.test.ts
50+
info.test.ts
51+
utils.test.ts
52+
operations/
53+
cloneRepo.test.ts
54+
createEnvFile.test.ts
55+
installPackages.test.ts
56+
cleanupFiles.test.ts
57+
```
58+
59+
## Key Abstractions
60+
61+
### Feature Definitions (`source/constants/config.ts`)
62+
63+
Single source of truth for all feature metadata. Every consumer reads from here:
64+
65+
```ts
66+
featureDefinitions: Record<FeatureName, {
67+
description: string // --info output, --help text
68+
label: string // TUI multiselect display
69+
packages: string[] // pnpm packages to remove when deselected
70+
default: boolean // --info output
71+
postInstall?: string[] // post-install instructions for agents and TUI
72+
}>
73+
```
74+
75+
`featureNames` is derived as `Object.keys(featureDefinitions)`.
76+
77+
When adding a new feature, add it here. All other code (validation, cleanup, info output, TUI selection) picks it up automatically — except `cleanupFiles.ts` which needs explicit cleanup rules.
78+
79+
### Operations Layer (`source/operations/`)
80+
81+
Plain async functions with no UI dependencies. Each operation receives explicit arguments (project folder, mode, features) and performs file system or shell work.
82+
83+
| Function | What it does |
84+
|---|---|
85+
| `cloneRepo(projectName)` | Shallow clone, checkout latest tag, rm .git, git init |
86+
| `createEnvFile(projectFolder)` | Copy .env.example to .env.local |
87+
| `installPackages(projectFolder, mode, features)` | Full: `pnpm i`. Custom: `pnpm remove` deselected packages + postinstall |
88+
| `cleanupFiles(projectFolder, mode, features)` | Remove files/folders for deselected features, patch package.json scripts, remove .install-files |
89+
90+
### Shell Execution (`source/operations/exec.ts`)
91+
92+
Two helpers with different security profiles:
93+
94+
- **`execFile(file, args, options)`** — uses `child_process.execFile` (no 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.
95+
- **`exec(command, options)`** — uses `child_process.exec` (spawns a shell). Only for commands that require shell features like `$(...)` substitution. Never interpolate user input into the command string.
96+
97+
Both helpers capture stdout/stderr (no leaking to parent process) and throw on failure with the stderr message.
98+
99+
## Data Flow
100+
101+
### Non-interactive (agent)
102+
103+
```
104+
CLI flags (string)
105+
→ meow parses to typed flags
106+
→ validate() converts to { name, mode, features: FeatureName[] }
107+
→ operations receive typed args
108+
→ JSON output to stdout
109+
```
110+
111+
**Routing:** `source/cli.tsx`
112+
113+
```
114+
--info → source/info.ts → print JSON → exit 0
115+
--ni / !isTTY → source/nonInteractive.ts → validate → operations → JSON
116+
default → dynamic import ink + App → TUI
117+
```
118+
119+
**Non-interactive validation order:**
120+
1. `--name` required
121+
2. `--mode` required
122+
3. `--name` matches `/^[a-zA-Z0-9_]+$/`
123+
4. `--mode` is `full` or `custom`
124+
5. Full mode: skip to step 8 (features ignored, all installed)
125+
6. `--features` required for custom mode
126+
7. All feature names are valid keys in `featureDefinitions`
127+
8. Project directory does not already exist
128+
129+
**Non-interactive execution order:**
130+
`cloneRepo``createEnvFile``installPackages``cleanupFiles` → success JSON
131+
132+
Any error produces `{ "success": false, "error": "..." }` and exit code 1.
133+
134+
**Success output:**
135+
```json
136+
{
137+
"success": true,
138+
"projectName": "...",
139+
"mode": "full|custom",
140+
"features": ["..."],
141+
"path": "/absolute/path",
142+
"postInstall": ["..."]
143+
}
144+
```
145+
146+
For full mode, `features` lists all feature names. For custom mode, only the selected ones.
147+
148+
### Interactive (human)
149+
150+
```
151+
User input via Ink components
152+
→ useState in App.tsx
153+
→ passed as props to step components
154+
→ components convert MultiSelectItem[] → FeatureName[]
155+
→ operations receive typed args
156+
→ Ink renders progress/status
157+
```
158+
159+
Steps: ProjectName → CloneRepo → InstallationMode → OptionalPackages → Install → FileCleanup → PostInstall
160+
161+
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.
162+
163+
## How to Add a New Feature
164+
165+
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.
166+
167+
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`.
168+
169+
3. **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`.
170+
171+
4. **Verify**`pnpm build && pnpm lint && pnpm test`
172+
173+
Steps 1 and 4 are always required. Steps 2-3 only apply if the feature has files/folders to clean up.
174+
175+
## How to Add a New Operation
176+
177+
1. Create `source/operations/newOperation.ts` — export an async function. Use `execFile` for commands with user input, `exec` only when shell features are needed.
178+
179+
2. Export from `source/operations/index.ts`.
180+
181+
3. Call from `source/nonInteractive.ts` (in the execution sequence) and from the relevant TUI component.
182+
183+
4. Add tests in `source/__tests__/operations/newOperation.test.ts` — mock `exec`/`execFile` to verify correct commands.
184+
185+
## Security
186+
187+
- User input (`projectName`) is validated against `/^[a-zA-Z0-9_]+$/` before any use
188+
- Operations use `execFile` (no shell) for commands that include user input
189+
- `exec` (shell) is reserved for commands needing shell substitution, and never receives user input in the command string
190+
- Non-interactive output suppresses child process stdout/stderr to guarantee clean JSON on stdout

0 commit comments

Comments
 (0)