draply-dev 1.2.0 → 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 CHANGED
@@ -35,12 +35,11 @@ function decode(headers, chunks) {
35
35
  });
36
36
  }
37
37
 
38
- // Храним CSS в скрытой папке .draply/ чтобы не засорять проект юзера
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,28 +61,13 @@ 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 endpoint — AUTO-APPLY to source files ─────────────────────
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);
70
68
  req.on('end', () => {
71
69
  try {
72
70
  const { changes } = JSON.parse(body);
73
- const projectInfo = detectProject(projectRoot);
74
- const results = [];
75
-
76
- for (const ch of (changes || [])) {
77
- if (!ch.selector || !ch.props) continue;
78
- const result = autoApplyChange(projectRoot, ch.selector, ch.props, projectInfo);
79
- results.push(result);
80
- if (result.applied) {
81
- console.log(` \x1b[32m✓\x1b[0m Applied: ${result.file} (${result.strategy})`);
82
- } else {
83
- console.log(` \x1b[33m⚠\x1b[0m Fallback CSS: ${ch.selector.split('>').pop().trim()}`);
84
- }
85
- }
86
-
87
- // Also save CSS override as backup
88
71
  const lines = [];
89
72
  for (const ch of (changes || [])) {
90
73
  if (!ch.selector) continue;
@@ -96,11 +79,9 @@ const server = http.createServer((req, res) => {
96
79
  }
97
80
  const css = '/* draply — ' + new Date().toLocaleString('ru-RU') + ' */\n\n' + lines.join('\n\n') + '\n';
98
81
  fs.writeFileSync(overridesPath, css, 'utf8');
99
-
100
- const applied = results.filter(r => r.applied).length;
101
- const total = results.length;
82
+ console.log(` \x1b[32m✓\x1b[0m Saved ${changes.length} changes to .draply/overrides.css`);
102
83
  res.writeHead(200, { 'Content-Type': 'application/json' });
103
- res.end(JSON.stringify({ ok: true, applied, total, results }));
84
+ res.end(JSON.stringify({ ok: true }));
104
85
  } catch (e) {
105
86
  res.writeHead(500, { 'Content-Type': 'application/json' });
106
87
  res.end(JSON.stringify({ ok: false, error: e.message }));
@@ -133,32 +114,6 @@ const server = http.createServer((req, res) => {
133
114
  return;
134
115
  }
135
116
 
136
- // ── Draply: Project Info endpoint ──────────────────────────────────────────
137
- if (req.url === '/draply-project-info' && req.method === 'GET') {
138
- const info = detectProject(projectRoot);
139
- res.writeHead(200, { 'Content-Type': 'application/json' });
140
- res.end(JSON.stringify(info));
141
- return;
142
- }
143
-
144
- // ── Draply: Find Source endpoint ──────────────────────────────────────────
145
- if (req.url === '/draply-find-source' && req.method === 'POST') {
146
- let body = '';
147
- req.on('data', c => body += c);
148
- req.on('end', () => {
149
- try {
150
- const { selector, tagName, className } = JSON.parse(body);
151
- const result = findSourceFile(projectRoot, { selector, tagName, className });
152
- res.writeHead(200, { 'Content-Type': 'application/json' });
153
- res.end(JSON.stringify(result));
154
- } catch (e) {
155
- res.writeHead(500, { 'Content-Type': 'application/json' });
156
- res.end(JSON.stringify({ found: false, error: e.message }));
157
- }
158
- });
159
- return;
160
- }
161
-
162
117
  // ── Proxy to dev server ────────────────────────────────────────────────────
163
118
  const opts = {
164
119
  hostname: targetHost,
@@ -196,8 +151,8 @@ const server = http.createServer((req, res) => {
196
151
  pReq.on('error', () => {
197
152
  res.writeHead(502, { 'Content-Type': 'text/html' });
198
153
  res.end(`<!DOCTYPE html><html><body style="background:#0a0a0f;color:#e8e8f0;font-family:monospace;padding:60px;text-align:center">
199
- <h2 style="color:#ff6b6b">⚠ Не могу достучаться до ${targetHost}:${targetPort}</h2>
200
- <p style="color:#555;margin-top:16px">Убедись что dev сервер запущен, потом обнови страницу</p>
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>
201
156
  <script>setTimeout(()=>location.reload(), 2000)</script>
202
157
  </body></html>`);
203
158
  });
@@ -206,435 +161,10 @@ const server = http.createServer((req, res) => {
206
161
  });
207
162
 
208
163
  server.listen(proxyPort, () => {
209
- console.log('\n \x1b[32m●\x1b[0m Draply запущен\n');
210
- console.log(` Твой проект → \x1b[36mhttp://${targetHost}:${targetPort}\x1b[0m`);
211
- console.log(` Открой это → \x1b[33mhttp://localhost:${proxyPort}\x1b[0m \x1b[32m← вот сюда заходи!\x1b[0m\n`);
212
- console.log(` \x1b[90mCtrl+C чтобы остановить\x1b[0m\n`);
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`);
213
168
  });
214
169
 
215
- process.on('SIGINT', () => { console.log('\n \x1b[90mDraply остановлен\x1b[0m\n'); process.exit(0); });
216
-
217
- // ══════════════════════════════════════════════════════════════════════════
218
- // PROJECT DETECTION
219
- // ══════════════════════════════════════════════════════════════════════════
220
- function detectProject(root) {
221
- const result = { framework: 'unknown', cssStrategy: 'unknown', root };
222
-
223
- try {
224
- const pkgPath = path.join(root, 'package.json');
225
- if (!fs.existsSync(pkgPath)) return result;
226
-
227
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
228
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
229
-
230
- // Detect framework
231
- if (allDeps['next']) result.framework = 'next';
232
- else if (allDeps['react']) result.framework = 'react';
233
- else if (allDeps['nuxt']) result.framework = 'nuxt';
234
- else if (allDeps['vue']) result.framework = 'vue';
235
- else if (allDeps['@angular/core']) result.framework = 'angular';
236
- else if (allDeps['svelte']) result.framework = 'svelte';
237
- else if (allDeps['vite']) result.framework = 'vite';
238
-
239
- // Detect CSS strategy
240
- if (allDeps['tailwindcss']) result.cssStrategy = 'tailwind';
241
- else if (allDeps['styled-components']) result.cssStrategy = 'styled-components';
242
- else if (allDeps['@emotion/react'] || allDeps['@emotion/styled']) result.cssStrategy = 'emotion';
243
- else if (allDeps['sass'] || allDeps['node-sass']) result.cssStrategy = 'sass';
244
- else {
245
- // Check for CSS modules (usually enabled by default in React/Next)
246
- if (['react', 'next'].includes(result.framework)) {
247
- result.cssStrategy = 'css-modules';
248
- } else {
249
- result.cssStrategy = 'external';
250
- }
251
- }
252
- } catch { /* ignore */ }
253
-
254
- return result;
255
- }
256
-
257
- // ══════════════════════════════════════════════════════════════════════════
258
- // SOURCE FILE FINDER
259
- // ══════════════════════════════════════════════════════════════════════════
260
- function findSourceFile(root, { selector, tagName, className }) {
261
- const result = { found: false, file: null, line: null, hint: '' };
262
-
263
- try {
264
- const srcDirs = ['src', 'app', 'pages', 'components', 'lib'];
265
- const extensions = ['.tsx', '.jsx', '.vue', '.svelte', '.js', '.ts'];
266
- const searchTerm = className || tagName || '';
267
-
268
- if (!searchTerm) {
269
- result.hint = 'Select an element with a class name for better results.';
270
- return result;
271
- }
272
-
273
- for (const dir of srcDirs) {
274
- const dirPath = path.join(root, dir);
275
- if (!fs.existsSync(dirPath)) continue;
276
-
277
- const files = walkDir(dirPath, extensions);
278
- for (const file of files) {
279
- try {
280
- const content = fs.readFileSync(file, 'utf8');
281
- const lines = content.split('\n');
282
- for (let i = 0; i < lines.length; i++) {
283
- if (lines[i].includes(searchTerm)) {
284
- result.found = true;
285
- result.file = path.relative(root, file);
286
- result.line = i + 1;
287
- result.hint = `Found "${searchTerm}" at ${result.file}:${result.line}`;
288
- return result;
289
- }
290
- }
291
- } catch { /* skip unreadable files */ }
292
- }
293
- }
294
-
295
- result.hint = `Could not find "${searchTerm}" in source files.`;
296
- } catch (e) {
297
- result.hint = 'Error searching source files: ' + e.message;
298
- }
299
-
300
- return result;
301
- }
302
-
303
- function walkDir(dir, extensions, results = []) {
304
- try {
305
- const entries = fs.readdirSync(dir, { withFileTypes: true });
306
- for (const entry of entries) {
307
- const fullPath = path.join(dir, entry.name);
308
- if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === '.next' || entry.name === 'dist') continue;
309
- if (entry.isDirectory()) {
310
- walkDir(fullPath, extensions, results);
311
- } else if (extensions.some(ext => entry.name.endsWith(ext))) {
312
- results.push(fullPath);
313
- }
314
- }
315
- } catch { /* ignore */ }
316
- return results;
317
- }
318
-
319
- // ══════════════════════════════════════════════════════════════════════════
320
- // AUTO-APPLY CHANGES TO SOURCE FILES
321
- // ══════════════════════════════════════════════════════════════════════════
322
-
323
- function autoApplyChange(root, selector, props, projectInfo) {
324
- const result = { selector, applied: false, file: null, strategy: null, reason: '' };
325
-
326
- // 1. Extract class name from selector
327
- const className = extractClassName(selector);
328
- if (!className) {
329
- result.reason = 'No class name in selector';
330
- return result;
331
- }
332
-
333
- // 2. Try: find existing CSS rule and modify it
334
- const cssResult = findAndModifyCSSRule(root, className, props);
335
- if (cssResult.applied) {
336
- result.applied = true;
337
- result.file = path.relative(root, cssResult.file);
338
- result.strategy = 'css-modify';
339
- return result;
340
- }
341
-
342
- // 3. Try: find JSX component using this class → find its CSS import → add rule there
343
- const importResult = findCSSImportAndAppend(root, className, props);
344
- if (importResult.applied) {
345
- result.applied = true;
346
- result.file = path.relative(root, importResult.file);
347
- result.strategy = 'css-append';
348
- return result;
349
- }
350
-
351
- // 4. Try: modify inline style in JSX
352
- const inlineResult = modifyInlineStyle(root, className, props);
353
- if (inlineResult.applied) {
354
- result.applied = true;
355
- result.file = path.relative(root, inlineResult.file);
356
- result.strategy = 'inline-style';
357
- return result;
358
- }
359
-
360
- // 5. Fallback: find any CSS file in project and append
361
- const fallbackResult = appendToAnyCSSFile(root, className, props);
362
- if (fallbackResult.applied) {
363
- result.applied = true;
364
- result.file = path.relative(root, fallbackResult.file);
365
- result.strategy = 'css-fallback';
366
- return result;
367
- }
368
-
369
- result.reason = 'Could not find target file for ' + className;
370
- return result;
371
- }
372
-
373
- // Extract the most specific class name from a CSS selector
374
- function extractClassName(selector) {
375
- // Selector like: #root > div > section.nexus-hero > h1.title
376
- const parts = selector.split('>').map(s => s.trim());
377
- // Try from the end — most specific first
378
- for (let i = parts.length - 1; i >= 0; i--) {
379
- const classMatch = parts[i].match(/\.([a-zA-Z][a-zA-Z0-9_-]*)/);
380
- if (classMatch) return classMatch[1];
381
- }
382
- return null;
383
- }
384
-
385
- // Strategy 1: Find existing CSS rule in .css/.scss files and modify it
386
- function findAndModifyCSSRule(root, className, props) {
387
- const cssFiles = walkDir(root, ['.css', '.scss', '.sass', '.less']);
388
- const ruleRegex = new RegExp(
389
- `(\\.${escapeRegex(className)}\\s*\\{)([^}]*)(\\})`, 's'
390
- );
391
-
392
- for (const file of cssFiles) {
393
- // Skip node_modules, .draply, etc
394
- const rel = path.relative(root, file);
395
- if (rel.includes('node_modules') || rel.includes('.draply')) continue;
396
-
397
- try {
398
- let content = fs.readFileSync(file, 'utf8');
399
- const match = content.match(ruleRegex);
400
- if (!match) continue;
401
-
402
- // Found the rule — modify it
403
- const existingBlock = match[2];
404
- let newBlock = existingBlock;
405
-
406
- for (const [prop, val] of Object.entries(props)) {
407
- const propRegex = new RegExp(`(${escapeRegex(prop)}\\s*:\\s*)([^;]+)(;)`, 'g');
408
- if (propRegex.test(newBlock)) {
409
- // Property exists — update value
410
- newBlock = newBlock.replace(
411
- new RegExp(`(${escapeRegex(prop)}\\s*:\\s*)([^;]+)(;)`, 'g'),
412
- `$1${val}$3`
413
- );
414
- } else {
415
- // Property doesn't exist — add it
416
- newBlock = newBlock.trimEnd() + `\n ${prop}: ${val};\n`;
417
- }
418
- }
419
-
420
- content = content.replace(ruleRegex, `$1${newBlock}$3`);
421
- fs.writeFileSync(file, content, 'utf8');
422
-
423
- return { applied: true, file };
424
- } catch { /* skip */ }
425
- }
426
-
427
- return { applied: false };
428
- }
429
-
430
- // Strategy 2: Find the JSX component using className, find its CSS import, append rule
431
- function findCSSImportAndAppend(root, className, props) {
432
- const jsxFiles = walkDir(root, ['.jsx', '.tsx', '.js', '.ts']);
433
-
434
- for (const jsxFile of jsxFiles) {
435
- const rel = path.relative(root, jsxFile);
436
- if (rel.includes('node_modules')) continue;
437
-
438
- try {
439
- const content = fs.readFileSync(jsxFile, 'utf8');
440
-
441
- // Check if this component uses the className
442
- if (!content.includes(className)) continue;
443
-
444
- // Find CSS imports in this file
445
- // Matches: import './styles.css' or import styles from './styles.module.css' or require('./styles.css')
446
- const importRegex = /(?:import\s+(?:\w+\s+from\s+)?['"]([^'"]+\.(?:css|scss|sass|less))['"]|require\(['"]([^'"]+\.(?:css|scss|sass|less))['"]\))/g;
447
- let importMatch;
448
- const cssImports = [];
449
-
450
- while ((importMatch = importRegex.exec(content)) !== null) {
451
- const importPath = importMatch[1] || importMatch[2];
452
- const fullCSSPath = path.resolve(path.dirname(jsxFile), importPath);
453
- if (fs.existsSync(fullCSSPath)) {
454
- cssImports.push(fullCSSPath);
455
- }
456
- }
457
-
458
- if (cssImports.length === 0) continue;
459
-
460
- // Append rule to the first CSS import
461
- const targetCSS = cssImports[0];
462
- const newRule = `\n/* Draply: .${className} */\n.${className} {\n${formatProps(props)}\n}\n`;
463
-
464
- let cssContent = fs.readFileSync(targetCSS, 'utf8');
465
-
466
- // Check if rule already exists (might have been added before)
467
- if (cssContent.includes(`.${className}`)) {
468
- // Modify existing rule instead
469
- return findAndModifyCSSRuleSingle(targetCSS, className, props);
470
- }
471
-
472
- cssContent += newRule;
473
- fs.writeFileSync(targetCSS, cssContent, 'utf8');
474
-
475
- return { applied: true, file: targetCSS };
476
- } catch { /* skip */ }
477
- }
478
-
479
- return { applied: false };
480
- }
481
-
482
- // Helper: modify rule in a specific file
483
- function findAndModifyCSSRuleSingle(file, className, props) {
484
- const ruleRegex = new RegExp(
485
- `(\\.${escapeRegex(className)}\\s*\\{)([^}]*)(\\})`, 's'
486
- );
487
- try {
488
- let content = fs.readFileSync(file, 'utf8');
489
- const match = content.match(ruleRegex);
490
- if (!match) return { applied: false };
491
-
492
- let newBlock = match[2];
493
- for (const [prop, val] of Object.entries(props)) {
494
- const propRegex = new RegExp(`(${escapeRegex(prop)}\\s*:\\s*)([^;]+)(;)`, 'g');
495
- if (propRegex.test(newBlock)) {
496
- newBlock = newBlock.replace(
497
- new RegExp(`(${escapeRegex(prop)}\\s*:\\s*)([^;]+)(;)`, 'g'),
498
- `$1${val}$3`
499
- );
500
- } else {
501
- newBlock = newBlock.trimEnd() + `\n ${prop}: ${val};\n`;
502
- }
503
- }
504
- content = content.replace(ruleRegex, `$1${newBlock}$3`);
505
- fs.writeFileSync(file, content, 'utf8');
506
- return { applied: true, file };
507
- } catch { return { applied: false }; }
508
- }
509
-
510
- // Strategy 3: Add inline style to JSX element
511
- function modifyInlineStyle(root, className, props) {
512
- const jsxFiles = walkDir(root, ['.jsx', '.tsx']);
513
-
514
- for (const file of jsxFiles) {
515
- const rel = path.relative(root, file);
516
- if (rel.includes('node_modules')) continue;
517
-
518
- try {
519
- let content = fs.readFileSync(file, 'utf8');
520
- if (!content.includes(className)) continue;
521
-
522
- // Find element with this className
523
- // Pattern: className="...nexus-hero..." or className={'...nexus-hero...'}
524
- const elementRegex = new RegExp(
525
- `(<[a-zA-Z][a-zA-Z0-9]*[^>]*className=["'{][^"'}]*${escapeRegex(className)}[^"'}]*["'}])([^>]*>|\\s*/>)`,
526
- 's'
527
- );
528
-
529
- const match = content.match(elementRegex);
530
- if (!match) continue;
531
-
532
- const fullTag = match[0];
533
- const jsxProps = propsToJSXStyle(props);
534
-
535
- // Check if element already has a style prop
536
- if (fullTag.includes('style=')) {
537
- // Modify existing style prop — add/update properties
538
- const styleRegex = /style=\{\{([^}]*)\}\}/;
539
- const styleMatch = fullTag.match(styleRegex);
540
- if (styleMatch) {
541
- let existingStyles = styleMatch[1];
542
- for (const [camelProp, val] of Object.entries(jsxProps)) {
543
- const propRegex = new RegExp(`${camelProp}\\s*:\\s*['"][^'"]*['"]`, 'g');
544
- if (propRegex.test(existingStyles)) {
545
- existingStyles = existingStyles.replace(propRegex, `${camelProp}: '${val}'`);
546
- } else {
547
- existingStyles = existingStyles.trim();
548
- if (existingStyles && !existingStyles.endsWith(',')) existingStyles += ',';
549
- existingStyles += ` ${camelProp}: '${val}'`;
550
- }
551
- }
552
- const newTag = fullTag.replace(styleRegex, `style={{${existingStyles}}}`);
553
- content = content.replace(fullTag, newTag);
554
- }
555
- } else {
556
- // Add new style prop
557
- const styleStr = Object.entries(jsxProps)
558
- .map(([k, v]) => `${k}: '${v}'`)
559
- .join(', ');
560
- const insertion = ` style={{${styleStr}}}`;
561
- // Insert before the closing > or />
562
- const newTag = fullTag.replace(match[2], insertion + match[2]);
563
- content = content.replace(fullTag, newTag);
564
- }
565
-
566
- fs.writeFileSync(file, content, 'utf8');
567
- return { applied: true, file };
568
- } catch { /* skip */ }
569
- }
570
-
571
- return { applied: false };
572
- }
573
-
574
- // Strategy 4: Find any CSS file in the project and append the rule
575
- function appendToAnyCSSFile(root, className, props) {
576
- // Priority: index.css, App.css, globals.css, main.css, styles.css
577
- const candidates = [
578
- 'src/index.css', 'src/App.css', 'src/app/globals.css', 'src/globals.css',
579
- 'src/main.css', 'src/styles.css', 'styles/globals.css', 'app/globals.css',
580
- 'src/styles/globals.css'
581
- ];
582
-
583
- for (const candidate of candidates) {
584
- const fullPath = path.join(root, candidate);
585
- if (fs.existsSync(fullPath)) {
586
- try {
587
- let content = fs.readFileSync(fullPath, 'utf8');
588
- // Check if rule already exists
589
- if (content.includes(`.${className}`)) {
590
- return findAndModifyCSSRuleSingle(fullPath, className, props);
591
- }
592
- const newRule = `\n/* Draply: .${className} */\n.${className} {\n${formatProps(props)}\n}\n`;
593
- content += newRule;
594
- fs.writeFileSync(fullPath, content, 'utf8');
595
- return { applied: true, file: fullPath };
596
- } catch { /* skip */ }
597
- }
598
- }
599
-
600
- // Last resort: find ANY css file
601
- const allCSS = walkDir(root, ['.css']);
602
- for (const file of allCSS) {
603
- const rel = path.relative(root, file);
604
- if (rel.includes('node_modules') || rel.includes('.draply') || rel.includes('.next')) continue;
605
- try {
606
- let content = fs.readFileSync(file, 'utf8');
607
- if (content.includes(`.${className}`)) {
608
- return findAndModifyCSSRuleSingle(file, className, props);
609
- }
610
- const newRule = `\n/* Draply: .${className} */\n.${className} {\n${formatProps(props)}\n}\n`;
611
- content += newRule;
612
- fs.writeFileSync(file, content, 'utf8');
613
- return { applied: true, file };
614
- } catch { /* skip */ }
615
- }
616
-
617
- return { applied: false };
618
- }
619
-
620
- // ── Helpers ──────────────────────────────────────────────────────────────────
621
-
622
- function escapeRegex(str) {
623
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
624
- }
625
-
626
- function formatProps(props) {
627
- return Object.entries(props).map(([k, v]) => ` ${k}: ${v};`).join('\n');
628
- }
629
-
630
- function camelCase(str) {
631
- return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
632
- }
633
-
634
- function propsToJSXStyle(props) {
635
- const result = {};
636
- for (const [prop, val] of Object.entries(props)) {
637
- result[camelCase(prop)] = val;
638
- }
639
- return result;
640
- }
170
+ process.on('SIGINT', () => { console.log('\n \x1b[90mDraply stopped\x1b[0m\n'); process.exit(0); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "draply-dev",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Visual overlay for any frontend project — move, resize, restyle live in the browser, save to CSS",
5
5
  "author": "Arman",
6
6
  "type": "commonjs",