@eyeglass/cli 0.1.5 → 0.1.6

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/dist/index.js CHANGED
@@ -267,6 +267,46 @@ function detectProject() {
267
267
  return { type: 'unknown', typescript: hasTs };
268
268
  }
269
269
  // ============================================================================
270
+ // HTML Entry Point Detection
271
+ // ============================================================================
272
+ /**
273
+ * Parses index.html to find the entry script path.
274
+ * Looks for <script type="module" src="..."> tags and returns the first match.
275
+ * Works for vanilla Vite, React, Vue, and other projects that use index.html.
276
+ */
277
+ function findEntryFromHtml(cwd) {
278
+ // Common locations for index.html
279
+ const htmlFiles = [
280
+ 'index.html',
281
+ 'public/index.html',
282
+ 'src/index.html',
283
+ ];
284
+ for (const htmlFile of htmlFiles) {
285
+ const htmlPath = path.join(cwd, htmlFile);
286
+ if (!fileExists(htmlPath))
287
+ continue;
288
+ const content = readFile(htmlPath);
289
+ // Match <script type="module" src="..."> - handles various quote styles and spacing
290
+ // This regex captures the src attribute value from module scripts
291
+ const moduleScriptRegex = /<script[^>]*type\s*=\s*["']module["'][^>]*src\s*=\s*["']([^"']+)["'][^>]*>/gi;
292
+ const altModuleScriptRegex = /<script[^>]*src\s*=\s*["']([^"']+)["'][^>]*type\s*=\s*["']module["'][^>]*>/gi;
293
+ let match = moduleScriptRegex.exec(content) || altModuleScriptRegex.exec(content);
294
+ if (match && match[1]) {
295
+ let scriptSrc = match[1];
296
+ // Remove leading slash if present (Vite uses /src/main.js format)
297
+ if (scriptSrc.startsWith('/')) {
298
+ scriptSrc = scriptSrc.slice(1);
299
+ }
300
+ // Resolve the path relative to the project root
301
+ const scriptPath = path.join(cwd, scriptSrc);
302
+ if (fileExists(scriptPath)) {
303
+ return scriptPath;
304
+ }
305
+ }
306
+ }
307
+ return null;
308
+ }
309
+ // ============================================================================
270
310
  // Config Modifiers
271
311
  // ============================================================================
272
312
  function setupEyeglassSkill(dryRun) {
@@ -482,8 +522,16 @@ function setupVite(configFile, dryRun) {
482
522
  break;
483
523
  }
484
524
  }
525
+ // Fallback: parse index.html to find the actual entry script
526
+ if (!entryFile) {
527
+ entryFile = findEntryFromHtml(cwd);
528
+ if (entryFile) {
529
+ log(`Found entry file from index.html: ${path.relative(cwd, entryFile)}`, 'info');
530
+ }
531
+ }
485
532
  if (!entryFile) {
486
533
  log('Could not find entry file - please import @eyeglass/inspector manually in your main file', 'warn');
534
+ log('Tip: Check your index.html for the entry script path', 'info');
487
535
  return false;
488
536
  }
489
537
  const content = readFile(entryFile);
@@ -573,8 +621,16 @@ function setupCRA(entryFile, dryRun) {
573
621
  }
574
622
  }
575
623
  }
624
+ // Fallback: parse index.html to find the actual entry script
625
+ if (!entryFile) {
626
+ entryFile = findEntryFromHtml(cwd) ?? undefined;
627
+ if (entryFile) {
628
+ log(`Found entry file from index.html: ${path.relative(cwd, entryFile)}`, 'info');
629
+ }
630
+ }
576
631
  if (!entryFile || !fileExists(entryFile)) {
577
632
  log('Could not find entry file - please import @eyeglass/inspector manually', 'warn');
633
+ log('Tip: Check your index.html for the entry script path', 'info');
578
634
  return false;
579
635
  }
580
636
  const content = readFile(entryFile);
@@ -665,10 +721,34 @@ async function init(options) {
665
721
  case 'remix':
666
722
  setupCRA(project.entryFile, dryRun);
667
723
  break;
668
- default:
669
- log('Unknown project type - please configure manually:', 'warn');
670
- console.log(' 1. Import @eyeglass/inspector in your entry file');
671
- console.log(' 2. Or add <script type="module">import "@eyeglass/inspector";</script> to your HTML\n');
724
+ default: {
725
+ // Try to find entry file from index.html for unknown project types
726
+ const entryFromHtml = findEntryFromHtml(process.cwd());
727
+ if (entryFromHtml) {
728
+ log(`Found entry file from index.html: ${path.relative(process.cwd(), entryFromHtml)}`, 'info');
729
+ const content = readFile(entryFromHtml);
730
+ if (content.includes('@eyeglass/inspector')) {
731
+ log('Inspector already imported', 'success');
732
+ }
733
+ else {
734
+ const importStatement = `import '@eyeglass/inspector';\n`;
735
+ const newContent = importStatement + content;
736
+ if (dryRun) {
737
+ log(`Would add inspector import to ${path.relative(process.cwd(), entryFromHtml)}`);
738
+ }
739
+ else {
740
+ writeFile(entryFromHtml, newContent);
741
+ log(`Added inspector import to ${path.relative(process.cwd(), entryFromHtml)}`, 'success');
742
+ }
743
+ }
744
+ }
745
+ else {
746
+ log('Unknown project type - please configure manually:', 'warn');
747
+ console.log(' 1. Import @eyeglass/inspector in your entry file');
748
+ console.log(' 2. Or add <script type="module">import "@eyeglass/inspector";</script> to your HTML\n');
749
+ }
750
+ break;
751
+ }
672
752
  }
673
753
  // Done!
674
754
  console.log('\n\x1b[1mSetup complete!\x1b[0m\n');
@@ -58,6 +58,34 @@ function detectPackageManager(cwd, mockFs) {
58
58
  return 'yarn';
59
59
  return 'npm';
60
60
  }
61
+ // Helper to recreate findEntryFromHtml logic
62
+ function findEntryFromHtml(cwd, mockFs) {
63
+ const htmlFiles = [
64
+ 'index.html',
65
+ 'public/index.html',
66
+ 'src/index.html',
67
+ ];
68
+ for (const htmlFile of htmlFiles) {
69
+ const htmlPath = path.join(cwd, htmlFile);
70
+ if (!mockFs.existsSync(htmlPath))
71
+ continue;
72
+ const content = mockFs.readFileSync(htmlPath);
73
+ const moduleScriptRegex = /<script[^>]*type\s*=\s*["']module["'][^>]*src\s*=\s*["']([^"']+)["'][^>]*>/gi;
74
+ const altModuleScriptRegex = /<script[^>]*src\s*=\s*["']([^"']+)["'][^>]*type\s*=\s*["']module["'][^>]*>/gi;
75
+ let match = moduleScriptRegex.exec(content) || altModuleScriptRegex.exec(content);
76
+ if (match && match[1]) {
77
+ let scriptSrc = match[1];
78
+ if (scriptSrc.startsWith('/')) {
79
+ scriptSrc = scriptSrc.slice(1);
80
+ }
81
+ const scriptPath = path.join(cwd, scriptSrc);
82
+ if (mockFs.existsSync(scriptPath)) {
83
+ return scriptPath;
84
+ }
85
+ }
86
+ }
87
+ return null;
88
+ }
61
89
  describe('eyeglass CLI', () => {
62
90
  beforeEach(() => {
63
91
  vi.clearAllMocks();
@@ -234,6 +262,89 @@ export default defineConfig({
234
262
  expect(layoutFiles.length).toBe(8);
235
263
  });
236
264
  });
265
+ describe('findEntryFromHtml', () => {
266
+ it('should find entry script from index.html with leading slash', () => {
267
+ const mockFs = {
268
+ existsSync: (p) => p.endsWith('index.html') || p.endsWith('src/main.js'),
269
+ readFileSync: () => `<!DOCTYPE html>
270
+ <html>
271
+ <body>
272
+ <script type="module" src="/src/main.js"></script>
273
+ </body>
274
+ </html>`,
275
+ };
276
+ const result = findEntryFromHtml('/my/project', mockFs);
277
+ expect(result).toContain('src/main.js');
278
+ });
279
+ it('should find entry script from index.html without leading slash', () => {
280
+ const mockFs = {
281
+ existsSync: (p) => p.endsWith('index.html') || p.endsWith('src/app.js'),
282
+ readFileSync: () => `<!DOCTYPE html>
283
+ <html>
284
+ <body>
285
+ <script type="module" src="src/app.js"></script>
286
+ </body>
287
+ </html>`,
288
+ };
289
+ const result = findEntryFromHtml('/my/project', mockFs);
290
+ expect(result).toContain('src/app.js');
291
+ });
292
+ it('should find entry script with src before type attribute', () => {
293
+ const mockFs = {
294
+ existsSync: (p) => p.endsWith('index.html') || p.endsWith('src/counter.ts'),
295
+ readFileSync: () => `<!DOCTYPE html>
296
+ <html>
297
+ <body>
298
+ <script src="/src/counter.ts" type="module"></script>
299
+ </body>
300
+ </html>`,
301
+ };
302
+ const result = findEntryFromHtml('/my/project', mockFs);
303
+ expect(result).toContain('src/counter.ts');
304
+ });
305
+ it('should find entry script from public/index.html', () => {
306
+ const mockFs = {
307
+ existsSync: (p) => p.endsWith('public/index.html') || p.endsWith('src/index.js'),
308
+ readFileSync: () => `<!DOCTYPE html>
309
+ <html>
310
+ <body>
311
+ <script type="module" src="/src/index.js"></script>
312
+ </body>
313
+ </html>`,
314
+ };
315
+ const result = findEntryFromHtml('/my/project', mockFs);
316
+ expect(result).toContain('src/index.js');
317
+ });
318
+ it('should return null when index.html does not exist', () => {
319
+ const mockFs = {
320
+ existsSync: () => false,
321
+ readFileSync: () => '',
322
+ };
323
+ const result = findEntryFromHtml('/my/project', mockFs);
324
+ expect(result).toBeNull();
325
+ });
326
+ it('should return null when script file does not exist', () => {
327
+ const mockFs = {
328
+ existsSync: (p) => p.endsWith('index.html'),
329
+ readFileSync: () => `<script type="module" src="/src/main.js"></script>`,
330
+ };
331
+ const result = findEntryFromHtml('/my/project', mockFs);
332
+ expect(result).toBeNull();
333
+ });
334
+ it('should return null when no module script is found', () => {
335
+ const mockFs = {
336
+ existsSync: (p) => p.endsWith('index.html'),
337
+ readFileSync: () => `<!DOCTYPE html>
338
+ <html>
339
+ <body>
340
+ <script src="/src/main.js"></script>
341
+ </body>
342
+ </html>`,
343
+ };
344
+ const result = findEntryFromHtml('/my/project', mockFs);
345
+ expect(result).toBeNull();
346
+ });
347
+ });
237
348
  describe('use client directive handling', () => {
238
349
  it('should preserve use client directive at top', () => {
239
350
  const fileContent = `'use client';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eyeglass/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Visual debugging for AI coding agents - CLI",
5
5
  "type": "module",
6
6
  "bin": {