Skip to content

Commit a6ed048

Browse files
Merge pull request #14 from BootNodeDev/refactor/codebase-sweep
fix: correct the install order, the husky feature and failure reporting
2 parents 1febf24 + 7f8cd8a commit a6ed048

37 files changed

Lines changed: 1184 additions & 983 deletions

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
A CLI installer tool for dAppBooster projects. It supports two **stacks** and two **modes**:
1212

1313
- **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.
14-
- **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.
14+
- **Interactive** (default): React + Ink TUI that prompts for the project name first, then the stack, then the 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.
1515
- **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.
1616

1717
## Stack & Conventions

architecture.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ everything.
66

77
| Doc | Read it when you're… | Covers |
88
|---|---|---|
9-
| [abstractions](./docs/architecture/abstractions.md) | touching the config model, operations, or shell exec | `Stack`/`StackConfig`, `FeatureDefinition` (`paths`, `requires`), operations layer, `exec`/`execFile`, security |
9+
| [abstractions](./docs/architecture/abstractions.md) | touching the config model, operations, or shell exec | `Stack`/`StackConfig` (`hygiene`, `staging`, `initialCommit`), `FeatureDefinition` (`paths`, `scripts`, `packages`, `requires`), operations layer, `exec`/`execFile`, security |
1010
| [data-flow](./docs/architecture/data-flow.md) | changing CLI routing or the step sequence | non-interactive validation/execution order, JSON output, interactive step flow |
1111
| [extending](./docs/architecture/extending.md) | adding a stack, feature, or operation | step-by-step checklists for each |
1212

@@ -38,18 +38,20 @@ source/
3838
cloneRepo.ts Clone (tag-latest OR branch), apply stack.removeAfterClone, rm .git, git init
3939
createEnvFile.ts Copy each stack's envFiles (with optional ifFeature gate)
4040
installPackages.ts Stack-aware: uses stack.packageManager (pnpm or npm)
41-
cleanupFiles.ts Dispatches to per-stack cleanup (cleanupEvmFiles / cleanupCantonFiles)
41+
cleanupFiles.ts Removes deselected features and patches package.json, before the install
42+
createInitialCommit.ts Commits the finished scaffold (stacks that ask for it)
4243
installGuard.ts Removes the partial project dir if interrupted mid-scaffold
4344
index.ts Barrel export
4445
components/
4546
steps/ TUI step components (presentation-only)
46-
StackSelection.tsx First step: pick a stack (skipped if preselectedStack is passed)
47-
ProjectName.tsx Prompt for project name
47+
ProjectName.tsx First step: prompt for the project name
48+
StackSelection.tsx Pick a stack (skipped when preselectedStack is passed)
4849
CloneRepo/CloneRepo.tsx Clone progress display (receives stack)
4950
InstallationMode.tsx Mode selection (Canton: Default/Full/Custom; EVM: Full/Custom)
5051
OptionalPackages.tsx Feature multiselect (per-stack; pre-checks default:true features)
51-
Install/Install.tsx Install progress display (receives stack)
52-
FileCleanup.tsx Cleanup progress display (receives stack)
52+
FileCleanup.tsx Cleanup progress display, runs before the install
53+
Install/Install.tsx Env files, package install and baseline commit
54+
StepProgress.tsx Shared runner for the operation steps: progress, errors, guard
5355
PostInstall.tsx Post-install instructions, stack-specific
5456
Ask.tsx Text input with validation
5557
Divider.tsx Section divider

