@nivustec/proteus 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +394 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +137 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +44 -0
- package/dist/injector.d.ts +10 -0
- package/dist/injector.js +157 -0
- package/dist/parser.d.ts +8 -0
- package/dist/parser.js +402 -0
- package/dist/tool-executor.d.ts +15 -0
- package/dist/tool-executor.js +52 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +42 -0
- package/dist/watcher.d.ts +2 -0
- package/dist/watcher.js +59 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Nivustec
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
|
|
2
|
+
<div align="center">
|
|
3
|
+
|
|
4
|
+
<img src="https://proteus.nivustec.com.br/icons/proteus.png" title="Proteus" alt="Proteus logo" width="128">
|
|
5
|
+
|
|
6
|
+
# Proteus
|
|
7
|
+
|
|
8
|
+
[🌍 Website](https://proteus.nivustec.com)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@nivustec/proteus"><img alt="npm" src="https://img.shields.io/npm/v/%40nivustec%2Fproteus.svg?color=2ea44f&label=npm" /></a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/@nivustec/proteus"><img alt="downloads" src="https://img.shields.io/npm/dm/%40nivustec%2Fproteus.svg?color=2ea44f" /></a>
|
|
14
|
+
<img alt="node" src="https://img.shields.io/badge/node-%3E%3D18.0.0-2ea44f" />
|
|
15
|
+
<img alt="ts" src="https://img.shields.io/badge/TypeScript-5.x-2ea44f" />
|
|
16
|
+
<a href="#license"><img alt="license" src="https://img.shields.io/badge/license-MIT-2ea44f" /></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
Automated data-testid injection for React (JS/TS).
|
|
20
|
+
Proteus “surfs” your codebase and transforms it on save/commit, enforcing qa_prefixed, unique, and semantic test IDs across your components.
|
|
21
|
+
<br/>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
### Why Proteus?
|
|
25
|
+
- Consistent, predictable, and unique test IDs
|
|
26
|
+
- Zero-runtime: IDs are injected at build/dev time
|
|
27
|
+
- Works with JSX/TSX and plain JS/TS React projects
|
|
28
|
+
- Safe defaults, with flexible strategies
|
|
29
|
+
|
|
30
|
+
### Features
|
|
31
|
+
- Two strategies only:
|
|
32
|
+
- safe-hash: compact ids like `qa_abc123`; in maps: ``qa_abc123_${item.id}``
|
|
33
|
+
- functional: semantic ids like ``qa_products_image_${item.id}``
|
|
34
|
+
- Uniqueness enforcement with sibling-indexing for repeated tags
|
|
35
|
+
- Map-awareness: automatically appends key/index for dynamic lists
|
|
36
|
+
- Watch mode (inject on save)
|
|
37
|
+
- Pre-commit integration (inject before commit)
|
|
38
|
+
- Default strategy: functional
|
|
39
|
+
- Default include: React frontend files only (`src/**/*.tsx`, `src/**/*.jsx`)
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm i -D @nivustec/proteus
|
|
47
|
+
# or
|
|
48
|
+
yarn add -D @nivustec/proteus
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For local development of Proteus itself:
|
|
52
|
+
```bash
|
|
53
|
+
# in the Proteus repo
|
|
54
|
+
npm run build
|
|
55
|
+
npm link
|
|
56
|
+
|
|
57
|
+
# in your app repo
|
|
58
|
+
npm link @nivustec/proteus
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
1) Initialize a config in your project root:
|
|
66
|
+
```bash
|
|
67
|
+
proteus init
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
2) Inject once:
|
|
71
|
+
```bash
|
|
72
|
+
proteus inject
|
|
73
|
+
# or specific files
|
|
74
|
+
proteus inject src/components/Button.tsx
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
3) Watch mode (auto-inject on save):
|
|
78
|
+
```bash
|
|
79
|
+
proteus inject --watch
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
4) Git pre-commit (Husky optional):
|
|
83
|
+
```bash
|
|
84
|
+
proteus setup # adds a pre-commit hook
|
|
85
|
+
git commit -m "feat: ..." # Proteus injects before commit
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Configuration
|
|
91
|
+
|
|
92
|
+
Create or edit `proteus.config.json` in your project root.
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"injectOnCommit": true,
|
|
97
|
+
"injectOnSave": true,
|
|
98
|
+
"include": ["src/**/*.tsx", "src/**/*.jsx"],
|
|
99
|
+
"exclude": ["node_modules/**", "dist/**"],
|
|
100
|
+
"strategy": "functional",
|
|
101
|
+
"verbose": false,
|
|
102
|
+
"detectReusableComponents": true,
|
|
103
|
+
"autoExcludePatterns": ["**/ui/**", "**/common/**"]
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- injectOnCommit: enable pre-commit injection
|
|
108
|
+
- injectOnSave: enable watch mode injection
|
|
109
|
+
- include/exclude: glob patterns for files
|
|
110
|
+
- strategy: `safe-hash` or `functional`
|
|
111
|
+
- verbose: enable additional logs in CLI mode
|
|
112
|
+
- **detectReusableComponents**: auto-detect and skip reusable UI components (default: `true`)
|
|
113
|
+
- **autoExcludePatterns**: glob patterns to auto-exclude (e.g., `["**/ui/**", "**/common/**"]`)
|
|
114
|
+
|
|
115
|
+
### 🆕 Reusable Components Detection (v1.1.0)
|
|
116
|
+
|
|
117
|
+
Proteus now automatically detects reusable components (like Button, Input, Card from UI libraries) and skips injecting data-testid directly in them. Instead, it injects at the usage sites, ensuring unique IDs for each instance.
|
|
118
|
+
|
|
119
|
+
**Detection criteria:**
|
|
120
|
+
- File is in a UI/common/shared folder (e.g., `/ui/`, `/common/`, `/shared/`)
|
|
121
|
+
- Uses `React.forwardRef` or `forwardRef<>`
|
|
122
|
+
- Has `{...props}` spread operator
|
|
123
|
+
|
|
124
|
+
**Example:**
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
// src/components/ui/button.tsx (auto-skipped by Proteus)
|
|
128
|
+
const Button = React.forwardRef(({ ...props }, ref) => {
|
|
129
|
+
return <button {...props} ref={ref} />; // ✅ No fixed data-testid
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// src/components/Hero.tsx (Proteus injects here)
|
|
133
|
+
<Button onClick={handleClick} data-testid="qa_hero_button_1_abc123">
|
|
134
|
+
Get Started
|
|
135
|
+
</Button>
|
|
136
|
+
<Button variant="outline" data-testid="qa_hero_button_2_def456">
|
|
137
|
+
View on GitHub
|
|
138
|
+
</Button>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Benefits:**
|
|
142
|
+
- ✅ No duplicate data-testid across multiple Button instances
|
|
143
|
+
- ✅ Each usage gets a unique, contextual ID
|
|
144
|
+
- ✅ Works with any UI library (shadcn, MUI, Ant Design, etc.)
|
|
145
|
+
- ✅ Zero configuration required (auto-detection enabled by default)
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Strategies
|
|
150
|
+
|
|
151
|
+
### functional (semantic, recommended)
|
|
152
|
+
|
|
153
|
+
Rules (simplified):
|
|
154
|
+
- Prefix: `qa_`
|
|
155
|
+
- Base: `qa_<component>_<role>_[<siblingIndex>]_[<descriptor>]`
|
|
156
|
+
- Dynamic lists (map): append key/index as the last segment
|
|
157
|
+
|
|
158
|
+
Examples:
|
|
159
|
+
```jsx
|
|
160
|
+
// Map wrapper element
|
|
161
|
+
<div className="item" key={item.id} data-testid={`qa_products_item_${item.id}`} />
|
|
162
|
+
|
|
163
|
+
// Image inside map
|
|
164
|
+
<img data-testid={`qa_products_image_${item.id}`} />
|
|
165
|
+
|
|
166
|
+
// Text containers inside map
|
|
167
|
+
<div data-testid={`qa_products_container_name_${item.id}`}>{item.name}</div>
|
|
168
|
+
<div data-testid={`qa_products_container_price_${item.id}`}>{formatCurrency(item.price)}</div>
|
|
169
|
+
|
|
170
|
+
// Duplicate siblings get indexed
|
|
171
|
+
<h1 data-testid={`qa_products_h1_1_${item.id}`}>One</h1>
|
|
172
|
+
<h1 data-testid={`qa_products_h1_2_${item.id}`}>Two</h1>
|
|
173
|
+
|
|
174
|
+
// Outside map, unique suffix may be added to avoid collisions
|
|
175
|
+
<div className="header" data-testid="qa_products_header_<uniq>" />
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### Before/After (functional)
|
|
179
|
+
|
|
180
|
+
```jsx
|
|
181
|
+
// Before
|
|
182
|
+
export function ProductCard({ item }) {
|
|
183
|
+
return (
|
|
184
|
+
<div className="card">
|
|
185
|
+
<img alt={item.name} />
|
|
186
|
+
<h2>{item.name}</h2>
|
|
187
|
+
<button className="add-to-cart">Add</button>
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// After (auto-injected)
|
|
193
|
+
export function ProductCard({ item }) {
|
|
194
|
+
return (
|
|
195
|
+
<div className="card" data-testid="qa_productcard_container_<uniq>">
|
|
196
|
+
<img alt={item.name} data-testid="qa_productcard_image_img-item-name_<uniq>" />
|
|
197
|
+
<h2 data-testid="qa_productcard_container_1_item-name_<uniq>">{item.name}</h2>
|
|
198
|
+
<button className="add-to-cart" data-testid="qa_productcard_button_add-to-cart_<uniq>">Add</button>
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### safe-hash (compact)
|
|
205
|
+
|
|
206
|
+
Rules:
|
|
207
|
+
- Prefix: `qa_`
|
|
208
|
+
- Base: `qa_<hash>` (stable per location)
|
|
209
|
+
- In maps: append key/index: ``qa_<hash>_${item.id}``
|
|
210
|
+
|
|
211
|
+
Examples:
|
|
212
|
+
```jsx
|
|
213
|
+
// Single element
|
|
214
|
+
<div data-testid="qa_ab12cd" />
|
|
215
|
+
|
|
216
|
+
// Inside map
|
|
217
|
+
<div data-testid={`qa_ab12cd_${item.id}`} />
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## CLI
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
proteus inject [files...] [options]
|
|
226
|
+
|
|
227
|
+
Options:
|
|
228
|
+
-c, --config <path> Path to config file
|
|
229
|
+
--include <paths> Files to include
|
|
230
|
+
--exclude <paths> Files to exclude
|
|
231
|
+
--strategy <type> safe-hash | functional
|
|
232
|
+
--verbose Enable verbose logs
|
|
233
|
+
--json Enable machine-readable JSON output
|
|
234
|
+
-w, --watch Watch mode for development
|
|
235
|
+
|
|
236
|
+
proteus pre-commit # injects on staged files (used by Husky)
|
|
237
|
+
proteus setup # configures Husky pre-commit hook
|
|
238
|
+
proteus init # writes default proteus.config.json
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Husky (Git hooks):
|
|
242
|
+
- Modern Husky (v7+): if `.husky/` exists, `proteus setup` writes `.husky/pre-commit` with `proteus pre-commit`.
|
|
243
|
+
- Legacy fallback: if `.husky/` is not detected, `proteus setup` configures `package.json` scripts/hooks.
|
|
244
|
+
|
|
245
|
+
- Pre-commit processes only staged React files (`*.jsx`, `*.tsx`).
|
|
246
|
+
- Watch mode observes the configured include patterns; defaults to `src/**/*.tsx` and `src/**/*.jsx`.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Agent-Friendly CLI (MCP Support)
|
|
251
|
+
|
|
252
|
+
Proteus can be used by AI agents and other automated systems via its machine-readable JSON output. Use the `--json` flag with the `inject` command to suppress human-readable logs and receive a structured JSON response.
|
|
253
|
+
|
|
254
|
+
### Usage with --json
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
proteus inject --json
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Example JSON Output
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"filesProcessed": 5,
|
|
265
|
+
"totalInjected": 12,
|
|
266
|
+
"errors": 0,
|
|
267
|
+
"errorMessages": []
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
If errors occur, they will be included in the `errorMessages` array:
|
|
272
|
+
|
|
273
|
+
```json
|
|
274
|
+
{
|
|
275
|
+
"filesProcessed": 1,
|
|
276
|
+
"totalInjected": 0,
|
|
277
|
+
"errors": 1,
|
|
278
|
+
"errorMessages": [
|
|
279
|
+
"Error processing src/components/BrokenComponent.tsx: Parser Error: Unexpected token (1:10)"
|
|
280
|
+
]
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Universal Agent (Tool Calling) Support
|
|
287
|
+
|
|
288
|
+
To enable any AI agent (Gemini, OpenAI, Anthropic, etc.) to interact with Proteus, we provide a universal JSON Schema for the `proteus_inject_tool` and a dedicated executor function. This approach removes any direct dependency on specific AI SDKs within the Proteus CLI itself.
|
|
289
|
+
|
|
290
|
+
### 1. Universal Contract (JSON Schema)
|
|
291
|
+
|
|
292
|
+
The `proteus_inject_tool`'s function declaration is available as a pure JSON Schema file at the root of the project:
|
|
293
|
+
|
|
294
|
+
* **`proteus-tool-schema.json`**: This file defines the structure and parameters of the `proteus_inject_tool` in a standard Open API/JSON Schema format, allowing any agent to understand how to call the tool.
|
|
295
|
+
|
|
296
|
+
### 2. Universal Executor
|
|
297
|
+
|
|
298
|
+
The `executeProteusCLI` function provides a clean API for executing the Proteus CLI and receiving its output in a machine-readable format.
|
|
299
|
+
|
|
300
|
+
* **`src/tool-executor.ts`**: This module exports the `executeProteusCLI` function.
|
|
301
|
+
|
|
302
|
+
### Example Consumer Agent Usage
|
|
303
|
+
|
|
304
|
+
Here's how a consumer agent (using any AI SDK) would typically integrate with Proteus:
|
|
305
|
+
|
|
306
|
+
1. **Load the JSON Schema:** The agent loads `proteus-tool-schema.json` to understand the tool's capabilities.
|
|
307
|
+
2. **Model Interaction:** The agent's model receives a user prompt and, based on the loaded schema, decides to call `proteus_inject_tool` with specific arguments.
|
|
308
|
+
3. **Execute the Tool:** The agent then calls the `executeProteusCLI` function from `src/tool-executor.ts` with the arguments provided by its model.
|
|
309
|
+
4. **Process Output:** The agent receives the JSON output from `executeProteusCLI` and can then use its model to generate a natural language response for the user.
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// Example of a consumer agent (conceptual, using a generic AI SDK)
|
|
313
|
+
import { readFileSync } from 'fs';
|
|
314
|
+
import { executeProteusCLI } from './src/tool-executor.js'; // Adjust path as needed
|
|
315
|
+
|
|
316
|
+
// 1. Load the Proteus tool schema
|
|
317
|
+
const proteusToolSchema = JSON.parse(readFileSync('proteus-tool-schema.json', 'utf-8'));
|
|
318
|
+
|
|
319
|
+
// Assume your AI SDK has a way to register tools and get model responses
|
|
320
|
+
async function runConsumerAgent(userPrompt: string) {
|
|
321
|
+
// --- Conceptual: AI Model decides to call the tool ---
|
|
322
|
+
// In a real scenario, your AI SDK would handle this based on the userPrompt
|
|
323
|
+
// and the registered proteusToolSchema.
|
|
324
|
+
// For demonstration, we'll simulate a tool call from the model.
|
|
325
|
+
|
|
326
|
+
const simulatedToolCallArgs = {
|
|
327
|
+
files: ['src/components/MyComponent.tsx'],
|
|
328
|
+
strategy: 'functional',
|
|
329
|
+
json_output: true, // Always true for tool calling
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
console.log(`Agent: Simulating tool call to proteus_inject_tool with args:`, simulatedToolCallArgs);
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
// 2. Execute the Proteus CLI via the universal executor
|
|
336
|
+
const toolOutput = await executeProteusCLI(simulatedToolCallArgs);
|
|
337
|
+
|
|
338
|
+
console.log(`Agent: Proteus CLI returned:`, toolOutput);
|
|
339
|
+
|
|
340
|
+
// --- Conceptual: AI Model processes tool output and generates response ---
|
|
341
|
+
// Your AI model would take toolOutput and generate a natural language response.
|
|
342
|
+
if (toolOutput.errors > 0) {
|
|
343
|
+
console.log(`Agent: I encountered ${toolOutput.errors} errors while injecting test IDs. Please check the logs.`);
|
|
344
|
+
} else {
|
|
345
|
+
console.log(`Agent: Successfully injected ${toolOutput.totalInjected} test IDs into ${toolOutput.filesProcessed} files.`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.error("Agent: Error executing Proteus CLI:", error);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Example usage
|
|
354
|
+
runConsumerAgent("Inject test IDs into src/components/MyComponent.tsx using the functional strategy.");
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Best Practices
|
|
360
|
+
- Commit generated IDs; they are part of your source (zero runtime)
|
|
361
|
+
- Choose one strategy per repo; we recommend `functional`
|
|
362
|
+
- Ensure `qa_` prefix uniqueness across layers and components
|
|
363
|
+
- Always append a key/index for dynamic maps
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Troubleshooting
|
|
368
|
+
|
|
369
|
+
1) npx proteus not found / CLI not running
|
|
370
|
+
- Ensure the package is installed in your project
|
|
371
|
+
- For local dev: build + `npm link` in Proteus repo, then `npm link @nivustec/proteus` in your app
|
|
372
|
+
|
|
373
|
+
2) Duplicate logs in watch mode
|
|
374
|
+
- Proteus debounces its own writes; if you still see duplicates, increase the debounce window in `watcher.ts`
|
|
375
|
+
|
|
376
|
+
3) Pre-commit too verbose
|
|
377
|
+
- Set `verbose: false` (default). Pre-commit runs in silent mode by design.
|
|
378
|
+
|
|
379
|
+
4) Pre-commit not injecting on non-React files
|
|
380
|
+
- By default, only `*.jsx` and `*.tsx` staged files are processed. Adjust `include` if you need other patterns.
|
|
381
|
+
|
|
382
|
+
5) IDs are inserted in the wrong place
|
|
383
|
+
- We use AST positions; if you find an edge-case, open an issue with a code sample.
|
|
384
|
+
|
|
385
|
+
6) Partial staging lost in pre-commit
|
|
386
|
+
- Current hook re-stages full files after injection. If you rely on partial staging (`git add -p`), consider disabling `injectOnCommit` and running `proteus inject` manually.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## License
|
|
391
|
+
|
|
392
|
+
MIT © Nivustec
|
|
393
|
+
|
|
394
|
+
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { loadConfig, validateConfig } from "./config.js";
|
|
4
|
+
import { injectTestIds, setupGitHooks, injectStagedFiles } from "./injector.js";
|
|
5
|
+
import { startWatchMode } from "./watcher.js";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program
|
|
10
|
+
.name("proteus")
|
|
11
|
+
.description("Proteus - The shape-shifting CLI for automatic data-testid injection")
|
|
12
|
+
.version(pkg.version);
|
|
13
|
+
program
|
|
14
|
+
.command("inject")
|
|
15
|
+
.description("Inject data-testid attributes into React components")
|
|
16
|
+
.argument("[files...]", "Specific files to process")
|
|
17
|
+
.option("-c, --config <path>", "Path to config file")
|
|
18
|
+
.option("--include <paths...>", "Files to include")
|
|
19
|
+
.option("--exclude <paths...>", "Files to exclude")
|
|
20
|
+
.option("--strategy <type>", "ID generation strategy (safe-hash | functional)")
|
|
21
|
+
.option("--verbose", "Enable verbose logs")
|
|
22
|
+
.option("--json", "Enable machine-readable JSON output")
|
|
23
|
+
.option("-w, --watch", "Watch mode for development")
|
|
24
|
+
.action(async (files, options) => {
|
|
25
|
+
try {
|
|
26
|
+
// When JSON output is enabled, automatically silence any other logs.
|
|
27
|
+
const silent = options.json;
|
|
28
|
+
let config = loadConfig(options.config, silent);
|
|
29
|
+
if (options.include)
|
|
30
|
+
config.include = options.include;
|
|
31
|
+
if (options.exclude)
|
|
32
|
+
config.exclude = options.exclude;
|
|
33
|
+
if (options.strategy)
|
|
34
|
+
config.strategy = options.strategy;
|
|
35
|
+
if (options.verbose !== undefined)
|
|
36
|
+
config.verbose = options.verbose;
|
|
37
|
+
if (options.json !== undefined)
|
|
38
|
+
config.json = options.json;
|
|
39
|
+
// Verbose and JSON modes are mutually exclusive.
|
|
40
|
+
if (config.verbose && config.json) {
|
|
41
|
+
config.verbose = false;
|
|
42
|
+
}
|
|
43
|
+
validateConfig(config, silent);
|
|
44
|
+
if (options.watch) {
|
|
45
|
+
startWatchMode(config);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
if (!silent) {
|
|
49
|
+
console.log("🌊 Proteus is transforming your components...");
|
|
50
|
+
}
|
|
51
|
+
const stats = await injectTestIds(config, files);
|
|
52
|
+
if (silent) {
|
|
53
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.log("\n📊 Injection completed:");
|
|
57
|
+
console.log(` Files processed: ${stats.filesProcessed}`);
|
|
58
|
+
console.log(` Test IDs injected: ${stats.totalInjected}`);
|
|
59
|
+
console.log(` Errors: ${stats.errors}`);
|
|
60
|
+
if (stats.errors > 0) {
|
|
61
|
+
console.log("\n❌ Errors occurred:");
|
|
62
|
+
stats.errorMessages.forEach(msg => console.log(` - ${msg}`));
|
|
63
|
+
}
|
|
64
|
+
if (stats.totalInjected === 0) {
|
|
65
|
+
console.log("💡 No new test IDs were injected. All elements may already have data-testid attributes.");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const isJson = options.json;
|
|
72
|
+
if (isJson) {
|
|
73
|
+
console.log(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2));
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.error("❌ Error:", error instanceof Error ? error.message : error);
|
|
77
|
+
}
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
program
|
|
82
|
+
.command("pre-commit")
|
|
83
|
+
.description("Check and inject data-testid in staged files (for Git hooks)")
|
|
84
|
+
.action(async () => {
|
|
85
|
+
try {
|
|
86
|
+
const config = loadConfig(undefined, true);
|
|
87
|
+
config.verbose = false;
|
|
88
|
+
if (!config.injectOnCommit) {
|
|
89
|
+
console.log("ℹ️ injectOnCommit is disabled - skipping pre-commit check");
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
const stats = await injectStagedFiles();
|
|
93
|
+
if (stats.errors > 0) {
|
|
94
|
+
console.error("\n❌ Errors occurred during pre-commit injection:");
|
|
95
|
+
stats.errorMessages.forEach(msg => console.error(` - ${msg}`));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
console.error("❌ Pre-commit check failed:", error);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
program
|
|
105
|
+
.command("setup")
|
|
106
|
+
.description("Setup Git hooks for automatic injection")
|
|
107
|
+
.action(() => {
|
|
108
|
+
try {
|
|
109
|
+
setupGitHooks();
|
|
110
|
+
console.log("✅ Setup completed successfully");
|
|
111
|
+
console.log("📋 Git hooks configured for automatic data-testid injection");
|
|
112
|
+
console.log("💡 Run 'proteus inject --watch' for development auto-injection");
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error("❌ Setup failed:", error);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
program
|
|
120
|
+
.command("init")
|
|
121
|
+
.description("Create default config file")
|
|
122
|
+
.action(() => {
|
|
123
|
+
const defaultConfig = {
|
|
124
|
+
injectOnCommit: true,
|
|
125
|
+
injectOnSave: true,
|
|
126
|
+
include: [
|
|
127
|
+
"src/**/*.tsx",
|
|
128
|
+
"src/**/*.jsx",
|
|
129
|
+
],
|
|
130
|
+
exclude: ["node_modules/**", "dist/**"],
|
|
131
|
+
strategy: "functional",
|
|
132
|
+
};
|
|
133
|
+
fs.writeFileSync("proteus.config.json", JSON.stringify(defaultConfig, null, 2));
|
|
134
|
+
console.log("✅ Created proteus.config.json");
|
|
135
|
+
console.log("💡 Run 'proteus setup' to configure Git hooks");
|
|
136
|
+
});
|
|
137
|
+
program.parse();
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "fs";
|
|
2
|
+
const defaultConfig = {
|
|
3
|
+
injectOnCommit: true,
|
|
4
|
+
injectOnSave: true,
|
|
5
|
+
include: ["src/**/*.tsx", "src/**/*.jsx"],
|
|
6
|
+
exclude: ["node_modules/**", "dist/**"],
|
|
7
|
+
strategy: "functional",
|
|
8
|
+
verbose: false,
|
|
9
|
+
detectReusableComponents: true,
|
|
10
|
+
autoExcludePatterns: [],
|
|
11
|
+
};
|
|
12
|
+
export function loadConfig(configPath, silent = false) {
|
|
13
|
+
const configFile = configPath || "proteus.config.json";
|
|
14
|
+
if (existsSync(configFile)) {
|
|
15
|
+
try {
|
|
16
|
+
const userConfig = JSON.parse(readFileSync(configFile, "utf-8"));
|
|
17
|
+
if (!silent)
|
|
18
|
+
console.log(`✅ Loaded config from ${configFile}`);
|
|
19
|
+
return { ...defaultConfig, ...userConfig };
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (!silent)
|
|
23
|
+
console.warn(`Warning: Could not parse ${configFile}, using defaults`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
if (!silent)
|
|
28
|
+
console.log(`ℹ️ Config file ${configFile} not found, using defaults`);
|
|
29
|
+
}
|
|
30
|
+
return defaultConfig;
|
|
31
|
+
}
|
|
32
|
+
export function validateConfig(config, silent = false) {
|
|
33
|
+
const validStrategies = ["safe-hash", "functional"];
|
|
34
|
+
if (config.strategy && !validStrategies.includes(config.strategy)) {
|
|
35
|
+
throw new Error(`Invalid strategy: ${config.strategy}`);
|
|
36
|
+
}
|
|
37
|
+
if (!silent) {
|
|
38
|
+
console.log(`✅ Config validated:`);
|
|
39
|
+
console.log(` - injectOnCommit: ${config.injectOnCommit}`);
|
|
40
|
+
console.log(` - injectOnSave: ${config.injectOnSave}`);
|
|
41
|
+
console.log(` - strategy: ${config.strategy}`);
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ProteuConfig } from "./types.js";
|
|
2
|
+
export interface InjectionStats {
|
|
3
|
+
filesProcessed: number;
|
|
4
|
+
totalInjected: number;
|
|
5
|
+
errors: number;
|
|
6
|
+
errorMessages: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function injectTestIds(config: ProteuConfig, specificFiles?: string[]): Promise<InjectionStats>;
|
|
9
|
+
export declare function injectStagedFiles(): Promise<InjectionStats>;
|
|
10
|
+
export declare function setupGitHooks(): void;
|