react-dev-panel 0.1.0
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/LICENSE +21 -0
- package/README.md +130 -0
- package/dist/adapters/next.cjs +95 -0
- package/dist/adapters/next.cjs.map +1 -0
- package/dist/adapters/next.d.cts +17 -0
- package/dist/adapters/next.d.ts +17 -0
- package/dist/adapters/next.js +65 -0
- package/dist/adapters/next.js.map +1 -0
- package/dist/adapters/server.cjs +72 -0
- package/dist/adapters/server.cjs.map +1 -0
- package/dist/adapters/server.d.cts +24 -0
- package/dist/adapters/server.d.ts +24 -0
- package/dist/adapters/server.js +4 -0
- package/dist/adapters/server.js.map +1 -0
- package/dist/adapters/vite.cjs +301 -0
- package/dist/adapters/vite.cjs.map +1 -0
- package/dist/adapters/vite.d.cts +35 -0
- package/dist/adapters/vite.d.ts +35 -0
- package/dist/adapters/vite.js +31 -0
- package/dist/adapters/vite.js.map +1 -0
- package/dist/chunk-2ZAPVMUL.js +25 -0
- package/dist/chunk-2ZAPVMUL.js.map +1 -0
- package/dist/chunk-MAYMGQIM.js +64 -0
- package/dist/chunk-MAYMGQIM.js.map +1 -0
- package/dist/chunk-XZ4DPO52.js +190 -0
- package/dist/chunk-XZ4DPO52.js.map +1 -0
- package/dist/cli/index.cjs +221 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +35 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/generate-UK65K5BU.js +3 -0
- package/dist/generate-UK65K5BU.js.map +1 -0
- package/dist/index.cjs +1745 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +53 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +1711 -0
- package/dist/index.js.map +1 -0
- package/dist/server-open-B4FK0jQF.d.cts +92 -0
- package/dist/server-open-B4FK0jQF.d.ts +92 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pavan Kumar Mistry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# react-dev-panel
|
|
2
|
+
|
|
3
|
+
A **framework-agnostic, self-contained** floating dev panel for any React app — no MUI, no
|
|
4
|
+
Redux, no design-system coupling. One component, gated to internal/dev users, giving you:
|
|
5
|
+
|
|
6
|
+
- **Developer Logs** — live console + network capture
|
|
7
|
+
- **Page Performance** — Core Web Vitals with fix hints
|
|
8
|
+
- **Component Graph Inspector** — hover-to-source, component tree, "what's on this page", open-in-editor
|
|
9
|
+
|
|
10
|
+
Styles are injected once under a `.rdp-root` scope, so the panel can't clash with — or be
|
|
11
|
+
clashed by — your app's theme. The only runtime requirement is React 18+.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i -D react-dev-panel
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
Mount it once near your app root. It renders nothing in production by default.
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { DevPanel } from 'react-dev-panel';
|
|
25
|
+
|
|
26
|
+
export function Providers({ children }) {
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
{children}
|
|
30
|
+
<DevPanel
|
|
31
|
+
// Gate: who may see it. Defaults to NODE_ENV !== 'production'.
|
|
32
|
+
enabled={process.env.NODE_ENV !== 'production' || user?.isInternal}
|
|
33
|
+
// Current route, for tools that display it (optional).
|
|
34
|
+
getRoute={() => location.pathname}
|
|
35
|
+
/>
|
|
36
|
+
</>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That's it — a draggable launcher appears in the corner.
|
|
42
|
+
|
|
43
|
+
### Configuration
|
|
44
|
+
|
|
45
|
+
| Prop | Type | Default | Purpose |
|
|
46
|
+
|------|------|---------|---------|
|
|
47
|
+
| `enabled` | `boolean` | `NODE_ENV !== 'production'` | Master gate — the host owns the access decision. |
|
|
48
|
+
| `getRoute` | `() => string` | `location.pathname` | Current route for display. Adapters wire your router. |
|
|
49
|
+
| `openInEditor` | `(loc, editor?) => void` | protocol URL + copy | How source files open. Adapters provide a server-backed opener. |
|
|
50
|
+
| `graphEndpoint` | `string` | — | URL serving the component graph JSON (Component Graph Inspector). |
|
|
51
|
+
| `editor` | `'auto' \| 'vscode' \| 'cursor' \| 'webstorm' \| 'zed'` | `'auto'` | Preferred editor for protocol opens. |
|
|
52
|
+
| `theme` | `{ accent?, accentContrast? }` | purple | Accent color override. |
|
|
53
|
+
| `tools` | `string[]` | all | Restrict/reorder tools by id (`'logs'`, `'perf'`, `'graph'`). |
|
|
54
|
+
|
|
55
|
+
### Custom tools
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { registerTool } from 'react-dev-panel';
|
|
59
|
+
|
|
60
|
+
registerTool({
|
|
61
|
+
id: 'my-tool',
|
|
62
|
+
title: 'My Tool',
|
|
63
|
+
subtitle: 'Does a thing',
|
|
64
|
+
icon: <MyIcon />,
|
|
65
|
+
Panel: ({ onClose }) => <div>…</div>,
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Component Graph Inspector
|
|
70
|
+
|
|
71
|
+
Reliable component inspection that works in normal dev (Turbopack/Vite — no special build):
|
|
72
|
+
|
|
73
|
+
1. **Generate the graph** (components, files, parent/child/import/route edges):
|
|
74
|
+
```bash
|
|
75
|
+
npx dev-panel-graph --scan src
|
|
76
|
+
# → .dev-panel/component-graph.json
|
|
77
|
+
```
|
|
78
|
+
2. **Serve it + enable open-in-editor** with an adapter (below).
|
|
79
|
+
3. Open the panel → **Inspect mode** → hover (highlight + tooltip), click to lock, ⌘/Ctrl+click
|
|
80
|
+
to open. Tabs: **Graph** (search + relationship tree), **Page** (what's mounted on this
|
|
81
|
+
route), **File** (source + open).
|
|
82
|
+
|
|
83
|
+
Resolution climbs the React fiber tree to the nearest component that's in the graph, so hovering
|
|
84
|
+
a leaf still lands on your real app component. Open-in-editor uses the editor **running the
|
|
85
|
+
project** (`launch-editor`) — VS Code, Cursor, WebStorm, Zed, … — via the adapter endpoint.
|
|
86
|
+
|
|
87
|
+
### Adapters
|
|
88
|
+
|
|
89
|
+
**Vite**
|
|
90
|
+
```ts
|
|
91
|
+
import { devPanel } from 'react-dev-panel/vite';
|
|
92
|
+
export default defineConfig({ plugins: [react(), devPanel({ scan: ['src'] })] });
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Next.js (App Router)** — gate these yourself; dev/internal only.
|
|
96
|
+
```ts
|
|
97
|
+
// app/dev-panel/graph/route.ts
|
|
98
|
+
export const { GET } = createGraphRoute({ enabled: () => process.env.NODE_ENV !== 'production' });
|
|
99
|
+
// app/dev-panel/open-file/route.ts
|
|
100
|
+
export const { POST } = createOpenFileRoute({ enabled: () => process.env.NODE_ENV !== 'production' });
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Any Node server (CRA/Express/custom)**
|
|
104
|
+
```ts
|
|
105
|
+
import { createDevPanelMiddleware } from 'react-dev-panel/server';
|
|
106
|
+
app.use(createDevPanelMiddleware());
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Then wire the client:
|
|
110
|
+
```tsx
|
|
111
|
+
import { DevPanel, serverOpenInEditor, DEFAULT_GRAPH_ENDPOINT } from 'react-dev-panel';
|
|
112
|
+
<DevPanel enabled={isDev} graphEndpoint={DEFAULT_GRAPH_ENDPOINT} openInEditor={serverOpenInEditor} />
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Without an adapter the inspector still works at runtime (component names, tree from the graph if
|
|
116
|
+
served, page scan); open-in-editor falls back to `editor://` protocol URLs then copy.
|
|
117
|
+
|
|
118
|
+
## Roadmap
|
|
119
|
+
|
|
120
|
+
- [x] Framework-agnostic core (provider, launcher, self-contained styles, tool registry)
|
|
121
|
+
- [x] Developer Logs · Page Performance · Component Graph Inspector
|
|
122
|
+
- [x] CLI `dev-panel-graph` (TypeScript Compiler API)
|
|
123
|
+
- [x] Adapters: `/next`, `/vite`, `/server`
|
|
124
|
+
- [ ] Demo apps + browser-tested screenshots
|
|
125
|
+
- [ ] Optional Babel/SWC transform for exact element line:col
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
|
130
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var path = require('path');
|
|
4
|
+
var fs = require('fs');
|
|
5
|
+
var promises = require('fs/promises');
|
|
6
|
+
|
|
7
|
+
// src/adapters/next.ts
|
|
8
|
+
|
|
9
|
+
// src/core/server-open.ts
|
|
10
|
+
var DEFAULT_GRAPH_ENDPOINT = "/dev-panel/graph";
|
|
11
|
+
var DEFAULT_OPEN_ENDPOINT = "/dev-panel/open-file";
|
|
12
|
+
function createServerOpenInEditor(endpoint = DEFAULT_OPEN_ENDPOINT) {
|
|
13
|
+
return async (loc, editor = "auto") => {
|
|
14
|
+
try {
|
|
15
|
+
const res = await fetch(endpoint, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: { "Content-Type": "application/json" },
|
|
18
|
+
body: JSON.stringify({ file: loc.file, line: loc.line, column: loc.column, editor })
|
|
19
|
+
});
|
|
20
|
+
if (res.ok) return true;
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
if (typeof navigator !== "undefined" && navigator.clipboard) {
|
|
24
|
+
await navigator.clipboard.writeText(`${loc.file}${loc.line ? `:${loc.line}` : ""}`).catch(() => void 0);
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
var serverOpenInEditor = createServerOpenInEditor();
|
|
30
|
+
|
|
31
|
+
// src/adapters/next.ts
|
|
32
|
+
var EDITOR_BIN = {
|
|
33
|
+
auto: void 0,
|
|
34
|
+
vscode: "code",
|
|
35
|
+
cursor: "cursor",
|
|
36
|
+
webstorm: "webstorm",
|
|
37
|
+
zed: "zed"
|
|
38
|
+
};
|
|
39
|
+
var json = (body, status = 200) => new Response(typeof body === "string" ? body : JSON.stringify(body), {
|
|
40
|
+
status,
|
|
41
|
+
headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
|
|
42
|
+
});
|
|
43
|
+
async function gate(enabled) {
|
|
44
|
+
if (process.env.NODE_ENV === "production" && !enabled) return false;
|
|
45
|
+
if (enabled) return await enabled() === true;
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
function createGraphRoute(options = {}) {
|
|
49
|
+
const root = path.resolve(options.root ?? process.cwd());
|
|
50
|
+
const graphFile = path.resolve(root, options.graphFile ?? ".dev-panel/component-graph.json");
|
|
51
|
+
return {
|
|
52
|
+
async GET() {
|
|
53
|
+
if (!await gate(options.enabled)) return json({ error: "Not found" }, 404);
|
|
54
|
+
try {
|
|
55
|
+
return json(await promises.readFile(graphFile, "utf8"));
|
|
56
|
+
} catch {
|
|
57
|
+
return json({ error: "graph-not-generated", hint: "Run: npx dev-panel-graph" }, 404);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function createOpenFileRoute(options = {}) {
|
|
63
|
+
const root = path.resolve(options.root ?? process.cwd());
|
|
64
|
+
return {
|
|
65
|
+
async POST(req) {
|
|
66
|
+
if (!await gate(options.enabled)) return json({ error: "Not found" }, 404);
|
|
67
|
+
let body;
|
|
68
|
+
try {
|
|
69
|
+
body = await req.json();
|
|
70
|
+
} catch {
|
|
71
|
+
return json({ error: "invalid body" }, 400);
|
|
72
|
+
}
|
|
73
|
+
if (!body.file) return json({ error: "file required" }, 400);
|
|
74
|
+
const abs = path.isAbsolute(body.file) ? path.resolve(body.file) : path.resolve(root, body.file);
|
|
75
|
+
const rel = path.relative(root, abs);
|
|
76
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) return json({ error: "path escapes root" }, 403);
|
|
77
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) return json({ error: "not found" }, 404);
|
|
78
|
+
try {
|
|
79
|
+
const launch = (await import('launch-editor')).default;
|
|
80
|
+
launch(`${abs}:${body.line ?? 1}:${body.column ?? 1}`, EDITOR_BIN[body.editor ?? "auto"]);
|
|
81
|
+
return json({ ok: true });
|
|
82
|
+
} catch (err) {
|
|
83
|
+
return json({ error: err instanceof Error ? err.message : "open failed" }, 500);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
exports.DEFAULT_GRAPH_ENDPOINT = DEFAULT_GRAPH_ENDPOINT;
|
|
90
|
+
exports.DEFAULT_OPEN_ENDPOINT = DEFAULT_OPEN_ENDPOINT;
|
|
91
|
+
exports.createGraphRoute = createGraphRoute;
|
|
92
|
+
exports.createOpenFileRoute = createOpenFileRoute;
|
|
93
|
+
exports.serverOpenInEditor = serverOpenInEditor;
|
|
94
|
+
//# sourceMappingURL=next.cjs.map
|
|
95
|
+
//# sourceMappingURL=next.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/server-open.ts","../../src/adapters/next.ts"],"names":["resolve","readFile","isAbsolute","relative","existsSync","statSync"],"mappings":"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","file":"next.cjs","sourcesContent":["import type { OpenInEditor } from './types';\n\n/**\n * Default endpoint paths the adapters mount and the client talks to. No leading underscore —\n * Next.js App Router treats `_`-prefixed segments as private (non-routable), so this path works\n * uniformly across the Next, Vite, and generic-server adapters.\n */\nexport const DEFAULT_GRAPH_ENDPOINT = '/dev-panel/graph';\nexport const DEFAULT_OPEN_ENDPOINT = '/dev-panel/open-file';\n\n/**\n * A client `openInEditor` that POSTs to a server endpoint (mounted by an adapter), which opens\n * the file in the editor running the project via `launch-editor`. Falls back to copying the\n * path when the endpoint is unreachable. Pass to `<DevPanel openInEditor={serverOpenInEditor} />`.\n */\nexport function createServerOpenInEditor(endpoint = DEFAULT_OPEN_ENDPOINT): OpenInEditor {\n return async (loc, editor = 'auto') => {\n try {\n const res = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ file: loc.file, line: loc.line, column: loc.column, editor }),\n });\n if (res.ok) return true;\n } catch {\n /* fall through to copy */\n }\n if (typeof navigator !== 'undefined' && navigator.clipboard) {\n await navigator.clipboard\n .writeText(`${loc.file}${loc.line ? `:${loc.line}` : ''}`)\n .catch(() => undefined);\n }\n return false;\n };\n}\n\n/** Ready-to-use server opener pointed at the default endpoint. */\nexport const serverOpenInEditor: OpenInEditor = createServerOpenInEditor();\n","/**\n * Next.js App Router adapter for react-dev-panel. Provides route-handler factories that serve\n * the component graph and open files in the running editor.\n *\n * // app/dev-panel/graph/route.ts\n * import { createGraphRoute } from 'react-dev-panel/next';\n * export const { GET } = createGraphRoute();\n *\n * // app/dev-panel/open-file/route.ts\n * import { createOpenFileRoute } from 'react-dev-panel/next';\n * export const { POST } = createOpenFileRoute();\n *\n * Then on the client:\n * import { DevPanel, serverOpenInEditor, DEFAULT_GRAPH_ENDPOINT } from 'react-dev-panel';\n * <DevPanel enabled={isDev} graphEndpoint={DEFAULT_GRAPH_ENDPOINT} openInEditor={serverOpenInEditor} />\n *\n * IMPORTANT: gate these routes yourself (NODE_ENV / auth) — pass `enabled` or wrap the handler.\n * They are intended for development and internal use only.\n */\nimport { resolve, relative, isAbsolute } from 'node:path';\nimport { existsSync, statSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\n\nexport interface NextGraphRouteOptions {\n root?: string;\n graphFile?: string;\n /** Extra gate — return false to 404 (e.g. NODE_ENV check + developer auth). */\n enabled?: () => boolean | Promise<boolean>;\n}\n\nconst EDITOR_BIN: Record<string, string | undefined> = {\n auto: undefined,\n vscode: 'code',\n cursor: 'cursor',\n webstorm: 'webstorm',\n zed: 'zed',\n};\n\nconst json = (body: unknown, status = 200): Response =>\n new Response(typeof body === 'string' ? body : JSON.stringify(body), {\n status,\n headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' },\n });\n\nasync function gate(enabled?: () => boolean | Promise<boolean>): Promise<boolean> {\n if (process.env.NODE_ENV === 'production' && !enabled) return false;\n if (enabled) return (await enabled()) === true;\n return true;\n}\n\nexport function createGraphRoute(options: NextGraphRouteOptions = {}) {\n const root = resolve(options.root ?? process.cwd());\n const graphFile = resolve(root, options.graphFile ?? '.dev-panel/component-graph.json');\n return {\n async GET() {\n if (!(await gate(options.enabled))) return json({ error: 'Not found' }, 404);\n try {\n return json(await readFile(graphFile, 'utf8'));\n } catch {\n return json({ error: 'graph-not-generated', hint: 'Run: npx dev-panel-graph' }, 404);\n }\n },\n };\n}\n\nexport function createOpenFileRoute(options: NextGraphRouteOptions = {}) {\n const root = resolve(options.root ?? process.cwd());\n return {\n async POST(req: Request) {\n if (!(await gate(options.enabled))) return json({ error: 'Not found' }, 404);\n let body: { file?: string; line?: number; column?: number; editor?: string };\n try {\n body = await req.json();\n } catch {\n return json({ error: 'invalid body' }, 400);\n }\n if (!body.file) return json({ error: 'file required' }, 400);\n const abs = isAbsolute(body.file) ? resolve(body.file) : resolve(root, body.file);\n const rel = relative(root, abs);\n if (rel.startsWith('..') || isAbsolute(rel)) return json({ error: 'path escapes root' }, 403);\n if (!existsSync(abs) || !statSync(abs).isFile()) return json({ error: 'not found' }, 404);\n try {\n const launch = (await import('launch-editor')).default;\n launch(`${abs}:${body.line ?? 1}:${body.column ?? 1}`, EDITOR_BIN[body.editor ?? 'auto']);\n return json({ ok: true });\n } catch (err) {\n return json({ error: err instanceof Error ? err.message : 'open failed' }, 500);\n }\n },\n };\n}\n\nexport { DEFAULT_GRAPH_ENDPOINT, DEFAULT_OPEN_ENDPOINT, serverOpenInEditor } from '../core/server-open';\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { D as DEFAULT_GRAPH_ENDPOINT, a as DEFAULT_OPEN_ENDPOINT, s as serverOpenInEditor } from '../server-open-B4FK0jQF.cjs';
|
|
2
|
+
import 'react';
|
|
3
|
+
|
|
4
|
+
interface NextGraphRouteOptions {
|
|
5
|
+
root?: string;
|
|
6
|
+
graphFile?: string;
|
|
7
|
+
/** Extra gate — return false to 404 (e.g. NODE_ENV check + developer auth). */
|
|
8
|
+
enabled?: () => boolean | Promise<boolean>;
|
|
9
|
+
}
|
|
10
|
+
declare function createGraphRoute(options?: NextGraphRouteOptions): {
|
|
11
|
+
GET(): Promise<Response>;
|
|
12
|
+
};
|
|
13
|
+
declare function createOpenFileRoute(options?: NextGraphRouteOptions): {
|
|
14
|
+
POST(req: Request): Promise<Response>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export { type NextGraphRouteOptions, createGraphRoute, createOpenFileRoute };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { D as DEFAULT_GRAPH_ENDPOINT, a as DEFAULT_OPEN_ENDPOINT, s as serverOpenInEditor } from '../server-open-B4FK0jQF.js';
|
|
2
|
+
import 'react';
|
|
3
|
+
|
|
4
|
+
interface NextGraphRouteOptions {
|
|
5
|
+
root?: string;
|
|
6
|
+
graphFile?: string;
|
|
7
|
+
/** Extra gate — return false to 404 (e.g. NODE_ENV check + developer auth). */
|
|
8
|
+
enabled?: () => boolean | Promise<boolean>;
|
|
9
|
+
}
|
|
10
|
+
declare function createGraphRoute(options?: NextGraphRouteOptions): {
|
|
11
|
+
GET(): Promise<Response>;
|
|
12
|
+
};
|
|
13
|
+
declare function createOpenFileRoute(options?: NextGraphRouteOptions): {
|
|
14
|
+
POST(req: Request): Promise<Response>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export { type NextGraphRouteOptions, createGraphRoute, createOpenFileRoute };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export { DEFAULT_GRAPH_ENDPOINT, DEFAULT_OPEN_ENDPOINT, serverOpenInEditor } from '../chunk-2ZAPVMUL.js';
|
|
2
|
+
import { resolve, isAbsolute, relative } from 'path';
|
|
3
|
+
import { existsSync, statSync } from 'fs';
|
|
4
|
+
import { readFile } from 'fs/promises';
|
|
5
|
+
|
|
6
|
+
var EDITOR_BIN = {
|
|
7
|
+
auto: void 0,
|
|
8
|
+
vscode: "code",
|
|
9
|
+
cursor: "cursor",
|
|
10
|
+
webstorm: "webstorm",
|
|
11
|
+
zed: "zed"
|
|
12
|
+
};
|
|
13
|
+
var json = (body, status = 200) => new Response(typeof body === "string" ? body : JSON.stringify(body), {
|
|
14
|
+
status,
|
|
15
|
+
headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
|
|
16
|
+
});
|
|
17
|
+
async function gate(enabled) {
|
|
18
|
+
if (process.env.NODE_ENV === "production" && !enabled) return false;
|
|
19
|
+
if (enabled) return await enabled() === true;
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
function createGraphRoute(options = {}) {
|
|
23
|
+
const root = resolve(options.root ?? process.cwd());
|
|
24
|
+
const graphFile = resolve(root, options.graphFile ?? ".dev-panel/component-graph.json");
|
|
25
|
+
return {
|
|
26
|
+
async GET() {
|
|
27
|
+
if (!await gate(options.enabled)) return json({ error: "Not found" }, 404);
|
|
28
|
+
try {
|
|
29
|
+
return json(await readFile(graphFile, "utf8"));
|
|
30
|
+
} catch {
|
|
31
|
+
return json({ error: "graph-not-generated", hint: "Run: npx dev-panel-graph" }, 404);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function createOpenFileRoute(options = {}) {
|
|
37
|
+
const root = resolve(options.root ?? process.cwd());
|
|
38
|
+
return {
|
|
39
|
+
async POST(req) {
|
|
40
|
+
if (!await gate(options.enabled)) return json({ error: "Not found" }, 404);
|
|
41
|
+
let body;
|
|
42
|
+
try {
|
|
43
|
+
body = await req.json();
|
|
44
|
+
} catch {
|
|
45
|
+
return json({ error: "invalid body" }, 400);
|
|
46
|
+
}
|
|
47
|
+
if (!body.file) return json({ error: "file required" }, 400);
|
|
48
|
+
const abs = isAbsolute(body.file) ? resolve(body.file) : resolve(root, body.file);
|
|
49
|
+
const rel = relative(root, abs);
|
|
50
|
+
if (rel.startsWith("..") || isAbsolute(rel)) return json({ error: "path escapes root" }, 403);
|
|
51
|
+
if (!existsSync(abs) || !statSync(abs).isFile()) return json({ error: "not found" }, 404);
|
|
52
|
+
try {
|
|
53
|
+
const launch = (await import('launch-editor')).default;
|
|
54
|
+
launch(`${abs}:${body.line ?? 1}:${body.column ?? 1}`, EDITOR_BIN[body.editor ?? "auto"]);
|
|
55
|
+
return json({ ok: true });
|
|
56
|
+
} catch (err) {
|
|
57
|
+
return json({ error: err instanceof Error ? err.message : "open failed" }, 500);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { createGraphRoute, createOpenFileRoute };
|
|
64
|
+
//# sourceMappingURL=next.js.map
|
|
65
|
+
//# sourceMappingURL=next.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/next.ts"],"names":[],"mappings":"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","file":"next.js","sourcesContent":["/**\n * Next.js App Router adapter for react-dev-panel. Provides route-handler factories that serve\n * the component graph and open files in the running editor.\n *\n * // app/dev-panel/graph/route.ts\n * import { createGraphRoute } from 'react-dev-panel/next';\n * export const { GET } = createGraphRoute();\n *\n * // app/dev-panel/open-file/route.ts\n * import { createOpenFileRoute } from 'react-dev-panel/next';\n * export const { POST } = createOpenFileRoute();\n *\n * Then on the client:\n * import { DevPanel, serverOpenInEditor, DEFAULT_GRAPH_ENDPOINT } from 'react-dev-panel';\n * <DevPanel enabled={isDev} graphEndpoint={DEFAULT_GRAPH_ENDPOINT} openInEditor={serverOpenInEditor} />\n *\n * IMPORTANT: gate these routes yourself (NODE_ENV / auth) — pass `enabled` or wrap the handler.\n * They are intended for development and internal use only.\n */\nimport { resolve, relative, isAbsolute } from 'node:path';\nimport { existsSync, statSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\n\nexport interface NextGraphRouteOptions {\n root?: string;\n graphFile?: string;\n /** Extra gate — return false to 404 (e.g. NODE_ENV check + developer auth). */\n enabled?: () => boolean | Promise<boolean>;\n}\n\nconst EDITOR_BIN: Record<string, string | undefined> = {\n auto: undefined,\n vscode: 'code',\n cursor: 'cursor',\n webstorm: 'webstorm',\n zed: 'zed',\n};\n\nconst json = (body: unknown, status = 200): Response =>\n new Response(typeof body === 'string' ? body : JSON.stringify(body), {\n status,\n headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' },\n });\n\nasync function gate(enabled?: () => boolean | Promise<boolean>): Promise<boolean> {\n if (process.env.NODE_ENV === 'production' && !enabled) return false;\n if (enabled) return (await enabled()) === true;\n return true;\n}\n\nexport function createGraphRoute(options: NextGraphRouteOptions = {}) {\n const root = resolve(options.root ?? process.cwd());\n const graphFile = resolve(root, options.graphFile ?? '.dev-panel/component-graph.json');\n return {\n async GET() {\n if (!(await gate(options.enabled))) return json({ error: 'Not found' }, 404);\n try {\n return json(await readFile(graphFile, 'utf8'));\n } catch {\n return json({ error: 'graph-not-generated', hint: 'Run: npx dev-panel-graph' }, 404);\n }\n },\n };\n}\n\nexport function createOpenFileRoute(options: NextGraphRouteOptions = {}) {\n const root = resolve(options.root ?? process.cwd());\n return {\n async POST(req: Request) {\n if (!(await gate(options.enabled))) return json({ error: 'Not found' }, 404);\n let body: { file?: string; line?: number; column?: number; editor?: string };\n try {\n body = await req.json();\n } catch {\n return json({ error: 'invalid body' }, 400);\n }\n if (!body.file) return json({ error: 'file required' }, 400);\n const abs = isAbsolute(body.file) ? resolve(body.file) : resolve(root, body.file);\n const rel = relative(root, abs);\n if (rel.startsWith('..') || isAbsolute(rel)) return json({ error: 'path escapes root' }, 403);\n if (!existsSync(abs) || !statSync(abs).isFile()) return json({ error: 'not found' }, 404);\n try {\n const launch = (await import('launch-editor')).default;\n launch(`${abs}:${body.line ?? 1}:${body.column ?? 1}`, EDITOR_BIN[body.editor ?? 'auto']);\n return json({ ok: true });\n } catch (err) {\n return json({ error: err instanceof Error ? err.message : 'open failed' }, 500);\n }\n },\n };\n}\n\nexport { DEFAULT_GRAPH_ENDPOINT, DEFAULT_OPEN_ENDPOINT, serverOpenInEditor } from '../core/server-open';\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var path = require('path');
|
|
4
|
+
var fs = require('fs');
|
|
5
|
+
|
|
6
|
+
// src/adapters/server.ts
|
|
7
|
+
|
|
8
|
+
// src/core/server-open.ts
|
|
9
|
+
var DEFAULT_GRAPH_ENDPOINT = "/dev-panel/graph";
|
|
10
|
+
var DEFAULT_OPEN_ENDPOINT = "/dev-panel/open-file";
|
|
11
|
+
|
|
12
|
+
// src/adapters/server.ts
|
|
13
|
+
var EDITOR_BIN = {
|
|
14
|
+
auto: void 0,
|
|
15
|
+
vscode: "code",
|
|
16
|
+
cursor: "cursor",
|
|
17
|
+
webstorm: "webstorm",
|
|
18
|
+
zed: "zed"
|
|
19
|
+
};
|
|
20
|
+
function send(res, status, body) {
|
|
21
|
+
res.statusCode = status;
|
|
22
|
+
res.setHeader("Content-Type", "application/json");
|
|
23
|
+
res.setHeader("Cache-Control", "no-store");
|
|
24
|
+
res.end(typeof body === "string" ? body : JSON.stringify(body));
|
|
25
|
+
}
|
|
26
|
+
async function readBody(req) {
|
|
27
|
+
const chunks = [];
|
|
28
|
+
for await (const c of req) chunks.push(c);
|
|
29
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
30
|
+
return raw ? JSON.parse(raw) : {};
|
|
31
|
+
}
|
|
32
|
+
function createDevPanelMiddleware(options = {}) {
|
|
33
|
+
const root = path.resolve(options.root ?? process.cwd());
|
|
34
|
+
const graphFile = path.resolve(root, options.graphFile ?? ".dev-panel/component-graph.json");
|
|
35
|
+
const graphEndpoint = options.graphEndpoint ?? DEFAULT_GRAPH_ENDPOINT;
|
|
36
|
+
const openEndpoint = options.openEndpoint ?? DEFAULT_OPEN_ENDPOINT;
|
|
37
|
+
return function devPanelMiddleware(req, res, next) {
|
|
38
|
+
const url = (req.url ?? "").split("?")[0];
|
|
39
|
+
if (req.method === "GET" && url === graphEndpoint) {
|
|
40
|
+
try {
|
|
41
|
+
send(res, 200, fs.readFileSync(graphFile, "utf8"));
|
|
42
|
+
} catch {
|
|
43
|
+
send(res, 404, { error: "graph-not-generated", hint: "Run: npx dev-panel-graph" });
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (req.method === "POST" && url === openEndpoint) {
|
|
48
|
+
void (async () => {
|
|
49
|
+
try {
|
|
50
|
+
const body = await readBody(req);
|
|
51
|
+
if (!body.file) return send(res, 400, { error: "file required" });
|
|
52
|
+
const abs = path.isAbsolute(body.file) ? path.resolve(body.file) : path.resolve(root, body.file);
|
|
53
|
+
const rel = path.relative(root, abs);
|
|
54
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) return send(res, 403, { error: "path escapes root" });
|
|
55
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) return send(res, 404, { error: "not found" });
|
|
56
|
+
const launch = (await import('launch-editor')).default;
|
|
57
|
+
const target = `${abs}:${body.line ?? 1}:${body.column ?? 1}`;
|
|
58
|
+
launch(target, EDITOR_BIN[body.editor ?? "auto"]);
|
|
59
|
+
return send(res, 200, { ok: true });
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return send(res, 500, { error: err instanceof Error ? err.message : "open failed" });
|
|
62
|
+
}
|
|
63
|
+
})();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
next();
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
exports.createDevPanelMiddleware = createDevPanelMiddleware;
|
|
71
|
+
//# sourceMappingURL=server.cjs.map
|
|
72
|
+
//# sourceMappingURL=server.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/server-open.ts","../../src/adapters/server.ts"],"names":["resolve","readFileSync","isAbsolute","relative","existsSync","statSync"],"mappings":"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","file":"server.cjs","sourcesContent":["import type { OpenInEditor } from './types';\n\n/**\n * Default endpoint paths the adapters mount and the client talks to. No leading underscore —\n * Next.js App Router treats `_`-prefixed segments as private (non-routable), so this path works\n * uniformly across the Next, Vite, and generic-server adapters.\n */\nexport const DEFAULT_GRAPH_ENDPOINT = '/dev-panel/graph';\nexport const DEFAULT_OPEN_ENDPOINT = '/dev-panel/open-file';\n\n/**\n * A client `openInEditor` that POSTs to a server endpoint (mounted by an adapter), which opens\n * the file in the editor running the project via `launch-editor`. Falls back to copying the\n * path when the endpoint is unreachable. Pass to `<DevPanel openInEditor={serverOpenInEditor} />`.\n */\nexport function createServerOpenInEditor(endpoint = DEFAULT_OPEN_ENDPOINT): OpenInEditor {\n return async (loc, editor = 'auto') => {\n try {\n const res = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ file: loc.file, line: loc.line, column: loc.column, editor }),\n });\n if (res.ok) return true;\n } catch {\n /* fall through to copy */\n }\n if (typeof navigator !== 'undefined' && navigator.clipboard) {\n await navigator.clipboard\n .writeText(`${loc.file}${loc.line ? `:${loc.line}` : ''}`)\n .catch(() => undefined);\n }\n return false;\n };\n}\n\n/** Ready-to-use server opener pointed at the default endpoint. */\nexport const serverOpenInEditor: OpenInEditor = createServerOpenInEditor();\n","/**\n * Framework-agnostic dev middleware (connect/express/http style) that powers the Component\n * Graph Inspector's server features:\n * GET /__dev-panel/graph → serves the generated component-graph JSON\n * POST /__dev-panel/open-file → opens a file in the editor running the project (launch-editor)\n *\n * Dev-only by design — mount it only in your dev server. Opens are path-traversal-guarded to\n * the repo root. Used directly for CRA/Express/custom servers, and internally by the Vite plugin.\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { resolve, relative, isAbsolute } from 'node:path';\nimport { existsSync, statSync, readFileSync } from 'node:fs';\n\nimport { DEFAULT_GRAPH_ENDPOINT, DEFAULT_OPEN_ENDPOINT } from '../core/server-open';\n\nexport interface DevPanelServerOptions {\n /** Repo root (absolute). Default: process.cwd(). */\n root?: string;\n /** Path to the generated graph JSON, relative to root. Default: .dev-panel/component-graph.json */\n graphFile?: string;\n graphEndpoint?: string;\n openEndpoint?: string;\n}\n\nconst EDITOR_BIN: Record<string, string | undefined> = {\n auto: undefined,\n vscode: 'code',\n cursor: 'cursor',\n webstorm: 'webstorm',\n zed: 'zed',\n};\n\ntype Next = (err?: unknown) => void;\n\nfunction send(res: ServerResponse, status: number, body: unknown) {\n res.statusCode = status;\n res.setHeader('Content-Type', 'application/json');\n res.setHeader('Cache-Control', 'no-store');\n res.end(typeof body === 'string' ? body : JSON.stringify(body));\n}\n\nasync function readBody(req: IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = [];\n for await (const c of req) chunks.push(c as Buffer);\n const raw = Buffer.concat(chunks).toString('utf8');\n return raw ? JSON.parse(raw) : {};\n}\n\nexport function createDevPanelMiddleware(options: DevPanelServerOptions = {}) {\n const root = resolve(options.root ?? process.cwd());\n const graphFile = resolve(root, options.graphFile ?? '.dev-panel/component-graph.json');\n const graphEndpoint = options.graphEndpoint ?? DEFAULT_GRAPH_ENDPOINT;\n const openEndpoint = options.openEndpoint ?? DEFAULT_OPEN_ENDPOINT;\n\n return function devPanelMiddleware(req: IncomingMessage, res: ServerResponse, next: Next): void {\n const url = (req.url ?? '').split('?')[0];\n\n if (req.method === 'GET' && url === graphEndpoint) {\n try {\n send(res, 200, readFileSync(graphFile, 'utf8'));\n } catch {\n send(res, 404, { error: 'graph-not-generated', hint: 'Run: npx dev-panel-graph' });\n }\n return;\n }\n\n if (req.method === 'POST' && url === openEndpoint) {\n void (async () => {\n try {\n const body = (await readBody(req)) as {\n file?: string;\n line?: number;\n column?: number;\n editor?: string;\n };\n if (!body.file) return send(res, 400, { error: 'file required' });\n const abs = isAbsolute(body.file) ? resolve(body.file) : resolve(root, body.file);\n const rel = relative(root, abs);\n if (rel.startsWith('..') || isAbsolute(rel)) return send(res, 403, { error: 'path escapes root' });\n if (!existsSync(abs) || !statSync(abs).isFile()) return send(res, 404, { error: 'not found' });\n\n const launch = (await import('launch-editor')).default;\n const target = `${abs}:${body.line ?? 1}:${body.column ?? 1}`;\n launch(target, EDITOR_BIN[body.editor ?? 'auto']);\n return send(res, 200, { ok: true });\n } catch (err) {\n return send(res, 500, { error: err instanceof Error ? err.message : 'open failed' });\n }\n })();\n return;\n }\n\n next();\n };\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Framework-agnostic dev middleware (connect/express/http style) that powers the Component
|
|
5
|
+
* Graph Inspector's server features:
|
|
6
|
+
* GET /__dev-panel/graph → serves the generated component-graph JSON
|
|
7
|
+
* POST /__dev-panel/open-file → opens a file in the editor running the project (launch-editor)
|
|
8
|
+
*
|
|
9
|
+
* Dev-only by design — mount it only in your dev server. Opens are path-traversal-guarded to
|
|
10
|
+
* the repo root. Used directly for CRA/Express/custom servers, and internally by the Vite plugin.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
interface DevPanelServerOptions {
|
|
14
|
+
/** Repo root (absolute). Default: process.cwd(). */
|
|
15
|
+
root?: string;
|
|
16
|
+
/** Path to the generated graph JSON, relative to root. Default: .dev-panel/component-graph.json */
|
|
17
|
+
graphFile?: string;
|
|
18
|
+
graphEndpoint?: string;
|
|
19
|
+
openEndpoint?: string;
|
|
20
|
+
}
|
|
21
|
+
type Next = (err?: unknown) => void;
|
|
22
|
+
declare function createDevPanelMiddleware(options?: DevPanelServerOptions): (req: IncomingMessage, res: ServerResponse, next: Next) => void;
|
|
23
|
+
|
|
24
|
+
export { type DevPanelServerOptions, createDevPanelMiddleware };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Framework-agnostic dev middleware (connect/express/http style) that powers the Component
|
|
5
|
+
* Graph Inspector's server features:
|
|
6
|
+
* GET /__dev-panel/graph → serves the generated component-graph JSON
|
|
7
|
+
* POST /__dev-panel/open-file → opens a file in the editor running the project (launch-editor)
|
|
8
|
+
*
|
|
9
|
+
* Dev-only by design — mount it only in your dev server. Opens are path-traversal-guarded to
|
|
10
|
+
* the repo root. Used directly for CRA/Express/custom servers, and internally by the Vite plugin.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
interface DevPanelServerOptions {
|
|
14
|
+
/** Repo root (absolute). Default: process.cwd(). */
|
|
15
|
+
root?: string;
|
|
16
|
+
/** Path to the generated graph JSON, relative to root. Default: .dev-panel/component-graph.json */
|
|
17
|
+
graphFile?: string;
|
|
18
|
+
graphEndpoint?: string;
|
|
19
|
+
openEndpoint?: string;
|
|
20
|
+
}
|
|
21
|
+
type Next = (err?: unknown) => void;
|
|
22
|
+
declare function createDevPanelMiddleware(options?: DevPanelServerOptions): (req: IncomingMessage, res: ServerResponse, next: Next) => void;
|
|
23
|
+
|
|
24
|
+
export { type DevPanelServerOptions, createDevPanelMiddleware };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"server.js"}
|