framer-code-link 0.1.2 → 0.1.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "framer-code-link",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "CLI tool for syncing Framer code components - controller-centric architecture",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -19,18 +19,18 @@
19
19
  "author": "",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@typescript/ata": "^0.9.7",
23
- "chokidar": "^4.0.3",
24
- "commander": "^12.1.0",
25
- "prettier": "^3.6.2",
26
- "typescript": "^5.7.2",
27
- "ws": "^8.18.0"
22
+ "@typescript/ata": "^0.9.8",
23
+ "chokidar": "^5.0.0",
24
+ "commander": "^14.0.2",
25
+ "prettier": "^3.7.4",
26
+ "typescript": "^5.9.3",
27
+ "ws": "^8.18.3"
28
28
  },
29
29
  "devDependencies": {
30
- "@types/node": "^22.10.2",
31
- "@types/ws": "^8.5.13",
32
- "tsdown": "^0.2.17",
33
- "tsx": "^4.19.2",
34
- "vitest": "^2.1.8"
30
+ "@types/node": "^22.19.2",
31
+ "@types/ws": "^8.18.1",
32
+ "tsdown": "^0.17.3",
33
+ "tsx": "^4.21.0",
34
+ "vitest": "^2.1.9"
35
35
  }
36
36
  }
@@ -1,98 +1,6 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest"
1
+ import { describe, it, expect } from "vitest"
2
2
  import { transition } from "./controller.js"
3
- import { createHashTracker } from "./utils/hashing.js"
4
- import type { Conflict, Config } from "./types.js"
5
- import type { Installer } from "./helpers/installer.js"
6
- import * as connection from "./helpers/connection.js"
7
- import * as filesHelper from "./helpers/files.js"
8
- import * as statePersistence from "./utils/state-persistence.js"
9
3
  import type { WebSocket } from "ws"
