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.
- package/Changelog.md +10 -5
- package/dist/cli.js +0 -0
- package/dist/index.js +4 -1
- package/dist/transformers/plugins/ssr-render.js +4 -3
- package/dist/transformers/preview/component/api.js +3 -2
- package/dist/transformers/preview/component/builder.js +2 -1
- package/dist/transformers/utils/handlebars.js +3 -2
- package/package.json +1 -1
- package/src/app/components/Component/Preview.tsx +121 -121
- package/src/app/components/ComponentSearch.tsx +1 -1
- package/src/app/components/util/index.ts +17 -9
- package/src/app/pages/system/component/[component]/index.tsx +1 -1
- package/src/app/pages/system/index.tsx +2 -2
- package/src/app/pages/system/tokens/index.tsx +10 -10
- package/src/index.ts +3 -0
- package/src/transformers/plugins/ssr-render.ts +3 -3
- package/src/transformers/preview/component/api.ts +1 -1
- package/src/transformers/preview/component/builder.ts +1 -1
- package/src/transformers/utils/handlebars.ts +2 -2
- package/dist/commands/platform/init.d.ts +0 -6
- package/dist/commands/platform/init.js +0 -231
- package/dist/commands/platform/login.d.ts +0 -6
- package/dist/commands/platform/login.js +0 -108
- package/dist/commands/platform/logout.d.ts +0 -6
- package/dist/commands/platform/logout.js +0 -88
- package/dist/commands/platform/pull.d.ts +0 -8
- package/dist/commands/platform/pull.js +0 -112
- package/dist/commands/platform/push.d.ts +0 -8
- package/dist/commands/platform/push.js +0 -428
- package/dist/platform/client.d.ts +0 -122
- package/dist/platform/client.js +0 -228
- package/dist/platform/credentials.d.ts +0 -40
- package/dist/platform/credentials.js +0 -91
- package/dist/platform/sync.d.ts +0 -68
- package/dist/platform/sync.js +0 -349
package/dist/platform/sync.js
DELETED
|
@@ -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
|
-
}
|