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