framer-code-link 0.1.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.
package/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # Framer Code Link CLI - Next Generation
2
+
3
+ A controller-centric architecture for syncing Framer code components to your local filesystem.
4
+
5
+ ## Architecture
6
+
7
+ This implementation follows a **controller-centric design** inspired by Go's `main.go` pattern:
8
+
9
+ - **Single source of truth**: All runtime state lives in `controller.ts`
10
+ - **Clear orchestration**: The controller owns the lifecycle and message routing
11
+ - **Thin helpers**: Domain logic is delegated to pure/stateless helper functions
12
+ - **Extensible**: Easy to add new flows without touching the core controller logic
13
+
14
+ ### File Structure
15
+
16
+ ```
17
+ src/
18
+ controller.ts # The orchestration hub - holds state, routes messages
19
+ index.ts # CLI entry point
20
+ types.ts # Shared TypeScript types
21
+ helpers/
22
+ connection.ts # WebSocket server wrapper
23
+ watcher.ts # File watcher wrapper (chokidar)
24
+ files.ts # Disk I/O and conflict detection
25
+ installer.ts # Type installer (ATA wrapper)
26
+ utils/
27
+ hashing.ts # Hash tracking for echo prevention
28
+ sanitization.ts # Path sanitization
29
+ logging.ts # Consistent logging
30
+ paths.ts # Path manipulation utilities
31
+ ```
32
+
33
+ ## Key Design Principles
34
+
35
+ ### 1. Controller Owns State
36
+
37
+ The controller maintains all runtime state in plain objects:
38
+
39
+ ```typescript
40
+ const state: RuntimeState = {
41
+ socket: null, // Active WebSocket connection
42
+ initialSyncComplete: false, // Has first sync finished?
43
+ pendingConflicts: [], // Conflicts awaiting user input
44
+ lastRemoteAck: new Map(), // Remote timestamps for conflict detection
45
+ }
46
+ ```
47
+
48
+ ### 2. Helpers Provide Data, Never Control
49
+
50
+ Helpers are thin wrappers that:
51
+
52
+ - Return data to the controller
53
+ - Never hold callbacks or "service" state
54
+ - Keep logic focused and testable
55
+
56
+ Example:
57
+
58
+ ```typescript
59
+ // Helper provides data
60
+ const { conflicts, writes } = detectConflicts(remoteFiles, filesDir)
61
+
62
+ // Controller decides what to do
63
+ if (conflicts.length > 0) {
64
+ state.pendingConflicts = conflicts
65
+ sendMessage(socket, { type: "conflicts-detected", conflicts })
66
+ }
67
+ ```
68
+
69
+ ### 3. Clear Message Routing
70
+
71
+ All incoming messages flow through a single `switch` statement in the controller:
72
+
73
+ ```typescript
74
+ switch (message.type) {
75
+ case "request-files": {
76
+ /* ... */
77
+ }
78
+ case "file-list": {
79
+ /* ... */
80
+ }
81
+ case "file-change": {
82
+ /* ... */
83
+ }
84
+ // etc.
85
+ }
86
+ ```
87
+
88
+ This makes the flow obvious and easy to trace.
89
+
90
+ ### 4. Echo Prevention
91
+
92
+ Critical ordering prevents infinite loops:
93
+
94
+ ```typescript
95
+ // 1. Update hash tracker FIRST (in memory)
96
+ hashTracker.remember(fileName, content)
97
+
98
+ // 2. Write to disk SECOND
99
+ await fs.writeFile(filePath, content)
100
+
101
+ // 3. Watcher fires, checks hash, and skips (no echo!)
102
+ ```
103
+
104
+ ## Usage
105
+
106
+ ### CLI
107
+
108
+ ```bash
109
+ # Start with project hash (project name will be received from plugin during handshake)
110
+ npx framer-code-link <projectHash>
111
+
112
+ # Override project name (optional - useful if you want a different folder name)
113
+ npx framer-code-link <projectHash> --name "My Project"
114
+
115
+ # Custom directory
116
+ npx framer-code-link <projectHash> --dir ./my-project
117
+
118
+ # Verbose logging
119
+ npx framer-code-link <projectHash> --verbose
120
+
121
+ # Auto-delete files without confirmation (use with caution!)
122
+ npx framer-code-link <projectHash> --dangerously-auto-delete
123
+ ```
124
+
125
+ **Note**: The project name is automatically received from the Framer plugin during the initial handshake. You only need to specify `--name` if you want to override it or if you're creating a new workspace before the plugin connects.
126
+
127
+ ### Sync Behavior
128
+
129
+ The CLI performs **symmetric two-way sync** during initial connection:
130
+
131
+ - **Remote-only files** → Downloaded to local
132
+ - **Local-only files** → Uploaded to remote
133
+ - **Files on both sides with differences** → Conflict (requires user resolution)
134
+
135
+ This ensures that new files created while the CLI was offline are automatically synced in both directions.
136
+
137
+ ### Delete Confirmation
138
+
139
+ By default, when you delete a file locally, the plugin will show a confirmation modal before deleting it from Framer. This prevents accidental data loss.
140
+
141
+ To skip confirmations and auto-delete files, use the `--dangerously-auto-delete` flag:
142
+
143
+ ```bash
144
+ npx framer-code-link <projectHash> --dangerously-auto-delete
145
+ ```
146
+
147
+ **⚠️ Warning**: This will immediately delete files from Framer without any confirmation when you delete them locally.
148
+
149
+ ### Programmatic
150
+
151
+ ```typescript
152
+ import { start } from "code-link-cli-next-2"
153
+
154
+ await start({
155
+ port: 8080,
156
+ projectHash: "my-project-hash",
157
+ projectDir: "/path/to/project",
158
+ filesDir: "/path/to/project/files",
159
+ })
160
+ ```
161
+
162
+ ## Extending the Controller
163
+
164
+ To add new functionality:
165
+
166
+ 1. **Add message type** to `types.ts`
167
+ 2. **Add case** to the controller's `switch` statement
168
+ 3. **Extract logic** to a helper if it's complex
169
+ 4. **Update state** as needed in the controller
170
+
171
+ Example - adding a "bulk sync" feature:
172
+
173
+ ```typescript
174
+ // 1. Add to types.ts
175
+ export type IncomingMessage =
176
+ | { type: "bulk-sync"; files: FileInfo[] }
177
+ | // ... existing types
178
+
179
+ // 2. Add case to controller
180
+ case "bulk-sync": {
181
+ info(`Bulk syncing ${message.files.length} files`)
182
+ await writeRemoteFiles(message.files, config.filesDir, hashTracker, installer)
183
+ success("Bulk sync complete")
184
+ break
185
+ }
186
+ ```
187
+
188
+ ## Testing Strategy
189
+
190
+ - **Controller**: Integration-style tests with mock connection + watcher
191
+ - **Helpers**: Unit tests for conflict detection, file operations, etc.
192
+ - **Utils**: Tiny focused tests for hashing, sanitization, etc.
193
+
194
+ ## Design Document
195
+
196
+ For the full design rationale, see `../cli-next/design.md`.