handoff-app 1.0.3 → 1.0.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.
Files changed (35) hide show
  1. package/Changelog.md +10 -5
  2. package/dist/cli.js +0 -0
  3. package/dist/index.js +4 -1
  4. package/dist/transformers/plugins/ssr-render.js +4 -3
  5. package/dist/transformers/preview/component/api.js +3 -2
  6. package/dist/transformers/preview/component/builder.js +2 -1
  7. package/dist/transformers/utils/handlebars.js +3 -2
  8. package/package.json +1 -1
  9. package/src/app/components/Component/Preview.tsx +121 -121
  10. package/src/app/components/ComponentSearch.tsx +1 -1
  11. package/src/app/components/util/index.ts +17 -9
  12. package/src/app/pages/system/component/[component]/index.tsx +1 -1
  13. package/src/app/pages/system/index.tsx +2 -2
  14. package/src/app/pages/system/tokens/index.tsx +10 -10
  15. package/src/index.ts +3 -0
  16. package/src/transformers/plugins/ssr-render.ts +3 -3
  17. package/src/transformers/preview/component/api.ts +1 -1
  18. package/src/transformers/preview/component/builder.ts +1 -1
  19. package/src/transformers/utils/handlebars.ts +2 -2
  20. package/dist/commands/platform/init.d.ts +0 -6
  21. package/dist/commands/platform/init.js +0 -231
  22. package/dist/commands/platform/login.d.ts +0 -6
  23. package/dist/commands/platform/login.js +0 -108
  24. package/dist/commands/platform/logout.d.ts +0 -6
  25. package/dist/commands/platform/logout.js +0 -88
  26. package/dist/commands/platform/pull.d.ts +0 -8
  27. package/dist/commands/platform/pull.js +0 -112
  28. package/dist/commands/platform/push.d.ts +0 -8
  29. package/dist/commands/platform/push.js +0 -428
  30. package/dist/platform/client.d.ts +0 -122
  31. package/dist/platform/client.js +0 -228
  32. package/dist/platform/credentials.d.ts +0 -40
  33. package/dist/platform/credentials.js +0 -91
  34. package/dist/platform/sync.d.ts +0 -68
  35. package/dist/platform/sync.js +0 -349
