@dcoder-x/vite 0.1.5 → 0.1.7
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/README.md +348 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# @dcoder-x/vite
|
|
2
|
+
|
|
3
|
+
Clippy build plugin for **Vite** (React, SvelteKit, Remix, and any Vite-based project). At compile time the plugin:
|
|
4
|
+
|
|
5
|
+
1. Injects stable `data-clippy-id` attributes into every interactive HTML element in your compiled output — never modifying your source files.
|
|
6
|
+
2. Analyzes your component tree to extract state variables, event handlers, and conditional render relationships.
|
|
7
|
+
3. Discovers all routes, navigation links, and user-flow paths.
|
|
8
|
+
4. Emits two JSON artifacts — `clippy-policy.json` and `clippy-selectors.json` — that the Clippy runtime uses to execute user prompts locally without LLM calls.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @dcoder-x/vite
|
|
16
|
+
# or
|
|
17
|
+
pnpm add @dcoder-x/vite
|
|
18
|
+
# or
|
|
19
|
+
yarn add @dcoder-x/vite
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`@dcoder-x/plugin-shared` is a transitive dependency and is installed automatically. You do not need to install it separately.
|
|
23
|
+
|
|
24
|
+
**Peer dependencies** (already present in any Vite project):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install --save-dev vite @babel/parser @babel/traverse
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
Add `clippyVitePlugin` to your Vite plugin array:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
// vite.config.ts
|
|
38
|
+
import { defineConfig } from 'vite'
|
|
39
|
+
import react from '@vitejs/plugin-react'
|
|
40
|
+
import { clippyVitePlugin } from '@dcoder-x/vite'
|
|
41
|
+
|
|
42
|
+
export default defineConfig({
|
|
43
|
+
plugins: [
|
|
44
|
+
react(),
|
|
45
|
+
clippyVitePlugin({
|
|
46
|
+
apiKey: process.env.CLIPPY_API_KEY!,
|
|
47
|
+
projectId: process.env.CLIPPY_PROJECT_ID!,
|
|
48
|
+
}),
|
|
49
|
+
],
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The plugin runs automatically during `vite build` — no additional scripts or CI steps required. It is a no-op during `vite dev` (apply mode is `'build'` only).
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
interface ClippyPluginOptions {
|
|
61
|
+
/** Your Clippy API key. Get this from the Clippy developer dashboard. */
|
|
62
|
+
apiKey: string
|
|
63
|
+
|
|
64
|
+
/** Your Clippy project ID. Found in the project settings page. */
|
|
65
|
+
projectId: string
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Skip uploading artifacts to the Clippy backend.
|
|
69
|
+
* Useful when you want to inspect the output before uploading.
|
|
70
|
+
* Artifacts are still written if localOutputDir is set.
|
|
71
|
+
* Default: false
|
|
72
|
+
*/
|
|
73
|
+
skipUpload?: boolean
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Only run the plugin during production builds (NODE_ENV === 'production').
|
|
77
|
+
* Default: false
|
|
78
|
+
*/
|
|
79
|
+
productionOnly?: boolean
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Write the generated JSON artifacts to a local directory in addition to
|
|
83
|
+
* (or instead of) uploading them.
|
|
84
|
+
*
|
|
85
|
+
* Two files are written:
|
|
86
|
+
* <localOutputDir>/clippy-policy.json
|
|
87
|
+
* <localOutputDir>/clippy-selectors.json
|
|
88
|
+
*
|
|
89
|
+
* Example: '.clippy-output'
|
|
90
|
+
*/
|
|
91
|
+
localOutputDir?: string
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* How to upload artifacts to the Clippy backend.
|
|
95
|
+
*
|
|
96
|
+
* 'split' — upload clippy-policy.json and clippy-selectors.json as separate
|
|
97
|
+
* requests. Recommended for large projects.
|
|
98
|
+
* 'single' — bundle both artifacts into one gzipped payload.
|
|
99
|
+
*
|
|
100
|
+
* Default: 'split'
|
|
101
|
+
*/
|
|
102
|
+
artifactUploadMode?: 'single' | 'split'
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Provide a custom upload function. When set, the built-in HTTP uploader
|
|
106
|
+
* is bypassed entirely. Use this to integrate with your own artifact
|
|
107
|
+
* storage, a proxy endpoint, or a CI/CD pipeline step.
|
|
108
|
+
*
|
|
109
|
+
* Example:
|
|
110
|
+
*
|
|
111
|
+
* uploadAdapter: {
|
|
112
|
+
* uploadArtifacts: async (artifacts) => {
|
|
113
|
+
* await myApi.uploadPolicy(artifacts.policy)
|
|
114
|
+
* await myApi.uploadSelectors(artifacts.selectorManifest)
|
|
115
|
+
* return { skipped: false, policyUploaded: true, selectorsUploaded: true, mode: 'split' }
|
|
116
|
+
* }
|
|
117
|
+
* }
|
|
118
|
+
*/
|
|
119
|
+
uploadAdapter?: {
|
|
120
|
+
uploadArtifacts?: (artifacts: PolicyArtifacts) => Promise<UploadResult>
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Recommended environment variable setup
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# .env (never commit API keys — use .env.local or CI secrets)
|
|
129
|
+
CLIPPY_API_KEY=pk_live_xxxxxxxxxxxx
|
|
130
|
+
CLIPPY_PROJECT_ID=proj_xxxxxxxxxxxx
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
In Vite, environment variables must be prefixed with `VITE_` to be exposed to the browser. Clippy's API key and project ID are **only used at build time** inside the plugin, so they do not need the `VITE_` prefix and should not be exposed to the browser.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
// Access in vite.config.ts via process.env (not import.meta.env)
|
|
137
|
+
clippyVitePlugin({
|
|
138
|
+
apiKey: process.env.CLIPPY_API_KEY!,
|
|
139
|
+
projectId: process.env.CLIPPY_PROJECT_ID!,
|
|
140
|
+
})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Output files
|
|
146
|
+
|
|
147
|
+
The plugin produces two files at the end of each build. Both share the same `buildId` so the Clippy runtime can link them to the active build.
|
|
148
|
+
|
|
149
|
+
### `clippy-selectors.json`
|
|
150
|
+
|
|
151
|
+
A flat, deduplicated manifest of every interactive element across all routes. Used for LLM context injection and selector health monitoring.
|
|
152
|
+
|
|
153
|
+
```jsonc
|
|
154
|
+
{
|
|
155
|
+
"version": "1.0.0",
|
|
156
|
+
"buildId": "abc123",
|
|
157
|
+
"generatedAt": "2026-06-02T10:00:00.000Z",
|
|
158
|
+
"selectors": [
|
|
159
|
+
{
|
|
160
|
+
// Stable ID injected as data-clippy-id in the compiled DOM.
|
|
161
|
+
// Format: ComponentName[-LabelText]-tag-lineNumber
|
|
162
|
+
"id": "SettingsPage-SaveChanges-button-205",
|
|
163
|
+
|
|
164
|
+
// CSS selector resolving this element in the live DOM.
|
|
165
|
+
"selector": "[data-clippy-id='SettingsPage-SaveChanges-button-205']",
|
|
166
|
+
|
|
167
|
+
"component": "SettingsPage",
|
|
168
|
+
"tag": "button",
|
|
169
|
+
|
|
170
|
+
// Human-readable label from aria-label, visible text, or placeholder.
|
|
171
|
+
// null when no static text is available.
|
|
172
|
+
"label": "Save Changes",
|
|
173
|
+
|
|
174
|
+
// All routes where this element is rendered.
|
|
175
|
+
// Shared components list multiple routes here rather than repeating.
|
|
176
|
+
"routes": ["/dashboard/settings"]
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### `clippy-policy.json`
|
|
183
|
+
|
|
184
|
+
The full knowledge document used by the Policy Executor for Tier 1 (no-LLM) flow execution and by the backend for LLM context enrichment.
|
|
185
|
+
|
|
186
|
+
```jsonc
|
|
187
|
+
{
|
|
188
|
+
"version": "1.0.0",
|
|
189
|
+
"buildId": "abc123",
|
|
190
|
+
"generatedAt": "2026-06-02T10:00:00.000Z",
|
|
191
|
+
"bundler": "vite",
|
|
192
|
+
|
|
193
|
+
// All discovered routes with relative file paths.
|
|
194
|
+
"routes": [
|
|
195
|
+
{
|
|
196
|
+
"path": "/dashboard/settings",
|
|
197
|
+
"filePath": "src/pages/settings.tsx",
|
|
198
|
+
"isDynamic": false,
|
|
199
|
+
"params": [],
|
|
200
|
+
"layout": null,
|
|
201
|
+
"routerType": "react-router",
|
|
202
|
+
"semantic": "settings dashboard"
|
|
203
|
+
}
|
|
204
|
+
],
|
|
205
|
+
|
|
206
|
+
// All interactive elements with selector candidates.
|
|
207
|
+
"selectors": [ /* ... */ ],
|
|
208
|
+
|
|
209
|
+
// Components with meaningful state or interaction data.
|
|
210
|
+
// Purely presentational components (Button, Card, etc.) are excluded.
|
|
211
|
+
"components": [
|
|
212
|
+
{
|
|
213
|
+
"name": "SettingsPage",
|
|
214
|
+
"filePath": "src/pages/settings.tsx",
|
|
215
|
+
"route": "/dashboard/settings",
|
|
216
|
+
"stateVariables": [
|
|
217
|
+
{ "name": "isDirty", "setter": "setIsDirty" }
|
|
218
|
+
],
|
|
219
|
+
"interactions": [
|
|
220
|
+
{
|
|
221
|
+
"trigger": { "event": "onClick", "element": "button", "setsState": "isDirty" },
|
|
222
|
+
"effect": {
|
|
223
|
+
"type": "asyncEffect",
|
|
224
|
+
"waitStrategy": "domSettle",
|
|
225
|
+
"settleMs": 300
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
],
|
|
231
|
+
|
|
232
|
+
// Multi-step navigation flows inferred from Link/anchor elements.
|
|
233
|
+
"flows": [
|
|
234
|
+
{
|
|
235
|
+
"flowId": "flow_1",
|
|
236
|
+
"page": "/auth/login",
|
|
237
|
+
"intentPatterns": ["log in", "sign in", "login"],
|
|
238
|
+
"steps": [
|
|
239
|
+
{ "step": 1, "action": "navigate", "target": "[data-clippy-id='LoginPage-form-115']" },
|
|
240
|
+
{ "step": 2, "action": "transition", "target": "[data-clippy-id='Dashboard-a-42']" }
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
]
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## How stable selectors work
|
|
250
|
+
|
|
251
|
+
The plugin transforms JSX at compile time — **not** your source files — to add two attributes to every native HTML element (`button`, `input`, `a`, `form`, `select`, `textarea`, `label`):
|
|
252
|
+
|
|
253
|
+
```html
|
|
254
|
+
<!-- Your source (never modified): -->
|
|
255
|
+
<button>Save Changes</button>
|
|
256
|
+
|
|
257
|
+
<!-- Compiled output (injected by the plugin): -->
|
|
258
|
+
<button
|
|
259
|
+
data-clippy-id="SettingsPage-SaveChanges-button-205"
|
|
260
|
+
data-clippy-component="SettingsPage"
|
|
261
|
+
>
|
|
262
|
+
Save Changes
|
|
263
|
+
</button>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The `data-clippy-id` format is `ComponentName[-LabelText]-tag-lineNumber`:
|
|
267
|
+
|
|
268
|
+
- **ComponentName** — the enclosing React component, or a route-derived name for page-level components.
|
|
269
|
+
- **LabelText** — the element's visible text or `aria-label`, sanitized to TitleCase, max two words. Omitted when no static text is available.
|
|
270
|
+
- **tag** — the lowercase HTML tag.
|
|
271
|
+
- **lineNumber** — the element's position in the source file, acting as a stable tiebreaker when multiple elements share the same name.
|
|
272
|
+
|
|
273
|
+
IDs are stable across builds as long as the component name and the element's file position don't change. CSS class hashes change with every Vite build; `data-clippy-id` does not.
|
|
274
|
+
|
|
275
|
+
React component calls (`<Button />`, `<Modal />`) are never touched — only native HTML tags receive injected attributes.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Router support
|
|
280
|
+
|
|
281
|
+
| Router | Detection | Notes |
|
|
282
|
+
|---|---|---|
|
|
283
|
+
| React Router | AST — `createBrowserRouter`, `<Route path>` | Detected in `src/` |
|
|
284
|
+
| TanStack Router | Filesystem — `src/routes/**/*.tsx` | Dot-separated filenames |
|
|
285
|
+
| Pages Router (Next.js via Vite) | Filesystem — `pages/**/*.tsx` | |
|
|
286
|
+
| App Router (Next.js via Vite) | Filesystem — `app/**/page.tsx` | Prefer `@dcoder-x/next` for Next.js |
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Using local output for inspection
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
clippyVitePlugin({
|
|
294
|
+
apiKey: process.env.CLIPPY_API_KEY!,
|
|
295
|
+
projectId: process.env.CLIPPY_PROJECT_ID!,
|
|
296
|
+
localOutputDir: '.clippy-output',
|
|
297
|
+
skipUpload: true,
|
|
298
|
+
})
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Add `.clippy-output` to `.gitignore`.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## CI/CD
|
|
306
|
+
|
|
307
|
+
The plugin runs at the end of `vite build` and uploads automatically. To control the upload yourself:
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
clippyVitePlugin({
|
|
311
|
+
apiKey: process.env.CLIPPY_API_KEY!,
|
|
312
|
+
projectId: process.env.CLIPPY_PROJECT_ID!,
|
|
313
|
+
localOutputDir: 'dist/clippy',
|
|
314
|
+
skipUpload: true,
|
|
315
|
+
uploadAdapter: {
|
|
316
|
+
uploadArtifacts: async (artifacts) => {
|
|
317
|
+
// your upload logic
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
})
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Troubleshooting
|
|
326
|
+
|
|
327
|
+
**`data-clippy-id` is not in the DOM**
|
|
328
|
+
|
|
329
|
+
Confirm you are inspecting the production build output (`vite build`), not the dev server. The plugin only runs during the build phase.
|
|
330
|
+
|
|
331
|
+
**Selectors show `button:contains(...)` instead of `data-clippy-id`**
|
|
332
|
+
|
|
333
|
+
The `text:contains()` fallback appears when the injected ID can't be matched back to a discovered element. Adding `aria-label` to the element resolves this permanently.
|
|
334
|
+
|
|
335
|
+
**No flows are generated**
|
|
336
|
+
|
|
337
|
+
Flows are inferred from `<a href>` and `<Link to/href>` elements pointing to known routes. If your navigation uses programmatic `router.push()` without visible links, flows will not be automatically detected. Consider adding visible navigation links or using the Clippy dashboard to record flows explicitly.
|
|
338
|
+
|
|
339
|
+
**Upload is failing**
|
|
340
|
+
|
|
341
|
+
Verify `CLIPPY_API_KEY` and `CLIPPY_PROJECT_ID` are set in your build environment (not in `VITE_`-prefixed variables, which are client-only). Set `localOutputDir` to confirm artifacts are generating correctly before troubleshooting the upload.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Related packages
|
|
346
|
+
|
|
347
|
+
- [`@dcoder-x/next`](https://www.npmjs.com/package/@dcoder-x/next) — same plugin for Next.js projects
|
|
348
|
+
- [`@dcoder-x/plugin-shared`](https://www.npmjs.com/package/@dcoder-x/plugin-shared) — internal shared extractors (not needed directly)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dcoder-x/vite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@dcoder-x/plugin-shared": "^0.1.
|
|
21
|
+
"@dcoder-x/plugin-shared": "^0.1.8"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"vite": ">=4.0.0",
|