nextjs-ide-helper 1.3.4 → 1.5.2
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 +181 -16
- package/lib/loader.js +122 -42
- package/lib/plugin.js +0 -3
- package/lib/withIdeButton.js +71 -24
- package/lib/withIdeButton.tsx +78 -28
- package/package.json +2 -2
- package/src/__tests__/loader.test.js +338 -2
- package/src/loader.js +122 -37
- package/src/withIdeButton.tsx +78 -28
- package/lib/__tests__/loader.test.js +0 -480
package/README.md
CHANGED
|
@@ -8,7 +8,8 @@ A Next.js plugin that automatically adds IDE buttons to React components in deve
|
|
|
8
8
|
- 🎯 **Smart Detection**: Only processes components in specified directories
|
|
9
9
|
- 🔧 **Zero Configuration**: Works out of the box with sensible defaults (Cursor as default IDE)
|
|
10
10
|
- 🏎️ **Development Only**: Only active in development mode, no production overhead
|
|
11
|
-
- 🎨 **Non-intrusive**:
|
|
11
|
+
- 🎨 **Non-intrusive**: Minimal blue dot indicators that don't disrupt your layout
|
|
12
|
+
- 👁️ **Toggle Visibility**: Floating button to show/hide all IDE dots instantly
|
|
12
13
|
- ⚡ **Hydration Safe**: No SSR/client hydration mismatches
|
|
13
14
|
- 📱 **TypeScript Support**: Full TypeScript definitions included
|
|
14
15
|
- 🔌 **Multi-IDE Support**: Supports Cursor, VS Code, WebStorm, and Atom
|
|
@@ -18,8 +19,7 @@ A Next.js plugin that automatically adds IDE buttons to React components in deve
|
|
|
18
19
|
## Installation
|
|
19
20
|
|
|
20
21
|
```bash
|
|
21
|
-
|
|
22
|
-
# or
|
|
22
|
+
npm install nextjs-ide-helper
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Quick Start
|
|
@@ -29,7 +29,7 @@ A Next.js plugin that automatically adds IDE buttons to React components in deve
|
|
|
29
29
|
Add the plugin to your `next.config.js`:
|
|
30
30
|
|
|
31
31
|
```javascript
|
|
32
|
-
const withIdeHelper = require('nextjs-ide-
|
|
32
|
+
const withIdeHelper = require('nextjs-ide-helper');
|
|
33
33
|
|
|
34
34
|
/** @type {import('next').NextConfig} */
|
|
35
35
|
const nextConfig = {
|
|
@@ -43,12 +43,30 @@ module.exports = withIdeHelper()(nextConfig);
|
|
|
43
43
|
|
|
44
44
|
All React components in your `src/components` directory will automatically get IDE buttons in development mode (defaults to Cursor IDE).
|
|
45
45
|
|
|
46
|
+
## User Interface
|
|
47
|
+
|
|
48
|
+
### IDE Indicator Dots
|
|
49
|
+
|
|
50
|
+
Each wrapped component displays a small **blue dot** (10x10px) in the top-right corner:
|
|
51
|
+
- **Click** the dot to open the component's source file in your IDE
|
|
52
|
+
- **Hover** to see the file path tooltip
|
|
53
|
+
- The dots are subtle (60% opacity) and brighten on hover
|
|
54
|
+
|
|
55
|
+
### Toggle Button
|
|
56
|
+
|
|
57
|
+
A **floating toggle button** appears in the bottom-right corner of your page:
|
|
58
|
+
- **Blue** = IDE dots are visible
|
|
59
|
+
- **Gray** = IDE dots are hidden
|
|
60
|
+
- **Click** to toggle all dots on/off instantly
|
|
61
|
+
|
|
62
|
+
This lets you quickly hide the dots when you want an unobstructed view of your app, then bring them back when needed.
|
|
63
|
+
|
|
46
64
|
## Configuration
|
|
47
65
|
|
|
48
66
|
You can customize the plugin behavior:
|
|
49
67
|
|
|
50
68
|
```javascript
|
|
51
|
-
const withIdeHelper = require('nextjs-ide-
|
|
69
|
+
const withIdeHelper = require('nextjs-ide-helper');
|
|
52
70
|
|
|
53
71
|
const nextConfig = {
|
|
54
72
|
// your existing config
|
|
@@ -57,7 +75,7 @@ const nextConfig = {
|
|
|
57
75
|
module.exports = withIdeHelper({
|
|
58
76
|
componentPaths: ['src/components', 'components', 'src/ui'], // directories to scan (supports glob patterns)
|
|
59
77
|
projectRoot: process.cwd(), // project root directory
|
|
60
|
-
importPath: 'nextjs-ide-
|
|
78
|
+
importPath: 'nextjs-ide-helper/withIdeButton', // import path for the HOC
|
|
61
79
|
enabled: process.env.NODE_ENV === 'development', // enable/disable
|
|
62
80
|
ideType: 'cursor' // IDE to use: 'cursor', 'vscode', 'webstorm', 'atom'
|
|
63
81
|
})(nextConfig);
|
|
@@ -69,7 +87,7 @@ module.exports = withIdeHelper({
|
|
|
69
87
|
|--------|------|---------|-------------|
|
|
70
88
|
| `componentPaths` | `string[]` | `['src/components']` | Directories to scan for React components (supports glob patterns) |
|
|
71
89
|
| `projectRoot` | `string` | `process.cwd()` | Root directory of your project |
|
|
72
|
-
| `importPath` | `string` | `'nextjs-ide-
|
|
90
|
+
| `importPath` | `string` | `'nextjs-ide-helper/withIdeButton'` | Import path for the withIdeButton HOC |
|
|
73
91
|
| `enabled` | `boolean` | `process.env.NODE_ENV === 'development'` | Enable/disable the plugin |
|
|
74
92
|
| `ideType` | `'cursor' \| 'vscode' \| 'webstorm' \| 'atom'` | `'cursor'` | IDE to open files in |
|
|
75
93
|
|
|
@@ -100,7 +118,7 @@ module.exports = withIdeHelper({
|
|
|
100
118
|
You can also manually wrap components:
|
|
101
119
|
|
|
102
120
|
```tsx
|
|
103
|
-
import { withIdeButton } from 'nextjs-ide-
|
|
121
|
+
import { withIdeButton } from 'nextjs-ide-helper';
|
|
104
122
|
|
|
105
123
|
const MyComponent = () => {
|
|
106
124
|
return <div>Hello World</div>;
|
|
@@ -119,7 +137,40 @@ export default withIdeButton(MyComponent, 'src/components/MyComponent.tsx', {
|
|
|
119
137
|
|
|
120
138
|
The plugin automatically detects and wraps all types of React component export patterns:
|
|
121
139
|
|
|
122
|
-
### Named Components
|
|
140
|
+
### Named Export Components
|
|
141
|
+
```tsx
|
|
142
|
+
// Named arrow function exports
|
|
143
|
+
export const Button = () => <button>Click me</button>;
|
|
144
|
+
export const Modal = () => <div>Modal content</div>;
|
|
145
|
+
|
|
146
|
+
// Named function exports
|
|
147
|
+
export function MyButton() {
|
|
148
|
+
return <button>Function Button</button>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function MyModal() {
|
|
152
|
+
return <div>Function Modal</div>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Mixed named and default exports
|
|
156
|
+
export const HeaderButton = () => <button>Header</button>;
|
|
157
|
+
|
|
158
|
+
const MainComponent = () => <div>Main</div>;
|
|
159
|
+
export default MainComponent;
|
|
160
|
+
|
|
161
|
+
// TypeScript named exports
|
|
162
|
+
interface ButtonProps {
|
|
163
|
+
onClick: () => void;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const TypedButton = ({ onClick }: ButtonProps) => (
|
|
167
|
+
<button onClick={onClick}>Typed Button</button>
|
|
168
|
+
);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Default Export Components
|
|
172
|
+
|
|
173
|
+
#### Named Components
|
|
123
174
|
```tsx
|
|
124
175
|
// Standard pattern
|
|
125
176
|
const MyComponent = () => <div>Hello</div>;
|
|
@@ -132,7 +183,7 @@ const MyComponent = function() {
|
|
|
132
183
|
export default MyComponent;
|
|
133
184
|
```
|
|
134
185
|
|
|
135
|
-
|
|
186
|
+
#### Direct Export Function Components
|
|
136
187
|
```tsx
|
|
137
188
|
// Named function
|
|
138
189
|
export default function MyComponent() {
|
|
@@ -145,7 +196,7 @@ export default function() {
|
|
|
145
196
|
}
|
|
146
197
|
```
|
|
147
198
|
|
|
148
|
-
|
|
199
|
+
#### Arrow Function Components
|
|
149
200
|
```tsx
|
|
150
201
|
// Anonymous arrow function
|
|
151
202
|
export default () => {
|
|
@@ -158,7 +209,7 @@ export default (props) => {
|
|
|
158
209
|
};
|
|
159
210
|
```
|
|
160
211
|
|
|
161
|
-
|
|
212
|
+
#### Class Components
|
|
162
213
|
```tsx
|
|
163
214
|
// Named class
|
|
164
215
|
export default class MyComponent extends React.Component {
|
|
@@ -175,7 +226,7 @@ export default class MyComponent extends Component<Props> {
|
|
|
175
226
|
}
|
|
176
227
|
```
|
|
177
228
|
|
|
178
|
-
|
|
229
|
+
#### TypeScript Components
|
|
179
230
|
```tsx
|
|
180
231
|
// Function with TypeScript
|
|
181
232
|
interface Props {
|
|
@@ -224,7 +275,7 @@ After (automatically transformed):
|
|
|
224
275
|
```tsx
|
|
225
276
|
// src/components/Button.tsx (transformed by the plugin)
|
|
226
277
|
import React from 'react';
|
|
227
|
-
import { withIdeButton } from 'nextjs-ide-
|
|
278
|
+
import { withIdeButton } from 'nextjs-ide-helper/withIdeButton';
|
|
228
279
|
|
|
229
280
|
const Button = ({ children, onClick }) => {
|
|
230
281
|
return (
|
|
@@ -240,6 +291,102 @@ export default withIdeButton(Button, 'src/components/Button.tsx', {
|
|
|
240
291
|
});
|
|
241
292
|
```
|
|
242
293
|
|
|
294
|
+
### Named Export Pattern
|
|
295
|
+
Before (your original named exports):
|
|
296
|
+
```tsx
|
|
297
|
+
// src/components/Buttons.tsx
|
|
298
|
+
export const PrimaryButton = ({ children, onClick }) => {
|
|
299
|
+
return (
|
|
300
|
+
<button className="primary" onClick={onClick}>
|
|
301
|
+
{children}
|
|
302
|
+
</button>
|
|
303
|
+
);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
export const SecondaryButton = ({ children, onClick }) => {
|
|
307
|
+
return (
|
|
308
|
+
<button className="secondary" onClick={onClick}>
|
|
309
|
+
{children}
|
|
310
|
+
</button>
|
|
311
|
+
);
|
|
312
|
+
};
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
After (automatically transformed):
|
|
316
|
+
```tsx
|
|
317
|
+
// src/components/Buttons.tsx (transformed by the plugin)
|
|
318
|
+
import { withIdeButton } from 'nextjs-ide-helper/withIdeButton';
|
|
319
|
+
|
|
320
|
+
export const PrimaryButton = withIdeButton(({ children, onClick }) => {
|
|
321
|
+
return (
|
|
322
|
+
<button className="primary" onClick={onClick}>
|
|
323
|
+
{children}
|
|
324
|
+
</button>
|
|
325
|
+
);
|
|
326
|
+
}, 'src/components/Buttons.tsx', {
|
|
327
|
+
projectRoot: '/path/to/project',
|
|
328
|
+
ideType: 'cursor'
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
export const SecondaryButton = withIdeButton(({ children, onClick }) => {
|
|
332
|
+
return (
|
|
333
|
+
<button className="secondary" onClick={onClick}>
|
|
334
|
+
{children}
|
|
335
|
+
</button>
|
|
336
|
+
);
|
|
337
|
+
}, 'src/components/Buttons.tsx', {
|
|
338
|
+
projectRoot: '/path/to/project',
|
|
339
|
+
ideType: 'cursor'
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Mixed Named and Default Exports
|
|
344
|
+
Before (mixed exports):
|
|
345
|
+
```tsx
|
|
346
|
+
// src/components/Layout.tsx
|
|
347
|
+
export const Header = () => <header>My App Header</header>;
|
|
348
|
+
export const Footer = () => <footer>© 2025 My App</footer>;
|
|
349
|
+
|
|
350
|
+
const Layout = ({ children }) => (
|
|
351
|
+
<div>
|
|
352
|
+
<Header />
|
|
353
|
+
<main>{children}</main>
|
|
354
|
+
<Footer />
|
|
355
|
+
</div>
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
export default Layout;
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
After (automatically transformed):
|
|
362
|
+
```tsx
|
|
363
|
+
// src/components/Layout.tsx (transformed by the plugin)
|
|
364
|
+
import { withIdeButton } from 'nextjs-ide-helper/withIdeButton';
|
|
365
|
+
|
|
366
|
+
export const Header = withIdeButton(() => <header>My App Header</header>, 'src/components/Layout.tsx', {
|
|
367
|
+
projectRoot: '/path/to/project',
|
|
368
|
+
ideType: 'cursor'
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
export const Footer = withIdeButton(() => <footer>© 2025 My App</footer>, 'src/components/Layout.tsx', {
|
|
372
|
+
projectRoot: '/path/to/project',
|
|
373
|
+
ideType: 'cursor'
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const Layout = ({ children }) => (
|
|
377
|
+
<div>
|
|
378
|
+
<Header />
|
|
379
|
+
<main>{children}</main>
|
|
380
|
+
<Footer />
|
|
381
|
+
</div>
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
export default withIdeButton(Layout, 'src/components/Layout.tsx', {
|
|
385
|
+
projectRoot: '/path/to/project',
|
|
386
|
+
ideType: 'cursor'
|
|
387
|
+
});
|
|
388
|
+
```
|
|
389
|
+
|
|
243
390
|
### Direct Export Function Component
|
|
244
391
|
Before:
|
|
245
392
|
```tsx
|
|
@@ -252,7 +399,7 @@ export default function Header() {
|
|
|
252
399
|
After (automatically transformed):
|
|
253
400
|
```tsx
|
|
254
401
|
// src/components/Header.tsx (transformed by the plugin)
|
|
255
|
-
import { withIdeButton } from 'nextjs-ide-
|
|
402
|
+
import { withIdeButton } from 'nextjs-ide-helper/withIdeButton';
|
|
256
403
|
|
|
257
404
|
function Header() {
|
|
258
405
|
return <header>My App Header</header>;
|
|
@@ -276,7 +423,7 @@ export default () => {
|
|
|
276
423
|
After (automatically transformed):
|
|
277
424
|
```tsx
|
|
278
425
|
// src/components/Footer.tsx (transformed by the plugin)
|
|
279
|
-
import { withIdeButton } from 'nextjs-ide-
|
|
426
|
+
import { withIdeButton } from 'nextjs-ide-helper/withIdeButton';
|
|
280
427
|
|
|
281
428
|
export default withIdeButton(() => {
|
|
282
429
|
return <footer>© 2025 My App</footer>;
|
|
@@ -327,6 +474,24 @@ See [CHANGELOG.md](./CHANGELOG.md) for detailed release notes.
|
|
|
327
474
|
|
|
328
475
|
### Recent Releases
|
|
329
476
|
|
|
477
|
+
### 1.5.0 - Toggle Visibility
|
|
478
|
+
- Added floating toggle button to show/hide all IDE dots
|
|
479
|
+
- Toggle button appears in bottom-right corner
|
|
480
|
+
- Blue when dots visible, gray when hidden
|
|
481
|
+
- State synchronized across all components via custom events
|
|
482
|
+
|
|
483
|
+
### 1.4.1 - Minimal UI
|
|
484
|
+
- Changed IDE buttons from text+emoji to minimal 10x10px blue dots
|
|
485
|
+
- Reduced visual footprint for less intrusive development experience
|
|
486
|
+
- Removed console.log statements from plugin
|
|
487
|
+
|
|
488
|
+
### 1.4.0 - Named Export Support
|
|
489
|
+
- Added comprehensive support for ES6 named exports in React components
|
|
490
|
+
- Support for `export const Component = () => {}` and `export function Component() {}` patterns
|
|
491
|
+
- Mixed export support - files with both named and default exports
|
|
492
|
+
- Enhanced component detection to distinguish React components from utility exports
|
|
493
|
+
- Expanded test suite with 8 new comprehensive test cases
|
|
494
|
+
|
|
330
495
|
### 1.3.0 - Glob Pattern Support
|
|
331
496
|
- Added support for glob patterns in `componentPaths` configuration
|
|
332
497
|
- Support for nested directory matching with `**` patterns
|
package/lib/loader.js
CHANGED
|
@@ -5,11 +5,6 @@ const generate = require('@babel/generator').default;
|
|
|
5
5
|
const t = require('@babel/types');
|
|
6
6
|
const { minimatch } = require('minimatch');
|
|
7
7
|
|
|
8
|
-
console.log('.................................')
|
|
9
|
-
console.log('🔧 minimatch version:', require('minimatch/package.json').version);
|
|
10
|
-
console.log('minimatch is', require('minimatch'));
|
|
11
|
-
console.log('.................................')
|
|
12
|
-
|
|
13
8
|
/**
|
|
14
9
|
* Webpack loader that automatically wraps React components with cursor buttons
|
|
15
10
|
* @param {string} source - The source code of the file
|
|
@@ -19,8 +14,6 @@ module.exports = function cursorButtonLoader(source) {
|
|
|
19
14
|
const filename = this.resourcePath;
|
|
20
15
|
const options = this.getOptions() || {};
|
|
21
16
|
|
|
22
|
-
console.log('🔧 Loader called for file:', filename);
|
|
23
|
-
|
|
24
17
|
const {
|
|
25
18
|
componentPaths = ['src/components'],
|
|
26
19
|
projectRoot = process.cwd(),
|
|
@@ -30,44 +23,34 @@ module.exports = function cursorButtonLoader(source) {
|
|
|
30
23
|
|
|
31
24
|
// Only process if enabled
|
|
32
25
|
if (!enabled) {
|
|
33
|
-
console.log('🔧 Loader disabled, returning original source');
|
|
34
26
|
return source;
|
|
35
27
|
}
|
|
36
28
|
|
|
37
29
|
// Only process files in specified component directories
|
|
38
30
|
const relativePath = path.relative(projectRoot, filename);
|
|
39
|
-
console.log('🔧 Processing file:', relativePath, 'against paths:', componentPaths);
|
|
40
31
|
|
|
41
32
|
const shouldProcess = componentPaths.some(componentPath => {
|
|
42
|
-
console.log('🔧 Checking path:', componentPath);
|
|
43
33
|
// Check if componentPath contains glob patterns
|
|
44
34
|
if (componentPath.includes('*')) {
|
|
45
35
|
const matches = minimatch(relativePath, componentPath);
|
|
46
|
-
console.log('🔧 Glob match result:', matches, 'for pattern:', componentPath);
|
|
47
36
|
return matches;
|
|
48
37
|
} else {
|
|
49
38
|
// Fallback to the original behavior for non-glob patterns
|
|
50
39
|
const matches = filename.includes(path.resolve(projectRoot, componentPath));
|
|
51
|
-
console.log('🔧 Direct path match result:', matches);
|
|
52
40
|
return matches;
|
|
53
41
|
}
|
|
54
42
|
});
|
|
55
43
|
|
|
56
|
-
console.log('🔧 Should process:', shouldProcess);
|
|
57
44
|
|
|
58
45
|
if (!shouldProcess || (!filename.endsWith('.tsx') && !filename.endsWith('.jsx'))) {
|
|
59
|
-
console.log('🔧 File does not match criteria, skipping');
|
|
60
46
|
return source;
|
|
61
47
|
}
|
|
62
48
|
|
|
63
49
|
// Check if it's already wrapped using simple string check for performance
|
|
64
50
|
if (source.includes('withIdeButton')) {
|
|
65
|
-
console.log('🔧 File already contains withIdeButton, skipping');
|
|
66
51
|
return source;
|
|
67
52
|
}
|
|
68
53
|
|
|
69
|
-
console.log('🔧 Proceeding to transform file:', relativePath);
|
|
70
|
-
|
|
71
54
|
let ast;
|
|
72
55
|
try {
|
|
73
56
|
// Parse the source code into an AST
|
|
@@ -99,6 +82,7 @@ module.exports = function cursorButtonLoader(source) {
|
|
|
99
82
|
let hasWithIdeButtonImport = false;
|
|
100
83
|
let lastImportPath = null;
|
|
101
84
|
let defaultExportPath = null;
|
|
85
|
+
let namedExports = []; // Track named component exports
|
|
102
86
|
|
|
103
87
|
// Traverse the AST to analyze the code
|
|
104
88
|
traverse(ast, {
|
|
@@ -107,7 +91,7 @@ module.exports = function cursorButtonLoader(source) {
|
|
|
107
91
|
|
|
108
92
|
// Check if withIdeButton is already imported
|
|
109
93
|
if (path.node.source.value === importPath) {
|
|
110
|
-
path.node.specifiers.forEach(spec => {
|
|
94
|
+
path.node.specifiers.forEach((spec) => {
|
|
111
95
|
if (t.isImportSpecifier(spec) && spec.imported.name === 'withIdeButton') {
|
|
112
96
|
hasWithIdeButtonImport = true;
|
|
113
97
|
}
|
|
@@ -146,17 +130,46 @@ module.exports = function cursorButtonLoader(source) {
|
|
|
146
130
|
|
|
147
131
|
// Stop traversal once we find the default export
|
|
148
132
|
path.stop();
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
ExportNamedDeclaration(path) {
|
|
136
|
+
// Handle named exports like: export const Button = () => {}
|
|
137
|
+
if (path.node.declaration && t.isVariableDeclaration(path.node.declaration)) {
|
|
138
|
+
path.node.declaration.declarations.forEach((declarator) => {
|
|
139
|
+
if (t.isIdentifier(declarator.id) &&
|
|
140
|
+
declarator.id.name[0] === declarator.id.name[0].toUpperCase() &&
|
|
141
|
+
(t.isArrowFunctionExpression(declarator.init) ||
|
|
142
|
+
t.isFunctionExpression(declarator.init))) {
|
|
143
|
+
namedExports.push({
|
|
144
|
+
name: declarator.id.name,
|
|
145
|
+
path: path,
|
|
146
|
+
declarator: declarator
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// Handle named function exports like: export function Button() {}
|
|
152
|
+
else if (path.node.declaration && t.isFunctionDeclaration(path.node.declaration)) {
|
|
153
|
+
const funcName = path.node.declaration.id.name;
|
|
154
|
+
if (funcName[0] === funcName[0].toUpperCase()) {
|
|
155
|
+
namedExports.push({
|
|
156
|
+
name: funcName,
|
|
157
|
+
path: path,
|
|
158
|
+
declaration: path.node.declaration
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
149
162
|
}
|
|
150
163
|
});
|
|
151
164
|
|
|
152
165
|
// Check if we should process this file
|
|
153
|
-
if (!hasDefaultExport || (!defaultExportName && !isAnonymousComponent)) {
|
|
166
|
+
if ((!hasDefaultExport || (!defaultExportName && !isAnonymousComponent)) && namedExports.length === 0) {
|
|
154
167
|
return source;
|
|
155
168
|
}
|
|
156
169
|
|
|
157
170
|
// Check if component name starts with uppercase (React component convention)
|
|
158
|
-
// Skip this check for anonymous components
|
|
159
|
-
if (defaultExportName && defaultExportName[0] !== defaultExportName[0].toUpperCase()) {
|
|
171
|
+
// Skip this check for anonymous components and if we have named exports
|
|
172
|
+
if (defaultExportName && defaultExportName[0] !== defaultExportName[0].toUpperCase() && namedExports.length === 0) {
|
|
160
173
|
return source;
|
|
161
174
|
}
|
|
162
175
|
|
|
@@ -170,26 +183,75 @@ module.exports = function cursorButtonLoader(source) {
|
|
|
170
183
|
// Transform the AST
|
|
171
184
|
let modified = false;
|
|
172
185
|
|
|
173
|
-
// Add the withIdeButton import
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
186
|
+
// Add the withIdeButton import if we have exports to process
|
|
187
|
+
if ((hasDefaultExport && (defaultExportName || isAnonymousComponent)) || namedExports.length > 0) {
|
|
188
|
+
const withIdeButtonImport = t.importDeclaration(
|
|
189
|
+
[t.importSpecifier(t.identifier('withIdeButton'), t.identifier('withIdeButton'))],
|
|
190
|
+
t.stringLiteral(importPath)
|
|
191
|
+
);
|
|
178
192
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
modified = true;
|
|
183
|
-
} else {
|
|
184
|
-
// No imports found, add at the beginning
|
|
185
|
-
if (ast.program && ast.program.body && Array.isArray(ast.program.body)) {
|
|
186
|
-
ast.program.body.unshift(withIdeButtonImport);
|
|
193
|
+
// Insert import after last existing import or at the beginning
|
|
194
|
+
if (lastImportPath) {
|
|
195
|
+
lastImportPath.insertAfter(withIdeButtonImport);
|
|
187
196
|
modified = true;
|
|
197
|
+
} else {
|
|
198
|
+
// No imports found, add at the beginning
|
|
199
|
+
if (ast.program && ast.program.body && Array.isArray(ast.program.body)) {
|
|
200
|
+
ast.program.body.unshift(withIdeButtonImport);
|
|
201
|
+
modified = true;
|
|
202
|
+
}
|
|
188
203
|
}
|
|
189
204
|
}
|
|
190
205
|
|
|
206
|
+
// Process named exports
|
|
207
|
+
namedExports.forEach(namedExport => {
|
|
208
|
+
if (namedExport.declarator) {
|
|
209
|
+
// Handle export const Component = () => {}
|
|
210
|
+
const wrappedCall = t.callExpression(
|
|
211
|
+
t.identifier('withIdeButton'),
|
|
212
|
+
[
|
|
213
|
+
namedExport.declarator.init,
|
|
214
|
+
t.stringLiteral(relativePath),
|
|
215
|
+
t.objectExpression([
|
|
216
|
+
t.objectProperty(t.identifier('projectRoot'), t.stringLiteral(projectRoot))
|
|
217
|
+
])
|
|
218
|
+
]
|
|
219
|
+
);
|
|
220
|
+
namedExport.declarator.init = wrappedCall;
|
|
221
|
+
modified = true;
|
|
222
|
+
} else if (namedExport.declaration) {
|
|
223
|
+
// Handle export function Component() {}
|
|
224
|
+
// Convert FunctionDeclaration to FunctionExpression (preserves name for stack traces)
|
|
225
|
+
const funcDecl = namedExport.declaration;
|
|
226
|
+
const funcExpr = t.functionExpression(
|
|
227
|
+
funcDecl.id, // Keep the name for debugging
|
|
228
|
+
funcDecl.params,
|
|
229
|
+
funcDecl.body,
|
|
230
|
+
funcDecl.generator,
|
|
231
|
+
funcDecl.async
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const wrappedCall = t.callExpression(
|
|
235
|
+
t.identifier('withIdeButton'),
|
|
236
|
+
[
|
|
237
|
+
funcExpr,
|
|
238
|
+
t.stringLiteral(relativePath),
|
|
239
|
+
t.objectExpression([
|
|
240
|
+
t.objectProperty(t.identifier('projectRoot'), t.stringLiteral(projectRoot))
|
|
241
|
+
])
|
|
242
|
+
]
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// Replace: export function X() {} -> export const X = withIdeButton(function X() {}, ...)
|
|
246
|
+
namedExport.path.node.declaration = t.variableDeclaration('const', [
|
|
247
|
+
t.variableDeclarator(t.identifier(namedExport.name), wrappedCall)
|
|
248
|
+
]);
|
|
249
|
+
modified = true;
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
191
253
|
// Replace the default export with wrapped version
|
|
192
|
-
if (defaultExportPath &&
|
|
254
|
+
if (defaultExportPath && (hasDefaultExport && (defaultExportName || isAnonymousComponent))) {
|
|
193
255
|
let wrappedCall;
|
|
194
256
|
|
|
195
257
|
if (isAnonymousComponent) {
|
|
@@ -234,15 +296,33 @@ module.exports = function cursorButtonLoader(source) {
|
|
|
234
296
|
);
|
|
235
297
|
}
|
|
236
298
|
|
|
237
|
-
// If the export is a named function
|
|
238
|
-
if (!isAnonymousComponent &&
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
299
|
+
// If the export is a named function declaration, convert to function expression inline
|
|
300
|
+
if (!isAnonymousComponent && t.isFunctionDeclaration(defaultExportPath.node.declaration)) {
|
|
301
|
+
const funcDecl = defaultExportPath.node.declaration;
|
|
302
|
+
const funcExpr = t.functionExpression(
|
|
303
|
+
funcDecl.id, // Keep the name for debugging
|
|
304
|
+
funcDecl.params,
|
|
305
|
+
funcDecl.body,
|
|
306
|
+
funcDecl.generator,
|
|
307
|
+
funcDecl.async
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// Wrap the function expression directly
|
|
311
|
+
wrappedCall = t.callExpression(
|
|
312
|
+
t.identifier('withIdeButton'),
|
|
313
|
+
[
|
|
314
|
+
funcExpr,
|
|
315
|
+
t.stringLiteral(relativePath),
|
|
316
|
+
t.objectExpression([
|
|
317
|
+
t.objectProperty(t.identifier('projectRoot'), t.stringLiteral(projectRoot))
|
|
318
|
+
])
|
|
319
|
+
]
|
|
320
|
+
);
|
|
321
|
+
defaultExportPath.node.declaration = wrappedCall;
|
|
322
|
+
} else if (!isAnonymousComponent && t.isClassDeclaration(defaultExportPath.node.declaration)) {
|
|
323
|
+
// For classes, we still need to separate declaration (can't inline class expressions easily)
|
|
242
324
|
const declaration = defaultExportPath.node.declaration;
|
|
243
325
|
defaultExportPath.insertBefore(declaration);
|
|
244
|
-
|
|
245
|
-
// Replace the export declaration with the wrapped call
|
|
246
326
|
defaultExportPath.node.declaration = wrappedCall;
|
|
247
327
|
} else {
|
|
248
328
|
// For other cases (identifier, variable declaration, anonymous functions), just replace the declaration
|
package/lib/plugin.js
CHANGED
|
@@ -42,8 +42,6 @@ function withCursorHelper(options = {}) {
|
|
|
42
42
|
webpack: (webpackConfig, context) => {
|
|
43
43
|
const { dev, isServer } = context;
|
|
44
44
|
|
|
45
|
-
console.log('🔧 Webpack config called:', { dev, isServer, enabled: config.enabled });
|
|
46
|
-
|
|
47
45
|
// Only apply in development and for client-side
|
|
48
46
|
if (config.enabled && dev && !isServer) {
|
|
49
47
|
// Extract base directories from glob patterns for webpack's include
|
|
@@ -51,7 +49,6 @@ function withCursorHelper(options = {}) {
|
|
|
51
49
|
config.componentPaths.map(p => extractBaseDirectory(p))
|
|
52
50
|
)].map(p => path.resolve(config.projectRoot, p));
|
|
53
51
|
|
|
54
|
-
console.log('🔧 Adding webpack rule with include directories:', includeDirectories);
|
|
55
52
|
|
|
56
53
|
const rule = {
|
|
57
54
|
test: /\.tsx?$/,
|