nextjs-ide-helper 1.3.4 → 1.4.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/README.md CHANGED
@@ -119,7 +119,40 @@ export default withIdeButton(MyComponent, 'src/components/MyComponent.tsx', {
119
119
 
120
120
  The plugin automatically detects and wraps all types of React component export patterns:
121
121
 
122
- ### Named Components
122
+ ### Named Export Components
123
+ ```tsx
124
+ // Named arrow function exports
125
+ export const Button = () => <button>Click me</button>;
126
+ export const Modal = () => <div>Modal content</div>;
127
+
128
+ // Named function exports
129
+ export function MyButton() {
130
+ return <button>Function Button</button>;
131
+ }
132
+
133
+ export function MyModal() {
134
+ return <div>Function Modal</div>;
135
+ }
136
+
137
+ // Mixed named and default exports
138
+ export const HeaderButton = () => <button>Header</button>;
139
+
140
+ const MainComponent = () => <div>Main</div>;
141
+ export default MainComponent;
142
+
143
+ // TypeScript named exports
144
+ interface ButtonProps {
145
+ onClick: () => void;
146
+ }
147
+
148
+ export const TypedButton = ({ onClick }: ButtonProps) => (
149
+ <button onClick={onClick}>Typed Button</button>
150
+ );
151
+ ```
152
+
153
+ ### Default Export Components
154
+
155
+ #### Named Components
123
156
  ```tsx
124
157
  // Standard pattern
125
158
  const MyComponent = () => <div>Hello</div>;
@@ -132,7 +165,7 @@ const MyComponent = function() {
132
165
  export default MyComponent;
133
166
  ```
134
167
 
135
- ### Direct Export Function Components
168
+ #### Direct Export Function Components
136
169
  ```tsx
137
170
  // Named function
138
171
  export default function MyComponent() {
@@ -145,7 +178,7 @@ export default function() {
145
178
  }
146
179
  ```
147
180
 
148
- ### Arrow Function Components
181
+ #### Arrow Function Components
149
182
  ```tsx
150
183
  // Anonymous arrow function
151
184
  export default () => {
@@ -158,7 +191,7 @@ export default (props) => {
158
191
  };
159
192
  ```
160
193
 
161
- ### Class Components
194
+ #### Class Components
162
195
  ```tsx
163
196
  // Named class
164
197
  export default class MyComponent extends React.Component {
@@ -175,7 +208,7 @@ export default class MyComponent extends Component<Props> {
175
208
  }
176
209
  ```
177
210
 
178
- ### TypeScript Components
211
+ #### TypeScript Components
179
212
  ```tsx
180
213
  // Function with TypeScript
181
214
  interface Props {
@@ -240,6 +273,102 @@ export default withIdeButton(Button, 'src/components/Button.tsx', {
240
273
  });
241
274
  ```
242
275
 
276
+ ### Named Export Pattern
277
+ Before (your original named exports):
278
+ ```tsx
279
+ // src/components/Buttons.tsx
280
+ export const PrimaryButton = ({ children, onClick }) => {
281
+ return (
282
+ <button className="primary" onClick={onClick}>
283
+ {children}
284
+ </button>
285
+ );
286
+ };
287
+
288
+ export const SecondaryButton = ({ children, onClick }) => {
289
+ return (
290
+ <button className="secondary" onClick={onClick}>
291
+ {children}
292
+ </button>
293
+ );
294
+ };
295
+ ```
296
+
297
+ After (automatically transformed):
298
+ ```tsx
299
+ // src/components/Buttons.tsx (transformed by the plugin)
300
+ import { withIdeButton } from 'nextjs-ide-plugin/withIdeButton';
301
+
302
+ export const PrimaryButton = withIdeButton(({ children, onClick }) => {
303
+ return (
304
+ <button className="primary" onClick={onClick}>
305
+ {children}
306
+ </button>
307
+ );
308
+ }, 'src/components/Buttons.tsx', {
309
+ projectRoot: '/path/to/project',
310
+ ideType: 'cursor'
311
+ });
312
+
313
+ export const SecondaryButton = withIdeButton(({ children, onClick }) => {
314
+ return (
315
+ <button className="secondary" onClick={onClick}>
316
+ {children}
317
+ </button>
318
+ );
319
+ }, 'src/components/Buttons.tsx', {
320
+ projectRoot: '/path/to/project',
321
+ ideType: 'cursor'
322
+ });
323
+ ```
324
+
325
+ ### Mixed Named and Default Exports
326
+ Before (mixed exports):
327
+ ```tsx
328
+ // src/components/Layout.tsx
329
+ export const Header = () => <header>My App Header</header>;
330
+ export const Footer = () => <footer>© 2025 My App</footer>;
331
+
332
+ const Layout = ({ children }) => (
333
+ <div>
334
+ <Header />
335
+ <main>{children}</main>
336
+ <Footer />
337
+ </div>
338
+ );
339
+
340
+ export default Layout;
341
+ ```
342
+
343
+ After (automatically transformed):
344
+ ```tsx
345
+ // src/components/Layout.tsx (transformed by the plugin)
346
+ import { withIdeButton } from 'nextjs-ide-plugin/withIdeButton';
347
+
348
+ export const Header = withIdeButton(() => <header>My App Header</header>, 'src/components/Layout.tsx', {
349
+ projectRoot: '/path/to/project',
350
+ ideType: 'cursor'
351
+ });
352
+
353
+ export const Footer = withIdeButton(() => <footer>© 2025 My App</footer>, 'src/components/Layout.tsx', {
354
+ projectRoot: '/path/to/project',
355
+ ideType: 'cursor'
356
+ });
357
+
358
+ const Layout = ({ children }) => (
359
+ <div>
360
+ <Header />
361
+ <main>{children}</main>
362
+ <Footer />
363
+ </div>
364
+ );
365
+
366
+ export default withIdeButton(Layout, 'src/components/Layout.tsx', {
367
+ projectRoot: '/path/to/project',
368
+ ideType: 'cursor'
369
+ });
370
+ ```
371
+
243
372
  ### Direct Export Function Component
244
373
  Before:
245
374
  ```tsx
@@ -327,6 +456,13 @@ See [CHANGELOG.md](./CHANGELOG.md) for detailed release notes.
327
456
 
328
457
  ### Recent Releases
329
458
 
459
+ ### 1.4.0 - Named Export Support
460
+ - Added comprehensive support for ES6 named exports in React components
461
+ - Support for `export const Component = () => {}` and `export function Component() {}` patterns
462
+ - Mixed export support - files with both named and default exports
463
+ - Enhanced component detection to distinguish React components from utility exports
464
+ - Expanded test suite with 8 new comprehensive test cases
465
+
330
466
  ### 1.3.0 - Glob Pattern Support
331
467
  - Added support for glob patterns in `componentPaths` configuration
332
468
  - 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,69 @@ module.exports = function cursorButtonLoader(source) {
170
183
  // Transform the AST
171
184
  let modified = false;
172
185
 
173
- // Add the withIdeButton import
174
- const withIdeButtonImport = t.importDeclaration(
175
- [t.importSpecifier(t.identifier('withIdeButton'), t.identifier('withIdeButton'))],
176
- t.stringLiteral(importPath)
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
- // Insert import after last existing import or at the beginning
180
- if (lastImportPath) {
181
- lastImportPath.insertAfter(withIdeButtonImport);
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
+ const funcDeclaration = namedExport.declaration;
225
+ const wrappedCall = t.callExpression(
226
+ t.identifier('withIdeButton'),
227
+ [
228
+ t.identifier(namedExport.name),
229
+ t.stringLiteral(relativePath),
230
+ t.objectExpression([
231
+ t.objectProperty(t.identifier('projectRoot'), t.stringLiteral(projectRoot))
232
+ ])
233
+ ]
234
+ );
235
+
236
+ // Insert the function declaration before the export
237
+ namedExport.path.insertBefore(funcDeclaration);
238
+
239
+ // Replace the export with wrapped call
240
+ namedExport.path.node.declaration = t.variableDeclaration('const', [
241
+ t.variableDeclarator(t.identifier(namedExport.name), wrappedCall)
242
+ ]);
243
+ modified = true;
244
+ }
245
+ });
246
+
191
247
  // Replace the default export with wrapped version
192
- if (defaultExportPath && modified) {
248
+ if (defaultExportPath && (hasDefaultExport && (defaultExportName || isAnonymousComponent))) {
193
249
  let wrappedCall;
194
250
 
195
251
  if (isAnonymousComponent) {
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?$/,
@@ -26,20 +26,19 @@ const IdeButton = ({ filePath, projectRoot, ideType = 'cursor' }) => {
26
26
  };
27
27
  return ((0, jsx_runtime_1.jsx)("button", { onClick: handleClick, style: {
28
28
  position: 'absolute',
29
- top: '8px',
30
- right: '8px',
29
+ top: '4px',
30
+ right: '4px',
31
+ width: '10px',
32
+ height: '10px',
31
33
  background: '#007acc',
32
- color: 'white',
33
34
  border: 'none',
34
- borderRadius: '4px',
35
- padding: '4px 8px',
36
- fontSize: '12px',
35
+ borderRadius: '50%',
36
+ padding: 0,
37
37
  cursor: 'pointer',
38
38
  zIndex: 1000,
39
- opacity: 0.7,
40
- transition: 'opacity 0.2s',
41
- fontFamily: 'monospace'
42
- }, onMouseEnter: (e) => (e.currentTarget.style.opacity = '1'), onMouseLeave: (e) => (e.currentTarget.style.opacity = '0.7'), title: `Open ${filePath} in ${ideType.toUpperCase()}`, children: "\uD83D\uDCDD" }));
39
+ opacity: 0.6,
40
+ transition: 'opacity 0.2s'
41
+ }, onMouseEnter: (e) => (e.currentTarget.style.opacity = '1'), onMouseLeave: (e) => (e.currentTarget.style.opacity = '0.6'), title: `Open ${filePath} in ${ideType.toUpperCase()}` }));
43
42
  };
44
43
  function withIdeButton(WrappedComponent, filePath, options = {}) {
45
44
  const { projectRoot, enabled = process.env.NODE_ENV === 'development', ideType = 'cursor' } = options;
@@ -35,26 +35,23 @@ const IdeButton: React.FC<IdeButtonProps> = ({ filePath, projectRoot, ideType =
35
35
  onClick={handleClick}
36
36
  style={{
37
37
  position: 'absolute',
38
- top: '8px',
39
- right: '8px',
38
+ top: '4px',
39
+ right: '4px',
40
+ width: '10px',
41
+ height: '10px',
40
42
  background: '#007acc',
41
- color: 'white',
42
43
  border: 'none',
43
- borderRadius: '4px',
44
- padding: '4px 8px',
45
- fontSize: '12px',
44
+ borderRadius: '50%',
45
+ padding: 0,
46
46
  cursor: 'pointer',
47
47
  zIndex: 1000,
48
- opacity: 0.7,
49
- transition: 'opacity 0.2s',
50
- fontFamily: 'monospace'
48
+ opacity: 0.6,
49
+ transition: 'opacity 0.2s'
51
50
  }}
52
51
  onMouseEnter={(e) => (e.currentTarget.style.opacity = '1')}
53
- onMouseLeave={(e) => (e.currentTarget.style.opacity = '0.7')}
52
+ onMouseLeave={(e) => (e.currentTarget.style.opacity = '0.6')}
54
53
  title={`Open ${filePath} in ${ideType.toUpperCase()}`}
55
- >
56
- 📝
57
- </button>
54
+ />
58
55
  );
59
56
  };
60
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextjs-ide-helper",
3
- "version": "1.3.4",
3
+ "version": "1.4.1",
4
4
  "description": "A Next.js plugin that automatically adds IDE buttons to React components for seamless IDE integration. Supports Cursor, VS Code, WebStorm, and Atom.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -9,7 +9,7 @@
9
9
  "src"
10
10
  ],
11
11
  "scripts": {
12
- "build": "tsc",
12
+ "build": "tsc && cp src/loader.js lib/loader.js",
13
13
  "prepare": "npm run build",
14
14
  "test": "jest"
15
15
  },