@rahul_ur/devlink 1.0.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/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # devlink
2
+
3
+ Click any element in your browser → jump to its source code → edit it → UI updates live.
4
+
5
+ Works with **Vite**, **Next.js**, and **CRA**. Fully local — no cloud, no API keys.
6
+
7
+ ---
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @rahul_ur/devlink
13
+ npm install -D @rahul_ur/devlink-babel-plugin @rahul_ur/devlink-vite-plugin
14
+ ```
15
+
16
+ ---
17
+
18
+ ## Setup (one command)
19
+
20
+ ```bash
21
+ npx devlink setup
22
+ ```
23
+
24
+ This automatically:
25
+ - Detects your framework (Vite / Next.js / CRA)
26
+ - Patches your `vite.config.ts` or `.babelrc`
27
+ - Creates `devlink.config.ts` with your project paths
28
+ - Adds `devlink:bridge` script to `package.json`
29
+
30
+ ---
31
+
32
+ ## Add to your app entry
33
+
34
+ ```tsx
35
+ // src/main.tsx (Vite) or pages/_app.tsx (Next.js)
36
+ import { DevlinkBridge } from '@rahul_ur/devlink-bridge'
37
+ import { DevlinkStudio } from '@rahul_ur/devlink-studio'
38
+ import { devlinkConfig } from '../devlink.config'
39
+
40
+ const bridge = new DevlinkBridge()
41
+
42
+ function Root() {
43
+ return (
44
+ <DevlinkStudio bridge={bridge} {...devlinkConfig}>
45
+ <App />
46
+ </DevlinkStudio>
47
+ )
48
+ }
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Start the bridge server
54
+
55
+ Open a separate terminal and keep it running:
56
+
57
+ ```bash
58
+ npm run devlink:bridge
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Use it
64
+
65
+ Start your app normally (`npm run dev`) then:
66
+
67
+ | Action | How |
68
+ |--------|-----|
69
+ | Toggle inspect mode | `Alt+D` |
70
+ | Click element → open source | Click any element |
71
+ | Box select multiple | `⬚ Box` button then drag |
72
+ | Area capture | Hold `+` and drag |
73
+ | Save file | `Ctrl+S` |
74
+ | Exit inspect mode | `Esc` or right-click |
75
+
76
+ ---
77
+
78
+ ## Vite config (manual)
79
+
80
+ If auto-setup didn't patch your config:
81
+
82
+ ```ts
83
+ // vite.config.ts
84
+ import { defineConfig } from 'vite'
85
+ import react from '@vitejs/plugin-react'
86
+ import { devlinkBabelConfig } from '@rahul_ur/devlink-vite-plugin'
87
+
88
+ export default defineConfig({
89
+ plugins: [
90
+ react({
91
+ babel: {
92
+ plugins: [
93
+ devlinkBabelConfig({ root: process.cwd() }),
94
+ ].filter(Boolean),
95
+ },
96
+ }),
97
+ ],
98
+ })
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Next.js config (manual)
104
+
105
+ ```json
106
+ // .babelrc
107
+ {
108
+ "presets": ["next/babel"],
109
+ "env": {
110
+ "development": {
111
+ "plugins": [
112
+ ["@rahul_ur/devlink-babel-plugin", { "root": ".", "envs": ["development"] }]
113
+ ]
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ ---
120
+
121
+ ## DevlinkStudio props
122
+
123
+ | Prop | Type | Default | Description |
124
+ |------|------|---------|-------------|
125
+ | `bridge` | `DevlinkBridge` | required | Bridge instance |
126
+ | `projectRoot` | `string` | required | Absolute path to your project root |
127
+ | `rootPath` | `string` | required | Path shown in file tree (usually `projectRoot/src`) |
128
+ | `defaultDocked` | `boolean` | `false` | Start editor docked to the right |
129
+ | `dockedWidth` | `number` | `460` | Width of docked panel in px |
130
+ | `enabled` | `boolean` | `NODE_ENV=development` | Disable entirely in production |
131
+
132
+ ---
133
+
134
+ ## Packages
135
+
136
+ This package installs all of these automatically:
137
+
138
+ | Package | Purpose |
139
+ |---------|---------|
140
+ | `@rahul_ur/devlink-bridge` | WebSocket tunnel between browser and local filesystem |
141
+ | `@rahul_ur/devlink-studio` | Floating Monaco editor + inspector + terminal + AI bar |
142
+ | `@rahul_ur/devlink-babel-plugin` | Stamps `data-source` on every JSX element at build time |
143
+ | `@rahul_ur/devlink-vite-plugin` | Wires babel plugin into Vite + React automatically |
144
+
145
+ ---
146
+
147
+ ## How it works
148
+
149
+ ```
150
+ Your browser app
151
+ → clicks element
152
+ → reads data-source="src/Button.tsx:9:4" (stamped by babel plugin)
153
+ → sends to bridge over local WebSocket
154
+ → bridge reads file from disk
155
+ → editor opens file, highlights lines
156
+ → you edit + Ctrl+S
157
+ → bridge writes to disk
158
+ → HMR reloads your app
159
+ → UI updates
160
+ ```
161
+
162
+ Everything stays on your machine. No cloud. No uploads.
163
+
164
+ ---
165
+
166
+ ## License
167
+
168
+ MIT
package/bin/cli.mjs ADDED
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { execSync } from 'node:child_process';
5
+
6
+ const projectRoot = process.cwd();
7
+ const args = process.argv.slice(2);
8
+ const command = args[0] || 'setup';
9
+
10
+ const log = (msg) => console.log(`\x1b[36m[devlink]\x1b[0m ${msg}`);
11
+ const ok = (msg) => console.log(`\x1b[32m[devlink] ✓\x1b[0m ${msg}`);
12
+ const warn = (msg) => console.warn(`\x1b[33m[devlink] ⚠\x1b[0m ${msg}`);
13
+ const err = (msg) => console.error(`\x1b[31m[devlink] ✗\x1b[0m ${msg}`);
14
+
15
+ function exists(p) { return fs.existsSync(p); }
16
+ function readJson(p) { return JSON.parse(fs.readFileSync(p, 'utf8')); }
17
+ function writeJson(p, v) { fs.writeFileSync(p, `${JSON.stringify(v, null, 2)}\n`); }
18
+
19
+ // ─── Detect framework ─────────────────────────────────────────────────────────
20
+
21
+ function detectFramework() {
22
+ const pkgPath = path.join(projectRoot, 'package.json');
23
+ if (!exists(pkgPath)) return 'unknown';
24
+ const pkg = readJson(pkgPath);
25
+ const all = { ...pkg.dependencies, ...pkg.devDependencies };
26
+ if (all['react-scripts']) return 'cra';
27
+ if (all['next']) return 'next';
28
+ if (all['vite'] || all['@vitejs/plugin-react']) return 'vite';
29
+ return 'unknown';
30
+ }
31
+
32
+ // ─── Add npm scripts ──────────────────────────────────────────────────────────
33
+
34
+ function addScripts() {
35
+ const pkgPath = path.join(projectRoot, 'package.json');
36
+ if (!exists(pkgPath)) return;
37
+ const pkg = readJson(pkgPath);
38
+ pkg.scripts = pkg.scripts || {};
39
+ let changed = false;
40
+ if (!pkg.scripts['devlink:bridge']) {
41
+ pkg.scripts['devlink:bridge'] = 'node node_modules/@rahul_ur/devlink-bridge/server.mjs';
42
+ changed = true;
43
+ }
44
+ if (changed) { writeJson(pkgPath, pkg); ok('added devlink:bridge script to package.json'); }
45
+ }
46
+
47
+ // ─── Setup Vite ───────────────────────────────────────────────────────────────
48
+
49
+ function setupVite() {
50
+ const candidates = ['vite.config.ts', 'vite.config.js', 'vite.config.mts', 'vite.config.mjs'];
51
+ const configFile = candidates.find(f => exists(path.join(projectRoot, f)));
52
+
53
+ if (!configFile) {
54
+ // Create vite.config.ts from scratch
55
+ const content = `import { defineConfig } from 'vite';
56
+ import react from '@vitejs/plugin-react';
57
+ import { devlinkBabelConfig } from '@rahul_ur/devlink-vite-plugin';
58
+
59
+ export default defineConfig({
60
+ plugins: [
61
+ react({
62
+ babel: {
63
+ plugins: [
64
+ devlinkBabelConfig({ root: process.cwd() }),
65
+ ].filter(Boolean),
66
+ },
67
+ }),
68
+ ],
69
+ });
70
+ `;
71
+ fs.writeFileSync(path.join(projectRoot, 'vite.config.ts'), content);
72
+ ok('created vite.config.ts with devlink config');
73
+ return;
74
+ }
75
+
76
+ const configPath = path.join(projectRoot, configFile);
77
+ let src = fs.readFileSync(configPath, 'utf8');
78
+
79
+ if (src.includes('@rahul_ur/devlink-vite-plugin') || src.includes('@rahul_ur/devlink-babel-plugin')) {
80
+ ok(`${configFile} already has devlink config`);
81
+ return;
82
+ }
83
+
84
+ // Add import line after last import
85
+ const importLine = `import { devlinkBabelConfig } from '@rahul_ur/devlink-vite-plugin';\n`;
86
+ const lastImport = src.lastIndexOf('\nimport ');
87
+ const insertAt = lastImport === -1 ? 0 : src.indexOf('\n', lastImport) + 1;
88
+ src = src.slice(0, insertAt) + importLine + src.slice(insertAt);
89
+
90
+ // Patch react() call
91
+ if (src.includes('react()')) {
92
+ src = src.replace(
93
+ 'react()',
94
+ `react({\n babel: {\n plugins: [devlinkBabelConfig({ root: process.cwd() })].filter(Boolean),\n },\n })`
95
+ );
96
+ ok(`patched ${configFile} — react() updated`);
97
+ } else if (src.match(/react\s*\(\s*\{/)) {
98
+ warn(`Could not auto-patch ${configFile}. Add manually:\n\n react({ babel: { plugins: [devlinkBabelConfig({ root: process.cwd() })] } })\n`);
99
+ }
100
+
101
+ fs.writeFileSync(configPath, src);
102
+ ok(`updated ${configFile}`);
103
+ }
104
+
105
+ // ─── Setup Next.js ────────────────────────────────────────────────────────────
106
+
107
+ function setupNext() {
108
+ const babelRcPath = path.join(projectRoot, '.babelrc');
109
+ const plugin = ['@rahul_ur/devlink-babel-plugin', { root: '.', envs: ['development'] }];
110
+
111
+ if (exists(babelRcPath)) {
112
+ const babelRc = readJson(babelRcPath);
113
+ babelRc.env = babelRc.env || {};
114
+ babelRc.env.development = babelRc.env.development || {};
115
+ babelRc.env.development.plugins = babelRc.env.development.plugins || [];
116
+ const already = babelRc.env.development.plugins.some(p =>
117
+ (Array.isArray(p) ? p[0] : p) === '@rahul_ur/devlink-babel-plugin'
118
+ );
119
+ if (!already) {
120
+ babelRc.env.development.plugins.push(plugin);
121
+ writeJson(babelRcPath, babelRc);
122
+ ok('patched .babelrc for Next.js');
123
+ } else {
124
+ ok('.babelrc already has devlink config');
125
+ }
126
+ } else {
127
+ writeJson(babelRcPath, {
128
+ presets: ['next/babel'],
129
+ env: { development: { plugins: [plugin] } }
130
+ });
131
+ ok('created .babelrc for Next.js');
132
+ warn('Adding .babelrc disables Next.js SWC compiler in development.');
133
+ }
134
+ }
135
+
136
+ // ─── Setup CRA ────────────────────────────────────────────────────────────────
137
+
138
+ function setupCra() {
139
+ const configPath = path.join(projectRoot, 'node_modules', 'react-scripts', 'config', 'webpack.config.js');
140
+ if (!exists(configPath)) {
141
+ warn('CRA webpack config not found. Run npm install first then run devlink setup again.');
142
+ return;
143
+ }
144
+ let src = fs.readFileSync(configPath, 'utf8');
145
+ if (src.includes('@rahul_ur/devlink-babel-plugin')) { ok('CRA already patched'); return; }
146
+
147
+ const marker = "const createEnvironmentHash = require('./webpack/persistentCache/createEnvironmentHash');";
148
+ if (src.includes(marker)) {
149
+ src = src.replace(marker, `${marker}\nconst devlinkPlugin = require.resolve('@rahul_ur/devlink-babel-plugin');`);
150
+ }
151
+ const refreshLine = "isEnvDevelopment &&\n shouldUseReactRefresh &&\n require.resolve('react-refresh/babel'),";
152
+ if (src.includes(refreshLine)) {
153
+ src = src.replace(refreshLine,
154
+ `isEnvDevelopment && [devlinkPlugin, { root: paths.appPath, envs: ['development'] }],\n ${refreshLine}`
155
+ );
156
+ }
157
+ fs.writeFileSync(configPath, src);
158
+ ok('patched CRA webpack config');
159
+ }
160
+
161
+ // ─── Generate devlink.config.ts ───────────────────────────────────────────────
162
+
163
+ function generateConfig() {
164
+ const configPath = path.join(projectRoot, 'devlink.config.ts');
165
+ if (exists(configPath)) { ok('devlink.config.ts already exists'); return; }
166
+
167
+ const root = projectRoot.replace(/\\/g, '/');
168
+ const srcDir = exists(path.join(projectRoot, 'src')) ? `${root}/src` : root;
169
+
170
+ fs.writeFileSync(configPath, `// devlink.config.ts — edit projectRoot and rootPath for your project
171
+ export const devlinkConfig = {
172
+ projectRoot: '${root}',
173
+ rootPath: '${srcDir}',
174
+ defaultDocked: true,
175
+ dockedWidth: 460,
176
+ };
177
+ `);
178
+ ok('created devlink.config.ts');
179
+ }
180
+
181
+ // ─── Print main.tsx instructions ──────────────────────────────────────────────
182
+
183
+ function printInstructions(framework) {
184
+ const isNext = framework === 'next';
185
+ console.log('\n\x1b[36m─────────────────────────────────────────────────\x1b[0m');
186
+ console.log('\x1b[36m devlink setup complete — final step:\x1b[0m');
187
+ console.log('\x1b[36m─────────────────────────────────────────────────\x1b[0m\n');
188
+
189
+ if (isNext) {
190
+ console.log('Add to pages/_app.tsx:\n');
191
+ console.log(` import { DevlinkBridge } from '@rahul_ur/devlink-bridge'
192
+ import { DevlinkStudio } from '@rahul_ur/devlink-studio'
193
+ import { devlinkConfig } from '../devlink.config'
194
+
195
+ const bridge = new DevlinkBridge()
196
+
197
+ export default function App({ Component, pageProps }) {
198
+ return (
199
+ <DevlinkStudio bridge={bridge} {...devlinkConfig}>
200
+ <Component {...pageProps} />
201
+ </DevlinkStudio>
202
+ )
203
+ }`);
204
+ } else {
205
+ console.log('Add to src/main.tsx:\n');
206
+ console.log(` import { DevlinkBridge } from '@rahul_ur/devlink-bridge'
207
+ import { DevlinkStudio } from '@rahul_ur/devlink-studio'
208
+ import { devlinkConfig } from '../devlink.config'
209
+
210
+ const bridge = new DevlinkBridge()
211
+
212
+ function Root() {
213
+ return (
214
+ <DevlinkStudio bridge={bridge} {...devlinkConfig}>
215
+ <App />
216
+ </DevlinkStudio>
217
+ )
218
+ }
219
+
220
+ ReactDOM.createRoot(document.getElementById('root')!).render(
221
+ <React.StrictMode><Root /></React.StrictMode>
222
+ )`);
223
+ }
224
+
225
+ console.log('\nThen start the bridge server:\n');
226
+ console.log(' npm run devlink:bridge\n');
227
+ console.log('Press \x1b[36mAlt+D\x1b[0m in the browser to activate inspect mode.\n');
228
+ console.log('\x1b[36m─────────────────────────────────────────────────\x1b[0m\n');
229
+ }
230
+
231
+ // ─── Commands ─────────────────────────────────────────────────────────────────
232
+
233
+ function runSetup() {
234
+ log(`setting up devlink in ${projectRoot}`);
235
+ const framework = detectFramework();
236
+ log(`detected framework: ${framework}`);
237
+
238
+ addScripts();
239
+
240
+ if (framework === 'vite') setupVite();
241
+ else if (framework === 'next') setupNext();
242
+ else if (framework === 'cra') setupCra();
243
+ else {
244
+ warn('Could not detect framework. Supported: Vite, Next.js, CRA.');
245
+ warn('Add @rahul_ur/devlink-babel-plugin manually to your build config.');
246
+ }
247
+
248
+ generateConfig();
249
+ printInstructions(framework);
250
+ }
251
+
252
+ function runHelp() {
253
+ console.log(`
254
+ \x1b[36mdevlink\x1b[0m — click any element in your browser, jump to source, edit, UI updates live.
255
+
256
+ \x1b[1mCommands:\x1b[0m
257
+ devlink setup Auto-configure your project (Vite / Next.js / CRA)
258
+ devlink help Show this help message
259
+
260
+ \x1b[1mQuick start:\x1b[0m
261
+ npm install @rahul_ur/devlink
262
+ npx devlink setup
263
+ npm run devlink:bridge
264
+
265
+ \x1b[1mKeyboard shortcuts:\x1b[0m
266
+ Alt+D Toggle inspect mode
267
+ Esc Exit inspect mode
268
+ + drag Area selection
269
+ Ctrl+S Save + format
270
+
271
+ \x1b[1mPackages installed:\x1b[0m
272
+ @rahul_ur/devlink-bridge WebSocket tunnel
273
+ @rahul_ur/devlink-studio Floating editor + inspector
274
+ @rahul_ur/devlink-babel-plugin Stamps data-source at build time
275
+ @rahul_ur/devlink-vite-plugin Wires plugin into Vite automatically
276
+
277
+ \x1b[1mDocs:\x1b[0m https://github.com/rahul_ur/devlink
278
+ `);
279
+ }
280
+
281
+ // ─── Run ──────────────────────────────────────────────────────────────────────
282
+
283
+ if (command === 'setup' || command === '') runSetup();
284
+ else if (command === 'help' || command === '--help' || command === '-h') runHelp();
285
+ else { err(`Unknown command: ${command}`); runHelp(); }
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ // @rahul_ur/devlink — type declarations
2
+
3
+ export { DevlinkBridge } from '@rahul_ur/devlink-bridge';
4
+ export { DevlinkStudio, FloatingWindow, useDeleteElement, useScreenCapture } from '@rahul_ur/devlink-studio';
5
+ export { devlinkVitePlugin, devlinkBabelConfig } from '@rahul_ur/devlink-vite-plugin';
package/index.js ADDED
@@ -0,0 +1,6 @@
1
+ // @rahul_ur/devlink
2
+ // Convenience re-exports — import from here or directly from each package
3
+
4
+ export { DevlinkBridge } from '@rahul_ur/devlink-bridge';
5
+ export { DevlinkStudio, FloatingWindow, useDeleteElement, useScreenCapture } from '@rahul_ur/devlink-studio';
6
+ export { devlinkVitePlugin, devlinkBabelConfig } from '@rahul_ur/devlink-vite-plugin';
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@rahul_ur/devlink",
3
+ "version": "1.0.0",
4
+ "description": "Click any element in your browser → jump to source code → edit → UI updates live. One command setup for Vite, Next.js and CRA.",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "devlink": "./bin/cli.mjs",
8
+ "devlink-setup": "./bin/cli.mjs"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "templates",
13
+ "index.js",
14
+ "index.d.ts",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "prepublishOnly": "npm pack --dry-run"
19
+ },
20
+ "keywords": [
21
+ "devlink",
22
+ "devtools",
23
+ "react",
24
+ "vite",
25
+ "nextjs",
26
+ "inspector",
27
+ "source-map",
28
+ "live-reload",
29
+ "monaco",
30
+ "editor",
31
+ "babel-plugin"
32
+ ],
33
+ "author": "Rahul <your@email.com>",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/rahul_ur/devlink"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/rahul_ur/devlink/issues"
41
+ },
42
+ "homepage": "https://github.com/rahul_ur/devlink#readme",
43
+ "dependencies": {
44
+ "@rahul_ur/devlink-bridge": "^1.0.0",
45
+ "@rahul_ur/devlink-studio": "^1.0.0",
46
+ "@rahul_ur/devlink-babel-plugin": "^1.0.0",
47
+ "@rahul_ur/devlink-vite-plugin": "^1.0.0"
48
+ },
49
+ "engines": {
50
+ "node": ">=18.0.0"
51
+ }
52
+ }