draply-dev 1.1.1 → 1.2.1
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/bin/cli.js +11 -143
- package/package.json +1 -1
- package/src/draply-features.js +224 -766
- package/src/overlay.js +4 -3
package/bin/cli.js
CHANGED
|
@@ -35,12 +35,11 @@ function decode(headers, chunks) {
|
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
//
|
|
38
|
+
// Store CSS in hidden .draply/ folder to keep user's project clean
|
|
39
39
|
const projectRoot = process.cwd();
|
|
40
40
|
const draplyDir = path.join(projectRoot, '.draply');
|
|
41
41
|
if (!fs.existsSync(draplyDir)) {
|
|
42
42
|
fs.mkdirSync(draplyDir, { recursive: true });
|
|
43
|
-
// Добавляем .draply в .gitignore если он есть
|
|
44
43
|
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
45
44
|
if (fs.existsSync(gitignorePath)) {
|
|
46
45
|
const gi = fs.readFileSync(gitignorePath, 'utf8');
|
|
@@ -54,7 +53,6 @@ if (!fs.existsSync(overridesPath)) {
|
|
|
54
53
|
fs.writeFileSync(overridesPath, '/* draply */\n', 'utf8');
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
|
|
58
56
|
const server = http.createServer((req, res) => {
|
|
59
57
|
|
|
60
58
|
// CORS preflight
|
|
@@ -63,7 +61,7 @@ const server = http.createServer((req, res) => {
|
|
|
63
61
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
64
62
|
if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; }
|
|
65
63
|
|
|
66
|
-
// ── Draply: Save
|
|
64
|
+
// ── Draply: Save changes to CSS ─────────────────────────────────────────────
|
|
67
65
|
if (req.url === '/draply-save' && req.method === 'POST') {
|
|
68
66
|
let body = '';
|
|
69
67
|
req.on('data', c => body += c);
|
|
@@ -77,13 +75,11 @@ const server = http.createServer((req, res) => {
|
|
|
77
75
|
.map(([k, v]) => ` ${k}: ${v};`)
|
|
78
76
|
.join('\n');
|
|
79
77
|
const label = ch.selector.split('>').pop().trim();
|
|
80
|
-
lines.push(`/* ${label}
|
|
81
|
-
${ch.selector} {
|
|
82
|
-
${props}
|
|
83
|
-
}`);
|
|
78
|
+
lines.push(`/* ${label} */\n${ch.selector} {\n${props}\n}`);
|
|
84
79
|
}
|
|
85
80
|
const css = '/* draply — ' + new Date().toLocaleString('ru-RU') + ' */\n\n' + lines.join('\n\n') + '\n';
|
|
86
81
|
fs.writeFileSync(overridesPath, css, 'utf8');
|
|
82
|
+
console.log(` \x1b[32m✓\x1b[0m Saved ${changes.length} changes to .draply/overrides.css`);
|
|
87
83
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
88
84
|
res.end(JSON.stringify({ ok: true }));
|
|
89
85
|
} catch (e) {
|
|
@@ -118,32 +114,6 @@ ${props}
|
|
|
118
114
|
return;
|
|
119
115
|
}
|
|
120
116
|
|
|
121
|
-
// ── Draply: Project Info endpoint ──────────────────────────────────────────
|
|
122
|
-
if (req.url === '/draply-project-info' && req.method === 'GET') {
|
|
123
|
-
const info = detectProject(projectRoot);
|
|
124
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
125
|
-
res.end(JSON.stringify(info));
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// ── Draply: Find Source endpoint ──────────────────────────────────────────
|
|
130
|
-
if (req.url === '/draply-find-source' && req.method === 'POST') {
|
|
131
|
-
let body = '';
|
|
132
|
-
req.on('data', c => body += c);
|
|
133
|
-
req.on('end', () => {
|
|
134
|
-
try {
|
|
135
|
-
const { selector, tagName, className } = JSON.parse(body);
|
|
136
|
-
const result = findSourceFile(projectRoot, { selector, tagName, className });
|
|
137
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
138
|
-
res.end(JSON.stringify(result));
|
|
139
|
-
} catch (e) {
|
|
140
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
141
|
-
res.end(JSON.stringify({ found: false, error: e.message }));
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
117
|
// ── Proxy to dev server ────────────────────────────────────────────────────
|
|
148
118
|
const opts = {
|
|
149
119
|
hostname: targetHost,
|
|
@@ -181,8 +151,8 @@ ${props}
|
|
|
181
151
|
pReq.on('error', () => {
|
|
182
152
|
res.writeHead(502, { 'Content-Type': 'text/html' });
|
|
183
153
|
res.end(`<!DOCTYPE html><html><body style="background:#0a0a0f;color:#e8e8f0;font-family:monospace;padding:60px;text-align:center">
|
|
184
|
-
<h2 style="color:#ff6b6b">⚠
|
|
185
|
-
<p style="color:#555;margin-top:16px"
|
|
154
|
+
<h2 style="color:#ff6b6b">⚠ Can't reach ${targetHost}:${targetPort}</h2>
|
|
155
|
+
<p style="color:#555;margin-top:16px">Make sure your dev server is running, then refresh</p>
|
|
186
156
|
<script>setTimeout(()=>location.reload(), 2000)</script>
|
|
187
157
|
</body></html>`);
|
|
188
158
|
});
|
|
@@ -191,112 +161,10 @@ ${props}
|
|
|
191
161
|
});
|
|
192
162
|
|
|
193
163
|
server.listen(proxyPort, () => {
|
|
194
|
-
console.log('\n \x1b[32m●\x1b[0m Draply
|
|
195
|
-
console.log(`
|
|
196
|
-
console.log(`
|
|
197
|
-
console.log(` \x1b[90mCtrl+C
|
|
164
|
+
console.log('\n \x1b[32m●\x1b[0m Draply running\n');
|
|
165
|
+
console.log(` Your project → \x1b[36mhttp://${targetHost}:${targetPort}\x1b[0m`);
|
|
166
|
+
console.log(` Open this → \x1b[33mhttp://localhost:${proxyPort}\x1b[0m \x1b[32m← go here!\x1b[0m\n`);
|
|
167
|
+
console.log(` \x1b[90mCtrl+C to stop\x1b[0m\n`);
|
|
198
168
|
});
|
|
199
169
|
|
|
200
|
-
process.on('SIGINT', () => { console.log('\n \x1b[90mDraply
|
|
201
|
-
|
|
202
|
-
// ══════════════════════════════════════════════════════════════════════════
|
|
203
|
-
// PROJECT DETECTION
|
|
204
|
-
// ══════════════════════════════════════════════════════════════════════════
|
|
205
|
-
function detectProject(root) {
|
|
206
|
-
const result = { framework: 'unknown', cssStrategy: 'unknown', root };
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
const pkgPath = path.join(root, 'package.json');
|
|
210
|
-
if (!fs.existsSync(pkgPath)) return result;
|
|
211
|
-
|
|
212
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
213
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
214
|
-
|
|
215
|
-
// Detect framework
|
|
216
|
-
if (allDeps['next']) result.framework = 'next';
|
|
217
|
-
else if (allDeps['react']) result.framework = 'react';
|
|
218
|
-
else if (allDeps['nuxt']) result.framework = 'nuxt';
|
|
219
|
-
else if (allDeps['vue']) result.framework = 'vue';
|
|
220
|
-
else if (allDeps['@angular/core']) result.framework = 'angular';
|
|
221
|
-
else if (allDeps['svelte']) result.framework = 'svelte';
|
|
222
|
-
else if (allDeps['vite']) result.framework = 'vite';
|
|
223
|
-
|
|
224
|
-
// Detect CSS strategy
|
|
225
|
-
if (allDeps['tailwindcss']) result.cssStrategy = 'tailwind';
|
|
226
|
-
else if (allDeps['styled-components']) result.cssStrategy = 'styled-components';
|
|
227
|
-
else if (allDeps['@emotion/react'] || allDeps['@emotion/styled']) result.cssStrategy = 'emotion';
|
|
228
|
-
else if (allDeps['sass'] || allDeps['node-sass']) result.cssStrategy = 'sass';
|
|
229
|
-
else {
|
|
230
|
-
// Check for CSS modules (usually enabled by default in React/Next)
|
|
231
|
-
if (['react', 'next'].includes(result.framework)) {
|
|
232
|
-
result.cssStrategy = 'css-modules';
|
|
233
|
-
} else {
|
|
234
|
-
result.cssStrategy = 'external';
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
} catch { /* ignore */ }
|
|
238
|
-
|
|
239
|
-
return result;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// ══════════════════════════════════════════════════════════════════════════
|
|
243
|
-
// SOURCE FILE FINDER
|
|
244
|
-
// ══════════════════════════════════════════════════════════════════════════
|
|
245
|
-
function findSourceFile(root, { selector, tagName, className }) {
|
|
246
|
-
const result = { found: false, file: null, line: null, hint: '' };
|
|
247
|
-
|
|
248
|
-
try {
|
|
249
|
-
const srcDirs = ['src', 'app', 'pages', 'components', 'lib'];
|
|
250
|
-
const extensions = ['.tsx', '.jsx', '.vue', '.svelte', '.js', '.ts'];
|
|
251
|
-
const searchTerm = className || tagName || '';
|
|
252
|
-
|
|
253
|
-
if (!searchTerm) {
|
|
254
|
-
result.hint = 'Select an element with a class name for better results.';
|
|
255
|
-
return result;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
for (const dir of srcDirs) {
|
|
259
|
-
const dirPath = path.join(root, dir);
|
|
260
|
-
if (!fs.existsSync(dirPath)) continue;
|
|
261
|
-
|
|
262
|
-
const files = walkDir(dirPath, extensions);
|
|
263
|
-
for (const file of files) {
|
|
264
|
-
try {
|
|
265
|
-
const content = fs.readFileSync(file, 'utf8');
|
|
266
|
-
const lines = content.split('\n');
|
|
267
|
-
for (let i = 0; i < lines.length; i++) {
|
|
268
|
-
if (lines[i].includes(searchTerm)) {
|
|
269
|
-
result.found = true;
|
|
270
|
-
result.file = path.relative(root, file);
|
|
271
|
-
result.line = i + 1;
|
|
272
|
-
result.hint = `Found "${searchTerm}" at ${result.file}:${result.line}`;
|
|
273
|
-
return result;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
} catch { /* skip unreadable files */ }
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
result.hint = `Could not find "${searchTerm}" in source files.`;
|
|
281
|
-
} catch (e) {
|
|
282
|
-
result.hint = 'Error searching source files: ' + e.message;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return result;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function walkDir(dir, extensions, results = []) {
|
|
289
|
-
try {
|
|
290
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
291
|
-
for (const entry of entries) {
|
|
292
|
-
const fullPath = path.join(dir, entry.name);
|
|
293
|
-
if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === '.next' || entry.name === 'dist') continue;
|
|
294
|
-
if (entry.isDirectory()) {
|
|
295
|
-
walkDir(fullPath, extensions, results);
|
|
296
|
-
} else if (extensions.some(ext => entry.name.endsWith(ext))) {
|
|
297
|
-
results.push(fullPath);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
} catch { /* ignore */ }
|
|
301
|
-
return results;
|
|
302
|
-
}
|
|
170
|
+
process.on('SIGINT', () => { console.log('\n \x1b[90mDraply stopped\x1b[0m\n'); process.exit(0); });
|