@eyeglass/cli 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/dist/index.js +84 -4
- package/dist/index.test.js +111 -0
- package/package.json +1 -1
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
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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');
|
package/dist/index.test.js
CHANGED
|
@@ -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';
|