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,228 +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
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.PlatformApiError = void 0;
13
- exports.createDeviceCode = createDeviceCode;
14
- exports.pollForToken = pollForToken;
15
- exports.revokeToken = revokeToken;
16
- exports.listProjects = listProjects;
17
- exports.getManifest = getManifest;
18
- exports.downloadFile = downloadFile;
19
- exports.pushChanges = pushChanges;
20
- exports.createPush = createPush;
21
- const logger_1 = require("../utils/logger");
22
- // ── Helpers ─────────────────────────────────────────────────────────────
23
- class PlatformApiError extends Error {
24
- constructor(statusCode, message) {
25
- super(message);
26
- this.statusCode = statusCode;
27
- this.name = 'PlatformApiError';
28
- }
29
- }
30
- exports.PlatformApiError = PlatformApiError;
31
- function authHeaders(token) {
32
- return {
33
- Authorization: `Bearer ${token}`,
34
- };
35
- }
36
- function handleResponse(res, context) {
37
- return __awaiter(this, void 0, void 0, function* () {
38
- if (!res.ok) {
39
- let message = `${context}: HTTP ${res.status}`;
40
- try {
41
- const body = yield res.json();
42
- if (body.error)
43
- message = `${context}: ${body.error}`;
44
- if (body.message)
45
- message = `${context}: ${body.message}`;
46
- }
47
- catch (_a) {
48
- // ignore parse errors
49
- }
50
- throw new PlatformApiError(res.status, message);
51
- }
52
- return res.json();
53
- });
54
- }
55
- // ── API Functions ───────────────────────────────────────────────────────
56
- /**
57
- * Initiate the device-code auth flow.
58
- */
59
- function createDeviceCode(baseUrl) {
60
- return __awaiter(this, void 0, void 0, function* () {
61
- const res = yield fetch(`${baseUrl}/api/cli/auth/device`, {
62
- method: 'POST',
63
- headers: { 'Content-Type': 'application/json' },
64
- });
65
- return handleResponse(res, 'Device code creation');
66
- });
67
- }
68
- /**
69
- * Poll for an approved token. Resolves when approved or rejects on
70
- * expiry / error.
71
- */
72
- function pollForToken(baseUrl, deviceCode, interval, expiresIn) {
73
- return __awaiter(this, void 0, void 0, function* () {
74
- const deadline = Date.now() + expiresIn * 1000;
75
- while (Date.now() < deadline) {
76
- yield sleep(interval * 1000);
77
- const res = yield fetch(`${baseUrl}/api/cli/auth/token`, {
78
- method: 'POST',
79
- headers: { 'Content-Type': 'application/json' },
80
- body: JSON.stringify({ deviceCode }),
81
- });
82
- if (res.status === 410) {
83
- throw new PlatformApiError(410, 'Device code expired. Please try logging in again.');
84
- }
85
- if (res.status === 404) {
86
- throw new PlatformApiError(404, 'Invalid device code.');
87
- }
88
- const body = (yield res.json());
89
- if (body.status === 'approved') {
90
- return body;
91
- }
92
- logger_1.Logger.debug('Token poll: still pending...');
93
- }
94
- throw new PlatformApiError(408, 'Authentication timed out. Please try again.');
95
- });
96
- }
97
- /**
98
- * Revoke (logout) the current CLI token.
99
- */
100
- function revokeToken(baseUrl, token) {
101
- return __awaiter(this, void 0, void 0, function* () {
102
- const res = yield fetch(`${baseUrl}/api/cli/auth/token`, {
103
- method: 'DELETE',
104
- headers: authHeaders(token),
105
- });
106
- if (!res.ok && res.status !== 401) {
107
- yield handleResponse(res, 'Token revocation');
108
- }
109
- });
110
- }
111
- /**
112
- * List all projects accessible to the authenticated user.
113
- */
114
- function listProjects(baseUrl, token) {
115
- return __awaiter(this, void 0, void 0, function* () {
116
- const res = yield fetch(`${baseUrl}/api/cli/projects`, {
117
- headers: authHeaders(token),
118
- });
119
- return handleResponse(res, 'List projects');
120
- });
121
- }
122
- /**
123
- * Get the file manifest for a project.
124
- */
125
- function getManifest(baseUrl, token, projectId) {
126
- return __awaiter(this, void 0, void 0, function* () {
127
- const res = yield fetch(`${baseUrl}/api/cli/projects/${projectId}/sync/manifest`, {
128
- headers: authHeaders(token),
129
- });
130
- return handleResponse(res, 'Get manifest');
131
- });
132
- }
133
- /**
134
- * Download a single file from the project by relative path.
135
- * Returns the file content as a Buffer.
136
- */
137
- function downloadFile(baseUrl, token, projectId, filePath) {
138
- return __awaiter(this, void 0, void 0, function* () {
139
- const url = `${baseUrl}/api/cli/projects/${projectId}/sync/file?path=${encodeURIComponent(filePath)}`;
140
- const res = yield fetch(url, {
141
- headers: authHeaders(token),
142
- });
143
- if (!res.ok) {
144
- let message = `Download file "${filePath}": HTTP ${res.status}`;
145
- try {
146
- const body = yield res.json();
147
- if (body.error)
148
- message = `Download file "${filePath}": ${body.error}`;
149
- }
150
- catch (_a) {
151
- // response might not be JSON
152
- }
153
- throw new PlatformApiError(res.status, message);
154
- }
155
- const arrayBuffer = yield res.arrayBuffer();
156
- return Buffer.from(arrayBuffer);
157
- });
158
- }
159
- /**
160
- * Push file changes to the project. Uses multipart/form-data.
161
- *
162
- * @param files Map of relative path -> file Buffer
163
- * @param deleted Array of relative paths to delete
164
- * @returns PushResult on success
165
- * @throws PlatformApiError with status 409 on version conflict
166
- */
167
- function pushChanges(baseUrl, token, projectId, baseVersion, files, deleted) {
168
- return __awaiter(this, void 0, void 0, function* () {
169
- // Use the built-in FormData (Node 18+) — the `form-data` npm package
170
- // is stream-based and incompatible with native fetch.
171
- const form = new FormData();
172
- form.append('baseVersion', String(baseVersion));
173
- if (deleted.length > 0) {
174
- form.append('deleted', JSON.stringify(deleted));
175
- }
176
- for (const [filePath, content] of files) {
177
- const blob = new Blob([new Uint8Array(content)]);
178
- form.append(filePath, blob, filePath);
179
- }
180
- const res = yield fetch(`${baseUrl}/api/cli/projects/${projectId}/sync/push`, {
181
- method: 'POST',
182
- headers: Object.assign({}, authHeaders(token)),
183
- body: form,
184
- });
185
- if (res.status === 409) {
186
- const body = (yield res.json());
187
- const err = new PlatformApiError(409, body.message || 'Version conflict — pull first');
188
- err.conflictData = body;
189
- throw err;
190
- }
191
- return handleResponse(res, 'Push changes');
192
- });
193
- }
194
- /**
195
- * Create a new project and push all files in a single request.
196
- * Used for onboarding existing handoff-app projects to the platform.
197
- *
198
- * @param orgId Organization to create the project in
199
- * @param name Optional project name (falls back to app.title in config)
200
- * @param figmaProjectId Optional Figma file ID
201
- * @param files Map of relative path -> file Buffer
202
- */
203
- function createPush(baseUrl_1, token_1, orgId_1, files_1) {
204
- return __awaiter(this, arguments, void 0, function* (baseUrl, token, orgId, files, options = {}) {
205
- const form = new FormData();
206
- form.append('orgId', orgId);
207
- if (options.name) {
208
- form.append('name', options.name);
209
- }
210
- if (options.figmaProjectId) {
211
- form.append('figmaProjectId', options.figmaProjectId);
212
- }
213
- for (const [filePath, content] of files) {
214
- const blob = new Blob([new Uint8Array(content)]);
215
- form.append(filePath, blob, filePath);
216
- }
217
- const res = yield fetch(`${baseUrl}/api/cli/projects/create-push`, {
218
- method: 'POST',
219
- headers: Object.assign({}, authHeaders(token)),
220
- body: form,
221
- });
222
- return handleResponse(res, 'Create and push project');
223
- });
224
- }
225
- // ── Utilities ───────────────────────────────────────────────────────────
226
- function sleep(ms) {
227
- return new Promise((resolve) => setTimeout(resolve, ms));
228
- }
@@ -1,40 +0,0 @@
1
- export interface CredentialEntry {
2
- token: string;
3
- user: {
4
- name: string | null;
5
- email: string;
6
- };
7
- createdAt: string;
8
- }
9
- /** Keyed by platform base URL */
10
- export type CredentialsStore = Record<string, CredentialEntry>;
11
- /**
12
- * Load the full credentials store from disk.
13
- * Returns an empty object if the file doesn't exist yet.
14
- */
15
- export declare function loadCredentials(): CredentialsStore;
16
- /**
17
- * Save the full credentials store to disk.
18
- * Creates the ~/.handoff directory if it doesn't exist.
19
- */
20
- export declare function saveCredentials(creds: CredentialsStore): void;
21
- /**
22
- * Retrieve the token for a specific platform URL.
23
- * Returns null if not logged in to that URL.
24
- */
25
- export declare function getToken(baseUrl: string): string | null;
26
- /**
27
- * Retrieve the full credential entry for a specific platform URL.
28
- */
29
- export declare function getCredential(baseUrl: string): CredentialEntry | null;
30
- /**
31
- * Store a token and user info for a platform URL.
32
- */
33
- export declare function setToken(baseUrl: string, token: string, user: {
34
- name: string | null;
35
- email: string;
36
- }): void;
37
- /**
38
- * Remove the credential for a platform URL.
39
- */
40
- export declare function removeToken(baseUrl: string): void;
@@ -1,91 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.loadCredentials = loadCredentials;
7
- exports.saveCredentials = saveCredentials;
8
- exports.getToken = getToken;
9
- exports.getCredential = getCredential;
10
- exports.setToken = setToken;
11
- exports.removeToken = removeToken;
12
- const fs_extra_1 = __importDefault(require("fs-extra"));
13
- const os_1 = __importDefault(require("os"));
14
- const path_1 = __importDefault(require("path"));
15
- const logger_1 = require("../utils/logger");
16
- // ── Paths ───────────────────────────────────────────────────────────────
17
- const HANDOFF_DIR = path_1.default.join(os_1.default.homedir(), '.handoff');
18
- const CREDENTIALS_FILE = path_1.default.join(HANDOFF_DIR, 'credentials.json');
19
- // ── Core Functions ──────────────────────────────────────────────────────
20
- /**
21
- * Load the full credentials store from disk.
22
- * Returns an empty object if the file doesn't exist yet.
23
- */
24
- function loadCredentials() {
25
- try {
26
- if (fs_extra_1.default.existsSync(CREDENTIALS_FILE)) {
27
- return fs_extra_1.default.readJsonSync(CREDENTIALS_FILE);
28
- }
29
- }
30
- catch (err) {
31
- logger_1.Logger.warn('Could not read credentials file, starting fresh.');
32
- logger_1.Logger.debug('Credentials read error', err);
33
- }
34
- return {};
35
- }
36
- /**
37
- * Save the full credentials store to disk.
38
- * Creates the ~/.handoff directory if it doesn't exist.
39
- */
40
- function saveCredentials(creds) {
41
- fs_extra_1.default.ensureDirSync(HANDOFF_DIR);
42
- fs_extra_1.default.writeJsonSync(CREDENTIALS_FILE, creds, { spaces: 2, mode: 0o600 });
43
- }
44
- /**
45
- * Retrieve the token for a specific platform URL.
46
- * Returns null if not logged in to that URL.
47
- */
48
- function getToken(baseUrl) {
49
- var _a, _b;
50
- const creds = loadCredentials();
51
- const normalized = normalizeUrl(baseUrl);
52
- return (_b = (_a = creds[normalized]) === null || _a === void 0 ? void 0 : _a.token) !== null && _b !== void 0 ? _b : null;
53
- }
54
- /**
55
- * Retrieve the full credential entry for a specific platform URL.
56
- */
57
- function getCredential(baseUrl) {
58
- var _a;
59
- const creds = loadCredentials();
60
- const normalized = normalizeUrl(baseUrl);
61
- return (_a = creds[normalized]) !== null && _a !== void 0 ? _a : null;
62
- }
63
- /**
64
- * Store a token and user info for a platform URL.
65
- */
66
- function setToken(baseUrl, token, user) {
67
- const creds = loadCredentials();
68
- const normalized = normalizeUrl(baseUrl);
69
- creds[normalized] = {
70
- token,
71
- user,
72
- createdAt: new Date().toISOString(),
73
- };
74
- saveCredentials(creds);
75
- }
76
- /**
77
- * Remove the credential for a platform URL.
78
- */
79
- function removeToken(baseUrl) {
80
- const creds = loadCredentials();
81
- const normalized = normalizeUrl(baseUrl);
82
- delete creds[normalized];
83
- saveCredentials(creds);
84
- }
85
- // ── Helpers ─────────────────────────────────────────────────────────────
86
- /**
87
- * Normalize a URL by removing trailing slashes for consistent key lookup.
88
- */
89
- function normalizeUrl(url) {
90
- return url.replace(/\/+$/, '');
91
- }
@@ -1,68 +0,0 @@
1
- import { CreatePushResult, ManifestFile, PushResult } from './client';
2
- export interface SyncState {
3
- version: number;
4
- manifest: Record<string, ManifestFile>;
5
- lastSync: string;
6
- }
7
- export interface PullResult {
8
- downloaded: string[];
9
- deleted: string[];
10
- unchanged: string[];
11
- version: number;
12
- }
13
- export interface PushDiff {
14
- added: string[];
15
- modified: string[];
16
- deleted: string[];
17
- }
18
- /**
19
- * Load the sync state from the project directory.
20
- * Returns null if no sync state exists (first sync).
21
- */
22
- export declare function loadSyncState(projectDir: string): SyncState | null;
23
- /**
24
- * Save the sync state to the project directory.
25
- */
26
- export declare function saveSyncState(projectDir: string, state: SyncState): void;
27
- /**
28
- * Pull files from the remote project to the local directory.
29
- *
30
- * Compares the remote manifest against local files (using MD5 hashes)
31
- * and downloads any files that differ or are missing locally.
32
- *
33
- * @param onProgress Optional callback for reporting progress
34
- */
35
- export declare function pullProject(baseUrl: string, token: string, projectId: string, projectDir: string, options?: {
36
- force?: boolean;
37
- onProgress?: (message: string) => void;
38
- }): Promise<PullResult>;
39
- /**
40
- * Compute the diff between local files and the last-known manifest.
41
- */
42
- export declare function computePushDiff(projectDir: string): PushDiff;
43
- /**
44
- * Push local changes to the remote project.
45
- *
46
- * @param onProgress Optional callback for reporting progress
47
- */
48
- export declare function pushProject(baseUrl: string, token: string, projectId: string, projectDir: string, options?: {
49
- force?: boolean;
50
- onProgress?: (message: string) => void;
51
- }): Promise<PushResult>;
52
- /**
53
- * Push an entire local project to the platform as a new project.
54
- * Collects all syncable local files and sends them via the create-push
55
- * endpoint, which creates the project and uploads files in one request.
56
- *
57
- * @param onProgress Optional callback for reporting progress
58
- */
59
- export declare function createPushProject(baseUrl: string, token: string, orgId: string, projectDir: string, options?: {
60
- name?: string;
61
- figmaProjectId?: string;
62
- onProgress?: (message: string) => void;
63
- }): Promise<CreatePushResult>;
64
- /**
65
- * Recursively walk a directory and return relative file paths,
66
- * excluding patterns that should not be synced.
67
- */
68
- export declare function walkDirectory(baseDir: string): string[];