framer-code-link 0.3.0 → 0.4.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.
Files changed (2) hide show
  1. package/dist/index.mjs +42 -231
  2. package/package.json +2 -1
package/dist/index.mjs CHANGED
@@ -5,6 +5,7 @@ import fs from "fs/promises";
5
5
  import { WebSocketServer } from "ws";
6
6
  import chokidar from "chokidar";
7
7
  import path from "path";
8
+ import { getPortFromHash, isSupportedExtension, normalizePath, pluralize, sanitizeFilePath, shortProjectHash } from "@code-link/shared";
8
9
  import { createHash } from "crypto";
9
10
  import { setupTypeAcquisition } from "@typescript/ata";
10
11
  import ts from "typescript";
@@ -118,7 +119,6 @@ let LogLevel = /* @__PURE__ */ function(LogLevel$1) {
118
119
  let currentLevel = LogLevel.INFO;
119
120
  let lastMessage = "";
120
121
  let lastMessageCount = 0;
121
- let lastCategory = "other";
122
122
  const CLEAR_LINE = "\x1B[2K";
123
123
  const MOVE_CURSOR_UP = "\x1B[1A";
124
124
  function rewriteLastLine(text) {
@@ -144,16 +144,6 @@ function flushDedupe() {
144
144
  lastMessageCount = 0;
145
145
  }
146
146
  /**
147
- * Handle category transition - adds newline when switching categories
148
- */
149
- function transitionCategory(newCategory) {
150
- if (lastCategory !== newCategory) {
151
- flushDedupe();
152
- console.log();
153
- lastCategory = newCategory;
154
- }
155
- }
156
- /**
157
147
  * Log with deduplication - repeated messages within window get counted
158
148
  */
159
149
  function logWithDedupe(message, writer) {
@@ -187,7 +177,6 @@ function debug(message, ...args) {
187
177
  */
188
178
  function info(message, ...args) {
189
179
  if (currentLevel <= LogLevel.INFO) {
190
- transitionCategory("other");
191
180
  const formatted = args.length > 0 ? `${message} ${args.join(" ")}` : message;
192
181
  logWithDedupe(formatted, () => console.log(formatted));
193
182
  }
@@ -198,7 +187,6 @@ function info(message, ...args) {
198
187
  function warn(message, ...args) {
199
188
  if (currentLevel <= LogLevel.WARN) {
200
189
  if (message === lastMessage) return;
201
- transitionCategory("other");
202
190
  flushDedupe();
203
191
  lastMessage = message;
204
192
  lastMessageCount = 1;
@@ -210,7 +198,6 @@ function warn(message, ...args) {
210
198
  */
211
199
  function error(message, ...args) {
212
200
  if (currentLevel <= LogLevel.ERROR) {
213
- transitionCategory("other");
214
201
  flushDedupe();
215
202
  console.error(import_picocolors.default.red(`✗ ${message}`), ...args);
216
203
  }
@@ -220,7 +207,6 @@ function error(message, ...args) {
220
207
  */
221
208
  function success(message, ...args) {
222
209
  if (currentLevel <= LogLevel.INFO) {
223
- transitionCategory("other");
224
210
  flushDedupe();
225
211
  console.log(import_picocolors.default.green(`✓ ${message}`), ...args);
226
212
  }
@@ -230,21 +216,18 @@ function success(message, ...args) {
230
216
  */
231
217
  function fileDown(fileName) {
232
218
  if (currentLevel <= LogLevel.INFO) {
233
- transitionCategory("file-sync");
234
219
  const msg = ` ${import_picocolors.default.blue("↓")} ${fileName}`;
235
220
  logWithDedupe(msg, () => console.log(msg));
236
221
  }
237
222
  }
238
223
  function fileUp(fileName) {
239
224
  if (currentLevel <= LogLevel.INFO) {
240
- transitionCategory("file-sync");
241
225
  const msg = ` ${import_picocolors.default.green("↑")} ${fileName}`;
242
226
  logWithDedupe(msg, () => console.log(msg));
243
227
  }
244
228
  }
245
229
  function fileDelete(fileName) {
246
230
  if (currentLevel <= LogLevel.INFO) {
247
- transitionCategory("file-sync");
248
231
  const msg = ` ${import_picocolors.default.red("×")} ${fileName}`;
249
232
  logWithDedupe(msg, () => console.log(msg));
250
233
  }
@@ -254,7 +237,6 @@ function fileDelete(fileName) {
254
237
  */
255
238
  function status(message) {
256
239
  if (currentLevel <= LogLevel.INFO) {
257
- transitionCategory("other");
258
240
  flushDedupe();
259
241
  console.log(import_picocolors.default.dim(` ${message}`));
260
242
  }
@@ -307,8 +289,7 @@ function resetDisconnectState() {
307
289
  /**
308
290
  * WebSocket connection helper
309
291
  *
310
- * Thin wrapper around ws.Server that normalizes handshake and surfaces
311
- * simple callbacks. Keeps raw socket API localized.
292
+ * Wrapper around ws.Server that normalizes handshake and surfaces callbacks.
312
293
  */
313
294
  /**
314
295
  * Initializes a WebSocket server and returns a connection interface
@@ -421,174 +402,7 @@ function sendMessage(socket, message) {
421
402
  }
422
403
 
423
404
  //#endregion
424
- //#region ../shared/dist/hash.js
425
- /**
426
- * Base58 alphabet (no 0/O/I/l to avoid confusion)
427
- */
428
- const BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
429
- /**
430
- * Derive a short, deterministic hash from the full Framer project hash.
431
- * Uses a simple numeric hash encoded in base58 for compactness.
432
- * Idempotent: if input is already the target length, returns it unchanged.
433
- */
434
- function shortProjectHash(fullHash, length = 8) {
435
- if (fullHash.length === length) return fullHash;
436
- let h1 = 0;
437
- let h2 = 0;
438
- for (let i = 0; i < fullHash.length; i++) {
439
- const char = fullHash.charCodeAt(i);
440
- h1 = Math.imul(h1 ^ char, 2246822507);
441
- h2 = Math.imul(h2 ^ char, 3266489909);
442
- }
443
- h1 ^= h2 >>> 16;
444
- h2 ^= h1 >>> 13;
445
- let result = "";
446
- const combined = [Math.abs(h1), Math.abs(h2)];
447
- for (const num of combined) {
448
- let n = num >>> 0;
449
- while (n > 0 && result.length < length) {
450
- result += BASE58[n % 58];
451
- n = Math.floor(n / 58);
452
- }
453
- }
454
- while (result.length < length) result += BASE58[0];
455
- return result.slice(0, length);
456
- }
457
-
458
- //#endregion
459
- //#region ../shared/dist/ports.js
460
- /**
461
- * Generate a deterministic port number from a project hash (full or short).
462
- * Port range: 3847-4096 (250 possible ports)
463
- * Must match between CLI and plugin.
464
- *
465
- * Internally normalizes to the short id so both full and short inputs yield the same port.
466
- */
467
- function getPortFromHash(projectHash) {
468
- const shortId = shortProjectHash(projectHash);
469
- let hash = 0;
470
- for (let i = 0; i < shortId.length; i++) {
471
- const char = shortId.charCodeAt(i);
472
- hash = (hash << 5) - hash + char;
473
- hash = hash & hash;
474
- }
475
- return 3847 + Math.abs(hash) % 250;
476
- }
477
-
478
- //#endregion
479
- //#region ../shared/dist/paths.js
480
- /**
481
- * File path normalization utilities
482
- * Framer code files include extensions in their paths (.tsx, .ts, etc.)
483
- */
484
- const firstCharacterRegex = /^[a-zA-Z$_]/;
485
- const remainingCharactersRegex = /[^a-zA-Z0-9$_]/g;
486
- const onlyDotsRegex = /^\.+$/;
487
- const tsxExtension = ".tsx";
488
- var NameType;
489
- (function(NameType$1) {
490
- NameType$1["Variable"] = "Variable";
491
- NameType$1["Selector"] = "Selector";
492
- NameType$1["Directory"] = "Directory";
493
- })(NameType || (NameType = {}));
494
- function sanitizedName(type, name) {
495
- if (!name) return null;
496
- let validName = name.trim();
497
- if (validName.length === 0) return null;
498
- const validFirstChar = type === NameType.Selector ? "_" : "$";
499
- if (type === NameType.Directory) {
500
- if (onlyDotsRegex.test(validName)) return null;
501
- } else if (!firstCharacterRegex.test(validName)) validName = validFirstChar + validName;
502
- validName = validName.replace(remainingCharactersRegex, "_");
503
- validName = validName.replace(/_+/g, "_");
504
- validName = validName.replace(/^\$_/u, validFirstChar);
505
- return validName;
506
- }
507
- function sanitizedVariableName(name) {
508
- return sanitizedName(NameType.Variable, name);
509
- }
510
- function sanitizedDirectoryName(name) {
511
- return sanitizedName(NameType.Directory, name);
512
- }
513
- function capitalizeFirstLetter(str) {
514
- if (str.length === 0) return str;
515
- return str.charAt(0).toUpperCase() + str.slice(1);
516
- }
517
- function hasValidExtension(fileName) {
518
- if (fileName.endsWith(".json")) return true;
519
- return /\.[tj]sx?$/u.test(fileName);
520
- }
521
- function splitExtension(fileName) {
522
- const match = fileName.match(/^(.+?)(\.[^.]+)?$/);
523
- if (!match) return [fileName, ""];
524
- return [match[1], match[2]?.slice(1) || ""];
525
- }
526
- function dirname(filePath) {
527
- const at = filePath.lastIndexOf("/");
528
- if (at < 0) return "";
529
- return filePath.slice(0, at);
530
- }
531
- function filename(filePath) {
532
- const at = filePath.lastIndexOf("/") + 1;
533
- return filePath.slice(at);
534
- }
535
- function pathJoin(...parts) {
536
- let res = "";
537
- parts.forEach((part) => {
538
- while (part.startsWith("/")) part = part.slice(1);
539
- while (part.endsWith("/")) part = part.slice(0, -1);
540
- if (part === "") return;
541
- if (res !== "") res += "/";
542
- res += part;
543
- });
544
- return res;
545
- }
546
- function normalizePath(filePath) {
547
- if (!filePath) return "";
548
- const isAbsolute = filePath.startsWith("/");
549
- const segments = filePath.replace(/\\/g, "/").split("/");
550
- const stack = [];
551
- for (const segment of segments) {
552
- if (!segment || segment === ".") continue;
553
- if (segment === "..") {
554
- if (stack.length > 0) stack.pop();
555
- continue;
556
- }
557
- stack.push(segment);
558
- }
559
- const normalized = stack.join("/");
560
- if (isAbsolute) return `/${normalized}`;
561
- return normalized;
562
- }
563
- function sanitizeFilePath(input, capitalizeReactComponent = true) {
564
- const trimmed = input.trim();
565
- let [inputName, extension] = splitExtension(filename(trimmed));
566
- if (extension) extension = `.${extension}`;
567
- const dirName = dirname(trimmed).split("/").map((part) => sanitizedDirectoryName(part)).filter((part) => Boolean(part)).join("/");
568
- let name = sanitizedVariableName(inputName) ?? "MyComponent";
569
- if ((!hasValidExtension(extension) || extension === tsxExtension) && capitalizeReactComponent) name = capitalizeFirstLetter(name);
570
- return {
571
- path: pathJoin(dirName, name + extension),
572
- dirName,
573
- name,
574
- extension
575
- };
576
- }
577
- function isSupportedExtension$1(filePath) {
578
- return /\.(tsx?|jsx?|json)$/i.test(filePath);
579
- }
580
- /**
581
- * Pluralize a word based on count
582
- * @example pluralize(1, "file") => "1 file"
583
- * @example pluralize(3, "file") => "3 files"
584
- * @example pluralize(0, "conflict") => "0 conflicts"
585
- */
586
- function pluralize(count, singular, plural) {
587
- return `${count} ${count === 1 ? singular : plural ?? `${singular}s`}`;
588
- }
589
-
590
- //#endregion
591
- //#region src/utils/paths.ts
405
+ //#region src/utils/node-paths.ts
592
406
  /**
593
407
  * Path manipulation utilities
594
408
  */
@@ -627,9 +441,7 @@ function normalizePath$1(filePath) {
627
441
  /**
628
442
  * File watcher helper
629
443
  *
630
- * Thin wrapper around chokidar that normalizes file paths and emits
631
- * only supported file types (ts, tsx, js, json). Controller never worries
632
- * about addDir or platform separators.
444
+ * Wrapper around chokidar that normalizes file paths and filters to ts, tsx, js, json.
633
445
  */
634
446
  /**
635
447
  * Initializes a file watcher for the given directory
@@ -643,7 +455,7 @@ function initWatcher(filesDir) {
643
455
  });
644
456
  debug(`Watching directory: ${filesDir}`);
645
457
  const emitEvent = async (kind, absolutePath) => {
646
- if (!isSupportedExtension$1(absolutePath)) return;
458
+ if (!isSupportedExtension(absolutePath)) return;
647
459
  const rawRelativePath = normalizePath(getRelativePath(filesDir, absolutePath));
648
460
  const relativePath = sanitizeFilePath(rawRelativePath, false).path;
649
461
  let effectiveAbsolutePath = absolutePath;
@@ -696,7 +508,7 @@ function initWatcher(filesDir) {
696
508
  * (hash matches), because that means the file wasn't edited while CLI was offline.
697
509
  */
698
510
  const STATE_FILE_NAME = ".framer-sync-state.json";
699
- const CURRENT_VERSION = 2;
511
+ const CURRENT_VERSION = 1;
700
512
  const SUPPORTED_EXTENSIONS$1 = [
701
513
  ".ts",
702
514
  ".tsx",
@@ -801,7 +613,7 @@ async function listFiles(filesDir) {
801
613
  await walk(entryPath);
802
614
  continue;
803
615
  }
804
- if (!isSupportedExtension(entry.name)) continue;
616
+ if (!isSupportedExtension$1(entry.name)) continue;
805
617
  const sanitizedPath = sanitizeFilePath(normalizePath(path.relative(filesDir, entryPath)), false).path;
806
618
  try {
807
619
  const [content, stats] = await Promise.all([fs.readFile(entryPath, "utf-8"), fs.stat(entryPath)]);
@@ -1026,6 +838,16 @@ async function readFileSafe(fileName, filesDir) {
1026
838
  return null;
1027
839
  }
1028
840
  }
841
+ /**
842
+ * Filter out files whose content matches the last remembered hash.
843
+ * Used to skip inbound echoes of our own local sends.
844
+ */
845
+ function filterEchoedFiles(files, hashTracker) {
846
+ return files.filter((file) => {
847
+ if (file.content === void 0) return true;
848
+ return !hashTracker.shouldSkip(file.name, file.content);
849
+ });
850
+ }
1029
851
  function resolveRemoteReference(filesDir, rawName) {
1030
852
  const normalized = sanitizeRelativePath(rawName);
1031
853
  const absolutePath = path.join(filesDir, normalized.relativePath);
@@ -1043,7 +865,7 @@ function sanitizeRelativePath(relativePath) {
1043
865
  extension: sanitized.extension || path.extname(normalized) || DEFAULT_EXTENSION
1044
866
  };
1045
867
  }
1046
- function isSupportedExtension(fileName) {
868
+ function isSupportedExtension$1(fileName) {
1047
869
  const lower = fileName.toLowerCase();
1048
870
  return SUPPORTED_EXTENSIONS.some((ext) => lower.endsWith(ext));
1049
871
  }
@@ -1210,10 +1032,10 @@ var Installer = class {
1210
1032
  });
1211
1033
  }
1212
1034
  async processImports(fileName, content) {
1213
- const allImports = extractImports(content).filter((imp) => imp.type === "npm");
1035
+ const allImports = extractImports(content).filter((i) => i.type === "npm");
1214
1036
  if (allImports.length === 0) return;
1215
- const imports = this.allowUnsupportedNpm ? allImports : allImports.filter((imp) => this.isSupportedPackage(imp.name));
1216
- if (allImports.length - imports.length > 0 && !this.allowUnsupportedNpm) debug(`Skipping unsupported packages: ${allImports.filter((imp) => !this.isSupportedPackage(imp.name)).map((imp) => imp.name).join(", ")} (use --unsupported-npm to enable)`);
1037
+ const imports = this.allowUnsupportedNpm ? allImports : allImports.filter((i) => this.isSupportedPackage(i.name));
1038
+ if (allImports.length - imports.length > 0 && !this.allowUnsupportedNpm) debug(`Skipping unsupported packages: ${allImports.filter((i) => !this.isSupportedPackage(i.name)).map((i) => i.name).join(", ")} (use --unsupported-npm to enable)`);
1217
1039
  if (imports.length === 0) return;
1218
1040
  const hash = imports.map((imp) => imp.name).sort().join(",");
1219
1041
  if (this.processedImports.has(hash)) return;
@@ -1700,7 +1522,7 @@ var UserActionCoordinator = class {
1700
1522
  * Note: This is for INCOMING changes from remote. Local changes (from watcher)
1701
1523
  * are handled separately and always sent during watching mode.
1702
1524
  */
1703
- function validateIncomingChange(file, fileMeta, currentMode) {
1525
+ function validateIncomingChange(fileMeta, currentMode) {
1704
1526
  if (currentMode === "snapshot_processing" || currentMode === "handshaking") return {
1705
1527
  action: "queue",
1706
1528
  reason: "snapshot-in-progress"
@@ -1751,7 +1573,7 @@ async function findOrCreateProjectDir(projectHash, projectName, explicitDir) {
1751
1573
  const cwd = process.cwd();
1752
1574
  const existing = await findExistingProjectDir(cwd, projectHash);
1753
1575
  if (existing) return existing;
1754
- if (!projectName) throw new Error("Project name is required when creating a new workspace. Pass --name <project name>.");
1576
+ if (!projectName) throw new Error("Failed to get Project name. Pass --name <project name>.");
1755
1577
  const dirName = toDirName(projectName);
1756
1578
  const pkgName = toPackageName(projectName);
1757
1579
  const shortId = shortProjectHash(projectHash);
@@ -1792,9 +1614,10 @@ async function matchesProject(packageJsonPath, projectHash) {
1792
1614
  //#endregion
1793
1615
  //#region src/controller.ts
1794
1616
  /**
1795
- * Controller
1796
- * Single source of truth for all runtime state and orchestrates the sync lifecycle.
1797
- * Helpers are functions that provide data - they never hold control or callbacks.
1617
+ * CLI Controller
1618
+ *
1619
+ * All runtime state and orchestrates the sync lifecycle.
1620
+ * Helpers should provide data, nevering hold control or callbacks.
1798
1621
  */
1799
1622
  /** Log helper */
1800
1623
  function log(level, message) {
@@ -1805,16 +1628,6 @@ function log(level, message) {
1805
1628
  };
1806
1629
  }
1807
1630
  /**
1808
- * Filter out files whose content matches the last remembered hash.
1809
- * Used to skip inbound echoes of our own local sends.
1810
- */
1811
- function filterEchoedFiles(files, hashTracker) {
1812
- return files.filter((file) => {
1813
- if (file.content === void 0) return true;
1814
- return !hashTracker.shouldSkip(file.name, file.content);
1815
- });
1816
- }
1817
- /**
1818
1631
  * Pure state transition function
1819
1632
  * Takes current state + event, returns new state + effects to execute
1820
1633
  */
@@ -1844,7 +1657,7 @@ function transition(state, event) {
1844
1657
  },
1845
1658
  effects
1846
1659
  };
1847
- case "FILE_SYNCED":
1660
+ case "FILE_SYNCED_CONFIRMATION":
1848
1661
  effects.push(log("debug", `Remote confirmed sync: ${event.fileName}`), {
1849
1662
  type: "UPDATE_FILE_METADATA",
1850
1663
  fileName: event.fileName,
@@ -1905,7 +1718,7 @@ function transition(state, event) {
1905
1718
  state: {
1906
1719
  ...state,
1907
1720
  mode: "snapshot_processing",
1908
- queuedDiffs: event.files
1721
+ pendingRemoteChanges: event.files
1909
1722
  },
1910
1723
  effects
1911
1724
  };
@@ -1948,7 +1761,7 @@ function transition(state, event) {
1948
1761
  effects
1949
1762
  };
1950
1763
  }
1951
- const remoteTotal = state.queuedDiffs.length;
1764
+ const remoteTotal = state.pendingRemoteChanges.length;
1952
1765
  const totalCount = remoteTotal + localOnly.length;
1953
1766
  const updatedCount = safeWrites.length + localOnly.length;
1954
1767
  const unchangedCount = Math.max(0, remoteTotal - safeWrites.length);
@@ -1962,19 +1775,19 @@ function transition(state, event) {
1962
1775
  state: {
1963
1776
  ...state,
1964
1777
  mode: "watching",
1965
- queuedDiffs: []
1778
+ pendingRemoteChanges: []
1966
1779
  },
1967
1780
  effects
1968
1781
  };
1969
1782
  }
1970
1783
  case "FILE_CHANGE": {
1971
- const validation = validateIncomingChange(event.file, event.fileMeta, state.mode);
1784
+ const validation = validateIncomingChange(event.fileMeta, state.mode);
1972
1785
  if (validation.action === "queue") {
1973
1786
  effects.push(log("debug", `Queueing file change: ${event.file.name} (${validation.reason})`));
1974
1787
  return {
1975
1788
  state: {
1976
1789
  ...state,
1977
- queuedDiffs: [...state.queuedDiffs, event.file]
1790
+ pendingRemoteChanges: [...state.pendingRemoteChanges, event.file]
1978
1791
  },
1979
1792
  effects
1980
1793
  };
@@ -2012,7 +1825,7 @@ function transition(state, event) {
2012
1825
  state,
2013
1826
  effects
2014
1827
  };
2015
- case "REMOTE_DELETE_CONFIRMED":
1828
+ case "LOCAL_DELETE_APPROVED":
2016
1829
  effects.push(log("debug", `Delete confirmed: ${event.fileName}`), {
2017
1830
  type: "DELETE_LOCAL_FILES",
2018
1831
  names: [event.fileName]
@@ -2021,7 +1834,7 @@ function transition(state, event) {
2021
1834
  state,
2022
1835
  effects
2023
1836
  };
2024
- case "REMOTE_DELETE_CANCELLED":
1837
+ case "LOCAL_DELETE_REJECTED":
2025
1838
  effects.push(log("debug", `Delete cancelled: ${event.fileName}`));
2026
1839
  effects.push({
2027
1840
  type: "WRITE_FILES",
@@ -2194,7 +2007,7 @@ function transition(state, event) {
2194
2007
  state: {
2195
2008
  ...rest,
2196
2009
  mode: "watching",
2197
- queuedDiffs: []
2010
+ pendingRemoteChanges: []
2198
2011
  },
2199
2012
  effects
2200
2013
  };
@@ -2397,7 +2210,7 @@ async function start(config) {
2397
2210
  let syncState = {
2398
2211
  mode: "disconnected",
2399
2212
  socket: null,
2400
- queuedDiffs: [],
2213
+ pendingRemoteChanges: [],
2401
2214
  pendingOperations: /* @__PURE__ */ new Map(),
2402
2215
  nextOperationId: 1
2403
2216
  };
@@ -2461,15 +2274,13 @@ async function start(config) {
2461
2274
  case "request-files":
2462
2275
  event = { type: "REQUEST_FILES" };
2463
2276
  break;
2464
- case "file-list": {
2465
- const totalSize = message.files.reduce((sum, f) => sum + (f.content?.length ?? 0), 0);
2466
- debug(`Received file list: ${message.files.length} files (${(totalSize / 1024).toFixed(1)}KB)`);
2277
+ case "file-list":
2278
+ debug(`Received file list: ${message.files.length} files`);
2467
2279
  event = {
2468
2280
  type: "FILE_LIST",
2469
2281
  files: message.files
2470
2282
  };
2471
2283
  break;
2472
- }
2473
2284
  case "file-change":
2474
2285
  event = {
2475
2286
  type: "FILE_CHANGE",
@@ -2491,7 +2302,7 @@ async function start(config) {
2491
2302
  const unmatched = [];
2492
2303
  for (const fileName of message.fileNames) if (!userActions.handleConfirmation(`delete:${fileName}`, true)) unmatched.push(fileName);
2493
2304
  for (const fileName of unmatched) await processEvent({
2494
- type: "REMOTE_DELETE_CONFIRMED",
2305
+ type: "LOCAL_DELETE_APPROVED",
2495
2306
  fileName
2496
2307
  });
2497
2308
  return;
@@ -2500,7 +2311,7 @@ async function start(config) {
2500
2311
  for (const file of message.files) {
2501
2312
  userActions.handleConfirmation(`delete:${file.fileName}`, false);
2502
2313
  await processEvent({
2503
- type: "REMOTE_DELETE_CANCELLED",
2314
+ type: "LOCAL_DELETE_REJECTED",
2504
2315
  fileName: file.fileName,
2505
2316
  content: file.content ?? ""
2506
2317
  });
@@ -2508,7 +2319,7 @@ async function start(config) {
2508
2319
  return;
2509
2320
  case "file-synced":
2510
2321
  event = {
2511
- type: "FILE_SYNCED",
2322
+ type: "FILE_SYNCED_CONFIRMATION",
2512
2323
  fileName: message.fileName,
2513
2324
  remoteModifiedAt: message.remoteModifiedAt
2514
2325
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "framer-code-link",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "CLI tool for syncing Framer code components - controller-centric architecture",
5
5
  "main": "dist/index.mjs",
6
6
  "type": "module",
@@ -22,6 +22,7 @@
22
22
  "author": "",
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
+ "@code-link/shared": "1.0.0",
25
26
  "@typescript/ata": "^0.9.8",
26
27
  "chokidar": "^5.0.0",
27
28
  "commander": "^14.0.2",