|
1 | | -# Safe CLI Development Guide |
2 | | - |
3 | | -## 🚨 CRITICAL SAFETY WARNING 🚨 |
4 | | - |
5 | | -**NEVER run tests without isolated storage!** Integration tests were previously written in a dangerous way that could **DELETE YOUR ACTUAL WALLET DATA AND SAFE CONFIGURATIONS**. |
6 | | - |
7 | | -### Mandatory Safety Rules: |
8 | | - |
9 | | -1. **ALL integration tests MUST use `createTestStorage()`** from `src/tests/helpers/test-storage.ts` |
10 | | -2. **NEVER instantiate storage classes without the `cwd` option** in test mode |
11 | | -3. **ALWAYS verify tests are using `/tmp` directories** before running |
12 | | -4. **Backup your config before running tests** if unsure |
13 | | - |
14 | | -The storage classes now have built-in safety checks that will throw an error if you try to use non-temp directories in test mode. |
15 | | - |
16 | | -### Safe Test Pattern (REQUIRED): |
17 | | - |
18 | | -```typescript |
19 | | -import { createTestStorage } from '../helpers/test-storage.js' |
20 | | -import { WalletStorageService } from '../../storage/wallet-store.js' |
21 | | - |
22 | | -describe('My Test', () => { |
23 | | - let testStorage: ReturnType<typeof createTestStorage> |
24 | | - let walletStorage: WalletStorageService |
25 | | - |
26 | | - beforeEach(() => { |
27 | | - // REQUIRED: Create isolated test storage |
28 | | - testStorage = createTestStorage('my-test') |
29 | | - walletStorage = new WalletStorageService({ cwd: testStorage.configDir }) |
30 | | - }) |
31 | | - |
32 | | - afterEach(() => { |
33 | | - // REQUIRED: Cleanup test directories |
34 | | - testStorage.cleanup() |
35 | | - }) |
36 | | -}) |
37 | | -``` |
38 | | - |
39 | | -### Dangerous Pattern (FORBIDDEN): |
40 | | - |
41 | | -```typescript |
42 | | -// ❌ NEVER DO THIS IN TESTS - touches real user config! |
43 | | -const walletStorage = new WalletStorageService() |
44 | | -walletStorage.getAllWallets().forEach(w => walletStorage.removeWallet(w.id)) // DELETES REAL DATA! |
45 | | -``` |
46 | | - |
47 | | -## Pre-Commit Checklist |
48 | | - |
49 | | -Run the following commands before committing: |
50 | | - |
51 | | -```bash |
52 | | -npm run lint # Check code style and potential issues |
53 | | -npm run format # Format code with Prettier |
54 | | -npm run typecheck # Run TypeScript type checking |
55 | | -npm run test # Run unit and integration tests |
56 | | -``` |
57 | | - |
58 | | -If any errors pop up, fix them before committing. |
59 | | - |
60 | | -## Development Workflow |
61 | | - |
62 | | -### Testing |
63 | | - |
64 | | -#### Unit Tests |
65 | | -Unit tests are located in `src/tests/unit/` and cover: |
66 | | -- Services (`src/services/*`) |
67 | | -- Utilities (`src/utils/*`) |
68 | | -- Storage (`src/storage/*`) |
69 | | - |
70 | | -Run unit tests: |
71 | | -```bash |
72 | | -npm test # Run all tests (excluding integration/e2e) |
73 | | -npm test -- --watch # Run tests in watch mode |
74 | | -npm test -- --ui # Run tests with Vitest UI |
75 | | -``` |
76 | | - |
77 | | -#### Integration Tests |
78 | | -Integration tests are in `src/tests/integration/` and test: |
79 | | -- Full workflows (wallet import, Safe creation, transaction lifecycle) |
80 | | -- Service integration |
81 | | -- Storage integration |
82 | | -- Transaction building and parsing |
83 | | - |
84 | | -Run integration tests explicitly (they require blockchain access): |
85 | | -```bash |
86 | | -npm test src/tests/integration/integration-*.test.ts |
87 | | -``` |
88 | | - |
89 | | -#### E2E Tests |
90 | | -E2E tests verify the CLI commands work correctly: |
91 | | -- `e2e-cli.test.ts` - Basic CLI functionality |
92 | | -- `e2e-wallet-commands.test.ts` - Wallet operations |
93 | | -- `e2e-config-commands.test.ts` - Configuration management |
94 | | -- `e2e-account-commands.test.ts` - Account operations |
95 | | -- `e2e-tx-commands.test.ts` - Transaction commands |
96 | | -- `integration-full-workflow.test.ts` - Complete end-to-end workflow |
97 | | - |
98 | | -Run E2E tests: |
99 | | -```bash |
100 | | -# Build the CLI first |
101 | | -npm run build |
102 | | - |
103 | | -# Run E2E tests (requires TEST_WALLET_PK environment variable) |
104 | | -TEST_WALLET_PK=0x... npm test src/tests/integration/e2e-*.test.ts |
105 | | -``` |
106 | | - |
107 | | -#### Coverage |
108 | | -Check test coverage: |
109 | | -```bash |
110 | | -npm test -- --coverage # Generate coverage report |
111 | | -``` |
112 | | - |
113 | | -Coverage thresholds are configured in `vitest.config.ts`: |
114 | | -- Lines: 30% |
115 | | -- Functions: 69% |
116 | | -- Branches: 85% |
117 | | -- Statements: 30% |
118 | | - |
119 | | -### Project Structure |
120 | | - |
121 | | -``` |
122 | | -src/ |
123 | | -├── commands/ # CLI command implementations (0% coverage - tested via E2E) |
124 | | -│ ├── account/ # Safe account management |
125 | | -│ ├── config/ # Configuration management |
126 | | -│ ├── tx/ # Transaction operations |
127 | | -│ └── wallet/ # Wallet management |
128 | | -├── services/ # Business logic (87% coverage) |
129 | | -│ ├── abi-service.ts |
130 | | -│ ├── api-service.ts |
131 | | -│ ├── contract-service.ts |
132 | | -│ ├── ledger-service.ts |
133 | | -│ ├── safe-service.ts |
134 | | -│ ├── transaction-builder.ts |
135 | | -│ ├── transaction-service.ts |
136 | | -│ └── validation-service.ts |
137 | | -├── storage/ # Data persistence (81% coverage) |
138 | | -│ ├── config-store.ts |
139 | | -│ ├── safe-store.ts |
140 | | -│ ├── transaction-store.ts |
141 | | -│ └── wallet-store.ts |
142 | | -├── ui/ # CLI interface (0% coverage - interactive components) |
143 | | -│ ├── components/ |
144 | | -│ ├── hooks/ |
145 | | -│ └── screens/ |
146 | | -├── utils/ # Utilities (96% coverage) |
147 | | -│ ├── balance.ts |
148 | | -│ ├── eip3770.ts |
149 | | -│ ├── errors.ts |
150 | | -│ ├── ethereum.ts |
151 | | -│ └── validation.ts |
152 | | -└── tests/ |
153 | | - ├── fixtures/ # Test data and mocks |
154 | | - ├── helpers/ # Test utilities |
155 | | - ├── integration/ # Integration and E2E tests |
156 | | - └── unit/ # Unit tests |
157 | | -``` |
158 | | - |
159 | | -### Configuration and Storage |
160 | | - |
161 | | -If in the course of development or testing you need to clear or modify the local configs, back up the existing ones first, and restore them when finished. |
162 | | - |
163 | | -Configuration is stored in: |
164 | | -- Config: `~/.config/@safe-global/safe-cli/config.json` |
165 | | -- Data: `~/.local/share/@safe-global/safe-cli/` |
166 | | - |
167 | | -For testing with isolated directories, use `XDG_CONFIG_HOME` and `XDG_DATA_HOME`: |
168 | | -```bash |
169 | | -XDG_CONFIG_HOME=/tmp/test-config XDG_DATA_HOME=/tmp/test-data npm run dev |
170 | | -``` |
171 | | - |
172 | | -### Adding New Features |
173 | | - |
174 | | -1. **Create the service/utility** - Write the core logic with tests |
175 | | -2. **Add storage layer** (if needed) - Implement data persistence |
176 | | -3. **Create command** - Implement the CLI command in `src/commands/` |
177 | | -4. **Add E2E test** - Verify the command works end-to-end |
178 | | -5. **Update documentation** - Add to README if user-facing |
179 | | - |
180 | | -### Debugging |
181 | | - |
182 | | -Run CLI in development mode: |
183 | | -```bash |
184 | | -npm run dev -- <command> # Run with tsx (fast reload) |
185 | | -DEBUG=* npm run dev -- <command> # Run with debug logging |
186 | | -``` |
187 | | - |
188 | | -Build and run production version: |
189 | | -```bash |
190 | | -npm run build |
191 | | -node dist/index.js <command> |
192 | | -``` |
193 | | - |
194 | | -### Code Style |
195 | | - |
196 | | -- TypeScript strict mode enabled |
197 | | -- ESLint for linting |
198 | | -- Prettier for formatting |
199 | | -- Husky for pre-commit hooks |
200 | | -- lint-staged for staged file checking |
201 | | - |
202 | | -### Common Patterns |
203 | | - |
204 | | -#### Error Handling |
205 | | -Use custom error classes from `src/utils/errors.ts`: |
206 | | -```typescript |
207 | | -import { ValidationError, SafeError } from '../utils/errors.js' |
208 | | - |
209 | | -throw new ValidationError('Invalid address format') |
210 | | -throw new SafeError('Failed to create Safe') |
211 | | -``` |
212 | | - |
213 | | -#### Address Validation |
214 | | -Support both plain and EIP-3770 addresses: |
215 | | -```typescript |
216 | | -import { parseEIP3770Address } from '../utils/eip3770.js' |
217 | | -import { validateAddress } from '../utils/validation.js' |
218 | | - |
219 | | -const { chainId, address } = parseEIP3770Address('sep:0x...') |
220 | | -validateAddress(address) // throws if invalid |
221 | | -``` |
222 | | - |
223 | | -#### Storage |
224 | | -All storage services follow the same pattern: |
225 | | -```typescript |
226 | | -import { ConfigStore } from '../storage/config-store.js' |
227 | | - |
228 | | -const store = new ConfigStore() |
229 | | -store.set('key', value) |
230 | | -const value = store.get('key') |
231 | | -``` |
232 | | - |
233 | | -### Testing Best Practices |
234 | | - |
235 | | -1. **Isolate test data** - Use temporary directories for test configs/data |
236 | | -2. **Mock external dependencies** - Mock API calls and blockchain interactions |
237 | | -3. **Test error cases** - Verify error handling and edge cases |
238 | | -4. **Use factories** - Use test helpers from `src/tests/helpers/factories.ts` |
239 | | -5. **Clean up after tests** - Remove temporary files/directories in `afterEach` |
240 | | - |
241 | | -### Environment Variables |
242 | | - |
243 | | -- `TEST_WALLET_PK` - Private key for E2E tests (Sepolia testnet) |
244 | | -- `XDG_CONFIG_HOME` - Custom config directory |
245 | | -- `XDG_DATA_HOME` - Custom data directory |
246 | | -- `NODE_ENV` - Set to 'test' during testing |
247 | | -- `CI` - Set to 'true' for non-interactive mode |
248 | | - |
249 | | -### Blockchain Testing |
250 | | - |
251 | | -E2E tests that interact with blockchain require: |
252 | | -- A funded Sepolia test wallet |
253 | | -- `TEST_WALLET_PK` environment variable set |
254 | | -- Network access to Sepolia RPC and Safe API |
255 | | - |
256 | | -Get Sepolia ETH: |
257 | | -- [Sepolia Faucet](https://sepoliafaucet.com/) |
258 | | -- [Alchemy Sepolia Faucet](https://sepoliafaucet.com/) |
259 | | - |
260 | | -### Troubleshooting |
261 | | - |
262 | | -**Tests timing out:** |
263 | | -- Increase timeout in test: `{ timeout: 60000 }` |
264 | | -- Check network connectivity |
265 | | -- Verify RPC endpoints are accessible |
266 | | - |
267 | | -**Interactive prompts in tests:** |
268 | | -- Use `CLITestHelper.execWithInput()` for tests with prompts |
269 | | -- Set `CI=true` environment variable for non-interactive mode |
270 | | -- Consider adding `--yes` flags to commands |
271 | | - |
272 | | -**Storage conflicts:** |
273 | | -- Use isolated directories with `XDG_CONFIG_HOME` and `XDG_DATA_HOME` |
274 | | -- Clean up in `afterEach` hooks |
275 | | -- Use `mkdtempSync()` for temporary directories |
| 1 | +Read @CLAUDE.md |
0 commit comments