docs/architecture/abstractions.md

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ type StackConfig = {
1717
packageManager: 'pnpm' | 'npm'
1818
removeAfterClone: string[] // paths nuked between clone and `git init` (empty for both stacks today)
1919
postInstall?: string[] // stack-level post-install guidance, shown for every scaffold (Canton run steps)
20-
envFiles: Array<{ from: string; to: string; ifFeature?: string }>
20+
hygiene?: { label: string; paths: string[] } // the template's own repo files, removed from every scaffold (EVM only)
21+
staging?: { label: string; paths: string[] } // where the template keeps replacement files; removed once cleanup is done (EVM's .install-files)
22+
initialCommit?: boolean // commit the finished scaffold as the project's baseline (Canton)
23+
envFiles: Array<{ from: string; to: string; ifFeature?: FeatureName }>
2124
features: Record<string, FeatureDefinition>
2225
}
2326
```
@@ -26,7 +29,9 @@ Installation modes are stack-aware via `getInstallationModes(stack)` — Canton
2629
2730
`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.
2831
29-
`getFeatureNames(stack)` and `isFeatureNameValid(stack, name)` are the per-stack feature accessors. There is no global `featureDefinitions` export — that would imply a single stack.
32+
`getFeatureNames(stack)`, `getFeatureEntries(stack)` and `isFeatureNameValid(stack, name)` are the per-stack feature accessors. There is no global `featureDefinitions` export — that would imply a single stack.
33+
34+
`FeatureName` is the union of every feature name both stacks define, taken from the `featureNamesByStack` list at the top of `config.ts`. The list exists so the config can refer to its own feature names by type (`requires`, `ifFeature`) without a circular reference; the `satisfies` clause on `stackDefinitions` requires the feature maps to hold exactly those keys, so the two cannot drift apart without a compile error. Renaming a feature therefore turns every stale `'oldName'` string in the codebase into a compile error. `isFeatureNameValid` is a type guard, so validated CLI input narrows from `string` to `FeatureName`. The type is deliberately not per-stack: passing an EVM feature name to a Canton call still compiles, and the runtime check in `nonInteractive.ts` catches it.
3035
3136
## Feature Definitions
3237
@@ -36,17 +41,20 @@ Stored inside each stack's `features` map. Shape:
3641
type FeatureDefinition = {
3742
description: string // --info output
3843
label: string // TUI multiselect display
39-
packages: string[] // package-manager packages to remove when deselected (empty for canton features today)
44+
packages: string[] // dependencies the package manager removes when the feature is deselected
4045
default: boolean // --info output
4146
postInstall?: string[] // post-install instructions for non-interactive JSON output
42-
paths?: string[] // files/dirs removed when the feature is deselected (Canton, data-driven cleanup)
47+
paths?: string[] // files/dirs removed when the feature is deselected
48+
scripts?: string[] // package.json scripts removed when the feature is deselected
4349
requires?: FeatureName[] // features this one depends on (one-directional, transitive)
4450
}
4551
```
4652
47-
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.
53+
When adding a new feature, add its name to `featureNamesByStack` and an entry to the stack's `features` map. Programmatic consumers pick it up automatically. Feature cleanup is data-driven from `paths` and `scripts` for both stacks, and removal of its `packages` is data-driven too (see the Operations Layer below), so a new feature usually needs no code. The one exception is EVM's `demo` and `subgraph`, which restore replacement source files from the template's staging directory. The CLI `--help` text in `cli.tsx` maintains its own copy either way.
54+
55+
`packages` are always removed by the package manager (`pnpm remove` / `npm uninstall`), which updates package.json and the lockfile together. Nothing hand-edits dependencies.
4856
49-
**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.
57+
**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), each with its `requires` resolved. The non-interactive path resolves it in `validate`; the interactive path resolves it once in `app.tsx` and passes the result to every step, so the review screen lists exactly what gets installed. `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.
5058
5159
## Operations Layer (`source/operations/`)
5260
@@ -56,12 +64,12 @@ Plain async functions, no UI dependencies. Each operation that varies per stack
5664
|---|---|
5765
| `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. |
5866
| `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). |
59-
| `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. |
60-
| `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 two `package.json` edits: **script stripping** by command target — any script whose command invokes a removed directory is dropped (so deselecting `carpincho` strips `wallet:dev` / `carpincho:build:extension`) — and **`workspaces` pruning**, dropping any workspace entry that points at a removed directory (both the `string[]` and `{ packages: string[] }` forms), so deselecting `carpincho` leaves no dangling `carpincho-wallet` workspace. 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. |
67+
| `installPackages(stack, projectFolder, mode, features, onProgress?)` | Uses `stack.packageManager`. Nothing to remove (full mode, or a selection that drops no packages): `<pm> install`. Otherwise `<pm> remove` (pnpm) or `<pm> uninstall` (npm), which prunes the manifest and the lockfile together, then `<pm> run postinstall` **only if the template defines that script** — the Canton template does not. Runs after `cleanupFiles`, so it resolves the pruned manifest once. `execFile` only — never shell. |
68+
| `cleanupFiles(stack, projectFolder, mode, features, onProgress?)` | Config-driven for both stacks, and runs **before** the install. First the stack's `hygiene` group, the paths belonging to the template's own repository (EVM: `.github` plus `.claude`, `AGENTS.md`, `CLAUDE.md`, `architecture.md`; Canton declares none, since it models those as its `github` and `llm` features). Then, in `default` and `custom` modes, it loops the stack's features and for each one the user left out removes its `paths` and collects its `scripts`. Removed **directories** drive two further package.json edits: **script stripping** by command target — any script whose command invokes a removed directory is dropped, so dropping `carpincho` strips `wallet:dev` and `carpincho:build:extension` — and **`workspaces` pruning**, dropping any entry pointing at a removed directory (both the `string[]` and `{ packages: string[] }` forms). Removed *files* never strip scripts, so a script that merely mentions `CLAUDE.md` survives. package.json is read once and written only when a value changed; dependencies are left to `installPackages`. EVM additionally restores the demo-free home page from the staged copies when `demo` or `subgraph` is dropped. Last comes the stack's `staging` group (EVM's `.install-files`), once the restores no longer need it. |
6169
6270
### Interrupt safety (`installGuard`)
6371
64-
`source/operations/installGuard.ts` makes a Ctrl+C mid-scaffold leave no partial directory behind. `beginInstall(projectFolder)` is called the instant disk work starts (before `cloneRepo`) and registers `SIGINT`/`SIGTERM` handlers; `completeInstall()` is called once cleanup finishes. On an interrupt while a scaffold is in progress, the handler removes the project directory; after `completeInstall` it is a no-op, so a finished project (or a Ctrl+C on the post-install screen) is never deleted. It only ever removes a directory created this run — both entry paths reject a pre-existing directory up front — so user data is never touched. Both paths wire it in: the non-interactive runner brackets its operation block, and interactively `CloneRepo` calls `beginInstall` while `FileCleanup` calls `completeInstall`.
72+
`source/operations/installGuard.ts` makes a Ctrl+C or a failure mid-scaffold leave no partial directory behind. `beginInstall(projectFolder)` is called the instant disk work starts (before `cloneRepo`) and registers `SIGINT`/`SIGTERM` handlers; `completeInstall()` is called once cleanup finishes; `abortInstall()` is called when an operation throws — it removes the partial directory and sets `process.exitCode = 1`, so a failed interactive run reports failure to the shell instead of exiting 0. The three interactive operation steps (`CloneRepo`, `Install`, `FileCleanup`) all call it from their `catch`. On an interrupt while a scaffold is in progress, the handler removes the project directory; after `completeInstall` it is a no-op, so a finished project (or a Ctrl+C on the post-install screen) is never deleted. It only ever removes a directory created this run — both entry paths reject a pre-existing directory up front — so user data is never touched. Both paths wire it in: the non-interactive runner brackets its operation block, and interactively `CloneRepo` calls `beginInstall` while `Install`, the last operation step, calls `completeInstall`.
6573
6674
## Shell Execution (`source/operations/exec.ts`)
6775

0 commit comments

Comments
 (0)