@@ -1,349 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.loadSyncState = loadSyncState;
16
- exports.saveSyncState = saveSyncState;
17
- exports.pullProject = pullProject;
18
- exports.computePushDiff = computePushDiff;
19
- exports.pushProject = pushProject;
20
- exports.createPushProject = createPushProject;
21
- exports.walkDirectory = walkDirectory;
22
- const crypto_1 = __importDefault(require("crypto"));
23
- const fs_extra_1 = __importDefault(require("fs-extra"));
24
- const path_1 = __importDefault(require("path"));
25
- const logger_1 = require("../utils/logger");
26
- const client_1 = require("./client");
27
- // ── Constants ───────────────────────────────────────────────────────────
28
- const SYNC_STATE_DIR = '.handoff';
29
- const SYNC_STATE_FILE = 'sync-state.json';
30
- /** Paths excluded from sync (matching server-side exclusions) */
31
- const EXCLUDED_PATTERNS = [
32
- '.env',
33
- 'node_modules',
34
- 'out',
35
- '.handoff',
36
- '.git',
37
- '.DS_Store',
38
- ];
39
- // ── Sync State ──────────────────────────────────────────────────────────
40
- /**
41
- * Load the sync state from the project directory.
42
- * Returns null if no sync state exists (first sync).
43
- */
44
- function loadSyncState(projectDir) {
45
- const stateFile = path_1.default.join(projectDir, SYNC_STATE_DIR, SYNC_STATE_FILE);
46
- try {
47
- if (fs_extra_1.default.existsSync(stateFile)) {
48
- return fs_extra_1.default.readJsonSync(stateFile);
49
- }
50
- }
51
- catch (err) {
52
- logger_1.Logger.warn('Could not read sync state file.');
53
- logger_1.Logger.debug('Sync state read error', err);
54
- }
55
- return null;
56
- }
57
- /**
58
- * Save the sync state to the project directory.
59
- */
60
- function saveSyncState(projectDir, state) {
61
- const stateDir = path_1.default.join(projectDir, SYNC_STATE_DIR);
62
- const stateFile = path_1.default.join(stateDir, SYNC_STATE_FILE);
63
- fs_extra_1.default.ensureDirSync(stateDir);
64
- fs_extra_1.default.writeJsonSync(stateFile, state, { spaces: 2 });
65
- }
66
- // ── Pull ────────────────────────────────────────────────────────────────
67
- /**
68
- * Pull files from the remote project to the local directory.
69
- *
70
- * Compares the remote manifest against local files (using MD5 hashes)
71
- * and downloads any files that differ or are missing locally.
72
- *
73
- * @param onProgress Optional callback for reporting progress
74
- */
75
- function pullProject(baseUrl_1, token_1, projectId_1, projectDir_1) {
76
- return __awaiter(this, arguments, void 0, function* (baseUrl, token, projectId, projectDir, options = {}) {
77
- const { force = false, onProgress } = options;
78
- onProgress === null || onProgress === void 0 ? void 0 : onProgress('Fetching remote manifest...');
79
- const manifest = yield (0, client_1.getManifest)(baseUrl, token, projectId);
80
- const remoteFiles = manifest.files;
81
- const downloaded = [];
82
- const deleted = [];
83
- const unchanged = [];
84
- // Download new or changed files
85
- const remoteKeys = Object.keys(remoteFiles);
86
- const total = remoteKeys.length;
87
- for (let i = 0; i < total; i++) {
88
- const relativePath = remoteKeys[i];
89
- const remoteEntry = remoteFiles[relativePath];
90
- const localPath = path_1.default.join(projectDir, relativePath);
91
- onProgress === null || onProgress === void 0 ? void 0 : onProgress(`Checking (${i + 1}/${total}): ${relativePath}`);
92
- let needsDownload = false;
93
- if (!fs_extra_1.default.existsSync(localPath)) {
94
- needsDownload = true;
95
- }
96
- else if (force) {
97
- needsDownload = true;
98
- }
99
- else {
100
- // Compare ETags (MD5 hash)
101
- const localHash = computeEtag(localPath);
102
- const remoteEtag = normalizeEtag(remoteEntry.etag);
103
- if (localHash !== remoteEtag) {
104
- needsDownload = true;
105
- }
106
- }
107
- if (needsDownload) {
108
- onProgress === null || onProgress === void 0 ? void 0 : onProgress(`Downloading (${i + 1}/${total}): ${relativePath}`);
109
- const content = yield (0, client_1.downloadFile)(baseUrl, token, projectId, relativePath);
110
- fs_extra_1.default.ensureDirSync(path_1.default.dirname(localPath));
111
- fs_extra_1.default.writeFileSync(localPath, content);
112
- downloaded.push(relativePath);
113
- }
114
- else {
115
- unchanged.push(relativePath);
116
- }
117
- }
118
- // Detect local files that no longer exist remotely
119
- const localFiles = walkDirectory(projectDir);
120
- for (const localRelative of localFiles) {
121
- if (!remoteFiles[localRelative]) {
122
- if (force) {
123
- const localPath = path_1.default.join(projectDir, localRelative);
124
- fs_extra_1.default.removeSync(localPath);
125
- deleted.push(localRelative);
126
- }
127
- // If not force, we leave orphaned local files alone
128
- }
129
- }
130
- // Save sync state
131
- saveSyncState(projectDir, {
132
- version: manifest.version,
133
- manifest: remoteFiles,
134
- lastSync: new Date().toISOString(),
135
- });
136
- return { downloaded, deleted, unchanged, version: manifest.version };
137
- });
138
- }
139
- // ── Push ────────────────────────────────────────────────────────────────
140
- /**
141
- * Compute the diff between local files and the last-known manifest.
142
- */
143
- function computePushDiff(projectDir) {
144
- var _a;
145
- const syncState = loadSyncState(projectDir);
146
- const lastManifest = (_a = syncState === null || syncState === void 0 ? void 0 : syncState.manifest) !== null && _a !== void 0 ? _a : {};
147
- const localFiles = walkDirectory(projectDir);
148
- const added = [];
149
- const modified = [];
150
- const deleted = [];
151
- // Check local files against the manifest
152
- for (const relativePath of localFiles) {
153
- const localPath = path_1.default.join(projectDir, relativePath);
154
- const localEtag = computeEtag(localPath);
155
- const remoteEntry = lastManifest[relativePath];
156
- if (!remoteEntry) {
157
- added.push(relativePath);
158
- }
159
- else {
160
- const remoteEtag = normalizeEtag(remoteEntry.etag);
161
- if (localEtag !== remoteEtag) {
162
- modified.push(relativePath);
163
- }
164
- }
165
- }
166
- // Check for files in the manifest that no longer exist locally
167
- const localSet = new Set(localFiles);
168
- for (const remotePath of Object.keys(lastManifest)) {
169
- if (!localSet.has(remotePath)) {
170
- deleted.push(remotePath);
171
- }
172
- }
173
- return { added, modified, deleted };
174
- }
175
- /**
176
- * Push local changes to the remote project.
177
- *
178
- * @param onProgress Optional callback for reporting progress
179
- */
180
- function pushProject(baseUrl_1, token_1, projectId_1, projectDir_1) {
181
- return __awaiter(this, arguments, void 0, function* (baseUrl, token, projectId, projectDir, options = {}) {
182
- var _a, _b;
183
- const { onProgress } = options;
184
- const syncState = loadSyncState(projectDir);
185
- const baseVersion = (_a = syncState === null || syncState === void 0 ? void 0 : syncState.version) !== null && _a !== void 0 ? _a : 0;
186
- onProgress === null || onProgress === void 0 ? void 0 : onProgress('Computing changes...');
187
- const diff = computePushDiff(projectDir);
188
- const changedPaths = [...diff.added, ...diff.modified];
189
- if (changedPaths.length === 0 && diff.deleted.length === 0) {
190
- onProgress === null || onProgress === void 0 ? void 0 : onProgress('No changes to push.');
191
- return {
192
- success: true,
193
- version: baseVersion,
194
- uploaded: [],
195
- deleted: [],
196
- errors: [],
197
- files: (_b = syncState === null || syncState === void 0 ? void 0 : syncState.manifest) !== null && _b !== void 0 ? _b : {},
198
- };
199
- }
200
- // For any changed file inside a component directory, also include the
201
- // component config file (e.g. button/button.js) so the server always
202
- // has the config context even if only other files in the dir changed.
203
- const configExtras = getComponentConfigsForChangedPaths(projectDir, changedPaths);
204
- for (const extra of configExtras) {
205
- if (!changedPaths.includes(extra)) {
206
- changedPaths.push(extra);
207
- }
208
- }
209
- onProgress === null || onProgress === void 0 ? void 0 : onProgress(`Preparing ${changedPaths.length} file(s) to upload, ${diff.deleted.length} to delete...`);
210
- // Read file contents into buffers
211
- const files = new Map();
212
- for (const filePath of changedPaths) {
213
- const localPath = path_1.default.join(projectDir, filePath);
214
- files.set(filePath, fs_extra_1.default.readFileSync(localPath));
215
- }
216
- onProgress === null || onProgress === void 0 ? void 0 : onProgress('Pushing changes...');
217
- const result = yield (0, client_1.pushChanges)(baseUrl, token, projectId, baseVersion, files, diff.deleted);
218
- // Update sync state with new version and manifest
219
- saveSyncState(projectDir, {
220
- version: result.version,
221
- manifest: result.files,
222
- lastSync: new Date().toISOString(),
223
- });
224
- return result;
225
- });
226
- }
227
- // ── Create Push (New Project) ───────────────────────────────────────────
228
- /**
229
- * Push an entire local project to the platform as a new project.
230
- * Collects all syncable local files and sends them via the create-push
231
- * endpoint, which creates the project and uploads files in one request.
232
- *
233
- * @param onProgress Optional callback for reporting progress
234
- */
235
- function createPushProject(baseUrl_1, token_1, orgId_1, projectDir_1) {
236
- return __awaiter(this, arguments, void 0, function* (baseUrl, token, orgId, projectDir, options = {}) {
237
- const { onProgress } = options;
238
- onProgress === null || onProgress === void 0 ? void 0 : onProgress('Scanning local files...');
239
- const localFiles = walkDirectory(projectDir);
240
- onProgress === null || onProgress === void 0 ? void 0 : onProgress(`Preparing ${localFiles.length} file(s) for upload...`);
241
- const files = new Map();
242
- for (const relativePath of localFiles) {
243
- const localPath = path_1.default.join(projectDir, relativePath);
244
- files.set(relativePath, fs_extra_1.default.readFileSync(localPath));
245
- }
246
- onProgress === null || onProgress === void 0 ? void 0 : onProgress('Uploading project to platform...');
247
- const result = yield (0, client_1.createPush)(baseUrl, token, orgId, files, {
248
- name: options.name,
249
- figmaProjectId: options.figmaProjectId,
250
- });
251
- // Save sync state so subsequent push/pull commands work correctly
252
- saveSyncState(projectDir, {
253
- version: result.syncVersion,
254
- manifest: result.files,
255
- lastSync: new Date().toISOString(),
256
- });
257
- return result;
258
- });
259
- }
260
- // ── File Utilities ──────────────────────────────────────────────────────
261
- /**
262
- * Recursively walk a directory and return relative file paths,
263
- * excluding patterns that should not be synced.
264
- */
265
- function walkDirectory(baseDir) {
266
- const results = [];
267
- function walk(dir) {
268
- if (!fs_extra_1.default.existsSync(dir))
269
- return;
270
- const entries = fs_extra_1.default.readdirSync(dir, { withFileTypes: true });
271
- for (const entry of entries) {
272
- const fullPath = path_1.default.join(dir, entry.name);
273
- const relativePath = path_1.default.relative(baseDir, fullPath);
274
- // Check exclusions against the first path segment and the entry name
275
- if (isExcluded(relativePath))
276
- continue;
277
- if (entry.isDirectory()) {
278
- walk(fullPath);
279
- }
280
- else if (entry.isFile()) {
281
- // Use forward slashes for consistency with the server
282
- results.push(relativePath.split(path_1.default.sep).join('/'));
283
- }
284
- }
285
- }
286
- walk(baseDir);
287
- return results;
288
- }
289
- /**
290
- * Check if a relative path should be excluded from sync.
291
- */
292
- function isExcluded(relativePath) {
293
- const segments = relativePath.split(path_1.default.sep);
294
- for (const pattern of EXCLUDED_PATTERNS) {
295
- // Match if any segment starts with or equals the pattern
296
- if (segments.some((seg) => seg === pattern || seg.startsWith(pattern + '/'))) {
297
- return true;
298
- }
299
- // Also match the exact relative path
300
- if (relativePath === pattern)
301
- return true;
302
- }
303
- return false;
304
- }
305
- /**
306
- * Compute the MD5 hash of a file (to compare against S3 ETags).
307
- */
308
- function computeEtag(filePath) {
309
- const content = fs_extra_1.default.readFileSync(filePath);
310
- return crypto_1.default.createHash('md5').update(content).digest('hex');
311
- }
312
- /**
313
- * Normalize an ETag string by removing surrounding quotes.
314
- */
315
- function normalizeEtag(etag) {
316
- return etag.replace(/^"/, '').replace(/"$/, '');
317
- }
318
- /** Config file extensions to look for in component directories. */
319
- const COMPONENT_CONFIG_EXTENSIONS = ['.js', '.cjs', '.json'];
320
- /**
321
- * For a list of changed file paths, find any component config files that
322
- * live in the same directory so they are always uploaded alongside changes.
323
- *
324
- * A "component config" is a file whose basename (without extension) matches
325
- * the containing directory name, e.g. `components/button/button.js`.
326
- */
327
- function getComponentConfigsForChangedPaths(projectDir, changedPaths) {
328
- const extras = new Set();
329
- const checkedDirs = new Set();
330
- for (const relativePath of changedPaths) {
331
- // Get the directory portion using forward-slash splitting (paths are
332
- // normalized to forward slashes by walkDirectory)
333
- const dirPart = relativePath.includes('/') ? relativePath.substring(0, relativePath.lastIndexOf('/')) : '';
334
- if (!dirPart || checkedDirs.has(dirPart))
335
- continue;
336
- checkedDirs.add(dirPart);
337
- // The component config file is named after the directory
338
- const dirName = dirPart.includes('/') ? dirPart.substring(dirPart.lastIndexOf('/') + 1) : dirPart;
339
- for (const ext of COMPONENT_CONFIG_EXTENSIONS) {
340
- const configRelative = `${dirPart}/${dirName}${ext}`;
341
- const configAbsolute = path_1.default.join(projectDir, configRelative);
342
- if (fs_extra_1.default.existsSync(configAbsolute)) {
343
- extras.add(configRelative);
344
- break; // only need the first matching config
345
- }
346
- }
347
- }
348
- return Array.from(extras);
349
- }