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 +196 -0
- package/dist/index.js +2021 -0
- package/dist/project-DhpsFg77.js +53 -0
- package/package.json +36 -0
- package/src/controller.test.ts +966 -0
- package/src/controller.ts +1212 -0
- package/src/helpers/connection.ts +95 -0
- package/src/helpers/files.test.ts +117 -0
- package/src/helpers/files.ts +378 -0
- package/src/helpers/installer.ts +534 -0
- package/src/helpers/sync-validator.ts +87 -0
- package/src/helpers/user-actions.ts +162 -0
- package/src/helpers/watcher.ts +115 -0
- package/src/index.ts +75 -0
- package/src/types.ts +107 -0
- package/src/utils/file-metadata-cache.ts +121 -0
- package/src/utils/hashing.ts +95 -0
- package/src/utils/imports.ts +62 -0
- package/src/utils/logging.ts +47 -0
- package/src/utils/paths.ts +76 -0
- package/src/utils/project.ts +94 -0
- package/src/utils/state-persistence.ts +138 -0
- package/tsconfig.json +14 -0
- package/vitest.config.ts +8 -0
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`.
|