10
- import { UserActionCoordinator } from "./helpers/user-actions.js"
11
-
12
- vi.mock("ws", () => {
13
- class MockWebSocket {
14
- send(
15
- _data: string,
16
- callback: (err?: Error | null) => void = () => undefined
17
- ) {
18
- callback(null)
19
- }
20
- }
21
-
22
- class MockWebSocketServer {
23
- constructor(_options: unknown) {}
24
- on(): void {}
25
- close(): void {}
26
- }
27
-
28
- return {
29
- WebSocket: MockWebSocket,
30
- WebSocketServer: MockWebSocketServer,
31
- }
32
- })
33
-
34
- const sendMessageSpy = vi.spyOn(connection, "sendMessage")
35
- const writeRemoteFilesSpy = vi.spyOn(filesHelper, "writeRemoteFiles")
36
- const savePersistedStateSpy = vi.spyOn(statePersistence, "savePersistedState")
37
- const readFileSafeSpy = vi.spyOn(filesHelper, "readFileSafe")
38
-
39
- function createState(): RuntimeState {
40
- return {
41
- socket: {} as WebSocket,
42
- initialSyncComplete: false,
43
- pendingWrites: [],
44
- pendingLocalAdds: [],
45
- lastRemoteSync: new Map(),
46
- }
47
- }
48
-
49
- function createConfig(): Config & { projectDir: string; filesDir: string } {
50
- return {
51
- port: 0,
52
- projectHash: "test-project",
53
- projectDir: "/tmp/project",
54
- filesDir: "/tmp/project/files",
55
- dangerouslyAutoDelete: false,
56
- }
57
- }
58
-
59
- function createInstaller(): Installer {
60
- return {
61
- process: vi.fn(),
62
- } as unknown as Installer
63
- }
64
-
65
- function createMockUserActions(): UserActionCoordinator & {
66
- requestDeleteDecision: ReturnType<typeof vi.fn>
67
- requestConflictDecisions: ReturnType<typeof vi.fn>
68
- handleConfirmation: ReturnType<typeof vi.fn>
69
- cleanup: ReturnType<typeof vi.fn>
70
- } {
71
- return {
72
- requestDeleteDecision: vi.fn(),
73
- requestConflictDecisions: vi.fn(),
74
- handleConfirmation: vi.fn(),
75
- cleanup: vi.fn(),
76
- } as unknown as UserActionCoordinator & {
77
- requestDeleteDecision: ReturnType<typeof vi.fn>
78
- requestConflictDecisions: ReturnType<typeof vi.fn>
79
- handleConfirmation: ReturnType<typeof vi.fn>
80
- cleanup: ReturnType<typeof vi.fn>
81
- }
82
- }
83
-
84
- beforeEach(() => {
85
- sendMessageSpy.mockReset()
86
- sendMessageSpy.mockResolvedValue()
87
- writeRemoteFilesSpy.mockReset()
88
- writeRemoteFilesSpy.mockResolvedValue()
89
- savePersistedStateSpy.mockReset()
90
- savePersistedStateSpy.mockResolvedValue()
91
- readFileSafeSpy.mockReset()
92
- readFileSafeSpy.mockResolvedValue(null)
93
- })
94
-
95
- // Legacy tests - commented out after state machine migration
96
4
  describe("State Machine", () => {
97
5
  describe("HANDSHAKE transition", () => {
98
6
  it("transitions from disconnected to handshaking", () => {
@@ -176,7 +84,7 @@ describe("State Machine", () => {
176
84
  expect(result.effects[0]).toMatchObject({ type: "PERSIST_STATE" })
177
85
  expect(result.effects[1]).toMatchObject({
178
86
  type: "LOG",
179
- level: "info",
87
+ level: "debug",
180
88
  })
181
89
  })
182
90
  })
@@ -206,7 +114,7 @@ describe("State Machine", () => {
206
114
  expect(result.effects).toHaveLength(2)
207
115
  expect(result.effects[0]).toMatchObject({
208
116
  type: "LOG",
209
- level: "info",
117
+ level: "debug",
210
118
  })
211
119
  expect(result.effects[1]).toMatchObject({
212
120
  type: "DETECT_CONFLICTS",
@@ -362,7 +270,7 @@ describe("State Machine", () => {
362
270
  })
363
271
 
364
272
  describe("REMOTE_FILE_DELETE transition", () => {
365
- it("requests confirmation in watching mode", () => {
273
+ it("applies delete immediately in watching mode", () => {
366
274
  const initialState = {
367
275
  mode: "watching" as const,
368
276
  socket: {} as WebSocket,
@@ -387,36 +295,9 @@ describe("State Machine", () => {
387
295
  })
388
296
 
389
297
  expect(result.state.mode).toBe("watching")
390
- expect(
391
- result.effects.some((e) => e.type === "REQUEST_DELETE_CONFIRMATION")
392
- ).toBe(true)
393
- const confirmEffect = result.effects.find(
394
- (e) => e.type === "REQUEST_DELETE_CONFIRMATION"
298
+ expect(result.effects.some((e) => e.type === "DELETE_LOCAL_FILES")).toBe(
299
+ true
395
300
  )
396
- expect(confirmEffect).toMatchObject({
397
- type: "REQUEST_DELETE_CONFIRMATION",
398
- fileName: "Test.tsx",
399
- requireConfirmation: true,
400
- })
401
- })
402
-
403
- it("auto-deletes when autoDelete is true", () => {
404
- const initialState = {
405
- mode: "watching" as const,
406
- socket: {} as WebSocket,
407
- files: new Map(),
408
- queuedDiffs: [],
409
- pendingOperations: new Map(),
410
- nextOperationId: 1,
411
- }
412
-
413
- const result = transition(initialState, {
414
- type: "REMOTE_FILE_DELETE",
415
- fileName: "Test.tsx",
416
- autoDelete: true,
417
- })
418
-
419
- expect(result.state.mode).toBe("watching")
420
301
  const deleteEffect = result.effects.find(
421
302
  (e) => e.type === "DELETE_LOCAL_FILES"
422
303
  )
@@ -424,12 +305,10 @@ describe("State Machine", () => {
424
305
  type: "DELETE_LOCAL_FILES",
425
306
  names: ["Test.tsx"],
426
307
  })
427
- expect(
428
- result.effects.some((e) => e.type === "PERSIST_STATE")
429
- ).toBe(true)
308
+ expect(result.effects.some((e) => e.type === "PERSIST_STATE")).toBe(true)
430
309
  })
431
310
 
432
- it("queues deletes during snapshot processing", () => {
311
+ it("applies deletes immediately during snapshot processing", () => {
433
312
  const initialState = {
434
313
  mode: "snapshot_processing" as const,
435
314
  socket: {} as WebSocket,
@@ -445,7 +324,9 @@ describe("State Machine", () => {
445
324
  })
446
325
 
447
326
  expect(result.state.mode).toBe("snapshot_processing")
448
- expect(result.effects.some((e) => e.type === "DELETE_FILES")).toBe(false)
327
+ expect(result.effects.some((e) => e.type === "DELETE_LOCAL_FILES")).toBe(
328
+ true
329
+ )
449
330
  expect(result.effects.some((e) => e.type === "LOG")).toBe(true)
450
331
  })
451
332
 
@@ -465,7 +346,9 @@ describe("State Machine", () => {
465
346
  })
466
347
 
467
348
  expect(result.state.mode).toBe("disconnected")
468
- expect(result.effects.some((e) => e.type === "DELETE_FILES")).toBe(false)
349
+ expect(result.effects.some((e) => e.type === "DELETE_LOCAL_FILES")).toBe(
350
+ false
351
+ )
469
352
  expect(
470
353
  result.effects.some((e) => e.type === "LOG" && e.level === "warn")
471
354
  ).toBe(true)
@@ -665,7 +548,7 @@ describe("State Machine", () => {
665
548
 
666
549
  it("ignores events when disconnected", () => {
667
550
  const initialState = {
668
- mode: "watching" as const,
551
+ mode: "disconnected" as const,
669
552
  socket: null,
670
553
  files: new Map(),
671
554
  queuedDiffs: [],
@@ -774,13 +657,15 @@ describe("State Machine", () => {
774
657
  const writeEffects = result.effects.filter(
775
658
  (e) => e.type === "WRITE_FILES"
776
659
  )
777
- expect(writeEffects).toHaveLength(1)
660
+ // Each conflict gets its own WRITE_FILES effect
661
+ expect(writeEffects).toHaveLength(2)
778
662
  expect(writeEffects[0]).toMatchObject({
779
663
  type: "WRITE_FILES",
780
- files: [
781
- { name: "Test1.tsx", content: "remote 1" },
782
- { name: "Test2.tsx", content: "remote 2" },
783
- ],
664
+ files: [{ name: "Test1.tsx", content: "remote 1" }],
665
+ })
666
+ expect(writeEffects[1]).toMatchObject({
667
+ type: "WRITE_FILES",
668
+ files: [{ name: "Test2.tsx", content: "remote 2" }],
784
669
  })
785
670
  expect(result.effects.some((e) => e.type === "PERSIST_STATE")).toBe(true)
786
671
  })