|
1 | 1 | #!/usr/bin/env node |
2 | 2 |
|
3 | | -import { spawn } from 'node:child_process' |
4 | 3 | import { resolve, relative } from 'node:path' |
5 | 4 | import { fileURLToPath } from 'node:url' |
| 5 | +import { existsSync } from 'node:fs' |
| 6 | +import { createServer } from 'vite' |
| 7 | +import react from '@vitejs/plugin-react' |
6 | 8 |
|
7 | 9 | const componentPath = process.argv[2] |
8 | 10 |
|
9 | 11 | if (!componentPath) { |
10 | | - console.error('Usage: observe path/to/MyComponent.tsx') |
| 12 | + console.error('Usage: reactoscope path/to/MyComponent.tsx') |
11 | 13 | process.exit(1) |
12 | 14 | } |
13 | 15 |
|
14 | | -const projectRoot = resolve(fileURLToPath(import.meta.url), '../..') |
15 | | -const abs = resolve(componentPath) |
| 16 | +const cwd = process.cwd() |
| 17 | +const abs = resolve(cwd, componentPath) |
16 | 18 |
|
17 | | -// Make it relative to project root so we don't leak absolute paths |
18 | | -const rel = relative(projectRoot, abs) |
| 19 | +if (!existsSync(abs)) { |
| 20 | + console.error(`Error: File not found: ${abs}`) |
| 21 | + process.exit(1) |
| 22 | +} |
| 23 | + |
| 24 | +const rel = relative(cwd, abs) |
19 | 25 |
|
20 | 26 | if (rel.startsWith('..')) { |
21 | | - console.error('Error: Component must be inside the project directory.') |
| 27 | + console.error( |
| 28 | + 'Error: Component must be inside the current working directory.', |
| 29 | + ) |
22 | 30 | process.exit(1) |
23 | 31 | } |
24 | 32 |
|
25 | | -const viteBin = resolve(projectRoot, 'node_modules/.bin/vite') |
26 | | - |
27 | | -// spawn avoids shell injection — no shell is involved |
28 | | -const existingNodeOptions = process.env.NODE_OPTIONS ?? '' |
29 | | -const child = spawn( |
30 | | - viteBin, |
31 | | - ['--open', `/?component=${encodeURIComponent(rel)}`], |
32 | | - { |
33 | | - cwd: projectRoot, |
34 | | - stdio: 'inherit', |
35 | | - env: { |
36 | | - ...process.env, |
37 | | - NODE_OPTIONS: `${existingNodeOptions} --expose-gc`.trim(), |
| 33 | +const pkgRoot = resolve(fileURLToPath(import.meta.url), '../..') |
| 34 | + |
| 35 | +// Dynamic import so this works whether the user installed the package |
| 36 | +// or is running from the repo itself. |
| 37 | +const { observatory } = await import(resolve(pkgRoot, 'dist/plugin.mjs')) |
| 38 | + |
| 39 | +/** Check whether the user's Vite config already includes a React plugin. */ |
| 40 | +function hasReactPlugin(plugins) { |
| 41 | + const reactPluginNames = new Set([ |
| 42 | + 'vite:react-babel', |
| 43 | + 'vite:react-swc', |
| 44 | + 'vite:react-refresh', |
| 45 | + ]) |
| 46 | + return plugins.some((p) => { |
| 47 | + if (Array.isArray(p)) return p.some((pp) => reactPluginNames.has(pp.name)) |
| 48 | + return reactPluginNames.has(p.name) |
| 49 | + }) |
| 50 | +} |
| 51 | + |
| 52 | +// Build the plugin list — always include observatory, |
| 53 | +// only add react() if the user's config doesn't already provide one. |
| 54 | +const extraPlugins = [...observatory()] |
| 55 | + |
| 56 | +const server = await createServer({ |
| 57 | + root: cwd, |
| 58 | + plugins: [ |
| 59 | + { |
| 60 | + name: 'observatory:inject', |
| 61 | + config(config) { |
| 62 | + const existing = config.plugins?.flat() ?? [] |
| 63 | + if (!hasReactPlugin(existing)) { |
| 64 | + config.plugins = [react(), ...existing] |
| 65 | + } |
| 66 | + }, |
38 | 67 | }, |
| 68 | + ...extraPlugins, |
| 69 | + ], |
| 70 | + resolve: { |
| 71 | + tsconfigPaths: true, |
| 72 | + }, |
| 73 | + server: { |
| 74 | + open: `/__observatory?component=${encodeURIComponent(rel)}`, |
39 | 75 | }, |
40 | | -) |
| 76 | +}) |
41 | 77 |
|
42 | | -child.on('exit', (code) => process.exit(code ?? 0)) |
| 78 | +await server.listen() |
| 79 | +server.printUrls() |
0 commit comments