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,428 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
- return new (P || (P = Promise))(function (resolve, reject) {
28
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
- step((generator = generator.apply(thisArg, _arguments || [])).next());
32
- });
33
- };
34
- var __importDefault = (this && this.__importDefault) || function (mod) {
35
- return (mod && mod.__esModule) ? mod : { "default": mod };
36
- };
37
- Object.defineProperty(exports, "__esModule", { value: true });
38
- const p = __importStar(require("@clack/prompts"));
39
- const chalk_1 = __importDefault(require("chalk"));
40
- const fs_extra_1 = __importDefault(require("fs-extra"));
41
- const path_1 = __importDefault(require("path"));
42
- const __1 = __importDefault(require("../../"));
43
- const client_1 = require("../../platform/client");
44
- const credentials_1 = require("../../platform/credentials");
45
- const sync_1 = require("../../platform/sync");
46
- const DEFAULT_PLATFORM_URL = 'http://localhost:3000';
47
- const CONFIG_FILES = ['handoff.config.json', 'handoff.config.js', 'handoff.config.cjs'];
48
- const command = {
49
- command: 'platform:push',
50
- describe: 'Push local changes to the Handoff platform',
51
- builder: (yargs) => {
52
- return yargs
53
- .option('url', {
54
- type: 'string',
55
- description: 'Platform URL (overrides config)',
56
- })
57
- .option('force', {
58
- alias: 'f',
59
- type: 'boolean',
60
- description: 'Push without confirmation prompt',
61
- default: false,
62
- })
63
- .option('debug', {
64
- alias: 'd',
65
- type: 'boolean',
66
- description: 'Enable debug mode',
67
- });
68
- },
69
- handler: (args) => __awaiter(void 0, void 0, void 0, function* () {
70
- const handoff = new __1.default(args.debug, args.force);
71
- const config = handoff.config;
72
- const baseUrl = (args.url || (config === null || config === void 0 ? void 0 : config.platformUrl) || process.env.HANDOFF_PLATFORM_URL || DEFAULT_PLATFORM_URL).replace(/\/+$/, '');
73
- const projectId = config === null || config === void 0 ? void 0 : config.platformProjectId;
74
- p.intro(chalk_1.default.bold('Handoff Platform Push'));
75
- // Check login first — needed for both flows
76
- const token = (0, credentials_1.getToken)(baseUrl);
77
- if (!token) {
78
- p.cancel(`Not logged in to ${baseUrl}. Run ${chalk_1.default.cyan('handoff-app platform:login')} first.`);
79
- process.exit(1);
80
- }
81
- // ── No project linked → offer to create a new one ──────────────────
82
- if (!projectId) {
83
- yield handleCreatePush(handoff, baseUrl, token, args.force);
84
- return;
85
- }
86
- // ── Normal push to existing project ────────────────────────────────
87
- yield handleExistingPush(handoff, baseUrl, token, projectId, args.force);
88
- }),
89
- };
90
- // ─────────────────────────────────────────────────────────────────────────
91
- // Create-push flow: push a legacy project as a new platform project
92
- // ─────────────────────────────────────────────────────────────────────────
93
- function handleCreatePush(handoff, baseUrl, token, force) {
94
- return __awaiter(this, void 0, void 0, function* () {
95
- var _a, _b;
96
- p.note([
97
- 'This project is not linked to a platform project.',
98
- '',
99
- 'You can create a new project on the platform and push',
100
- 'your local files up in a single step.',
101
- ].join('\n'), 'New Project');
102
- const wantCreate = yield p.confirm({
103
- message: 'Would you like to create a new project on the platform?',
104
- });
105
- if (p.isCancel(wantCreate) || !wantCreate) {
106
- p.cancel(`Run ${chalk_1.default.cyan('handoff-app platform:init')} to link to an existing project instead.`);
107
- process.exit(0);
108
- }
109
- const spinner = p.spinner();
110
- // Fetch the project list to get available organizations
111
- spinner.start('Loading organizations...');
112
- let projects;
113
- try {
114
- projects = yield (0, client_1.listProjects)(baseUrl, token);
115
- }
116
- catch (err) {
117
- spinner.stop('Failed to load organizations.');
118
- p.cancel(err.message || 'Could not fetch project list');
119
- process.exit(1);
120
- }
121
- spinner.stop('Organizations loaded.');
122
- // Extract unique organizations the user has access to
123
- const orgMap = new Map();
124
- for (const proj of projects) {
125
- if (!orgMap.has(proj.orgId)) {
126
- orgMap.set(proj.orgId, { id: proj.orgId, name: proj.orgName, slug: proj.orgSlug });
127
- }
128
- }
129
- // If no orgs available at all, the user might still have org access even with 0 projects.
130
- // The listProjects endpoint returns projects — if there are none, we still have the token.
131
- // We'll ask the user to type an org ID, or if we found orgs, let them pick.
132
- let orgId;
133
- if (orgMap.size > 0) {
134
- const orgs = Array.from(orgMap.values());
135
- if (orgs.length === 1) {
136
- orgId = orgs[0].id;
137
- p.log.info(`Organization: ${chalk_1.default.bold(orgs[0].name)}`);
138
- }
139
- else {
140
- const selectedOrg = yield p.select({
141
- message: 'Which organization should own this project?',
142
- options: orgs.map((org) => ({
143
- value: org.id,
144
- label: org.name,
145
- hint: org.slug,
146
- })),
147
- });
148
- if (p.isCancel(selectedOrg)) {
149
- p.cancel('Push cancelled.');
150
- process.exit(0);
151
- }
152
- orgId = selectedOrg;
153
- }
154
- }
155
- else {
156
- // No projects found → we can't infer orgs. Ask the user.
157
- const orgInput = yield p.text({
158
- message: 'Enter the organization ID to create the project in:',
159
- placeholder: 'uuid',
160
- validate: (val) => {
161
- if (!val || val.trim().length === 0)
162
- return 'Organization ID is required';
163
- return undefined;
164
- },
165
- });
166
- if (p.isCancel(orgInput)) {
167
- p.cancel('Push cancelled.');
168
- process.exit(0);
169
- }
170
- orgId = orgInput.trim();
171
- }
172
- // Collect project name
173
- const defaultName = ((_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.app) === null || _b === void 0 ? void 0 : _b.title) || path_1.default.basename(handoff.workingPath);
174
- const projectName = yield p.text({
175
- message: 'Project name:',
176
- placeholder: defaultName,
177
- defaultValue: defaultName,
178
- validate: (val) => {
179
- if (!val || val.trim().length === 0)
180
- return 'Project name is required';
181
- return undefined;
182
- },
183
- });
184
- if (p.isCancel(projectName)) {
185
- p.cancel('Push cancelled.');
186
- process.exit(0);
187
- }
188
- // Optionally collect Figma project ID
189
- const wantFigma = yield p.confirm({
190
- message: 'Do you want to connect a Figma file? (can be added later)',
191
- initialValue: false,
192
- });
193
- let figmaProjectId;
194
- if (!p.isCancel(wantFigma) && wantFigma) {
195
- const figmaInput = yield p.text({
196
- message: 'Figma file ID:',
197
- placeholder: 'e.g. abc123XYZ',
198
- validate: (val) => {
199
- if (!val || val.trim().length === 0)
200
- return 'Figma file ID is required';
201
- return undefined;
202
- },
203
- });
204
- if (p.isCancel(figmaInput)) {
205
- p.cancel('Push cancelled.');
206
- process.exit(0);
207
- }
208
- figmaProjectId = figmaInput.trim();
209
- }
210
- // Confirm before proceeding
211
- if (!force) {
212
- const confirmed = yield p.confirm({
213
- message: `Create project "${projectName}" and push all local files?`,
214
- });
215
- if (p.isCancel(confirmed) || !confirmed) {
216
- p.cancel('Push cancelled.');
217
- process.exit(0);
218
- }
219
- }
220
- // Execute create-push
221
- try {
222
- spinner.start('Creating project and uploading files...');
223
- const result = yield (0, sync_1.createPushProject)(baseUrl, token, orgId, handoff.workingPath, {
224
- name: projectName.trim(),
225
- figmaProjectId,
226
- onProgress: (msg) => spinner.message(msg),
227
- });
228
- spinner.stop('Project created and files uploaded!');
229
- // Update the local config with the new project ID and platform URL
230
- yield updateLocalConfig(handoff.workingPath, result.project.id, baseUrl);
231
- // Separate excluded-path warnings from real errors
232
- const excludedErrors = result.errors.filter((e) => /excluded\s*path/i.test(e));
233
- const realErrors = result.errors.filter((e) => !/excluded\s*path/i.test(e));
234
- const summary = [
235
- `Project: ${chalk_1.default.bold(result.project.name)}`,
236
- `Project ID: ${chalk_1.default.dim(result.project.id)}`,
237
- `Files uploaded: ${chalk_1.default.green(String(result.uploaded))}`,
238
- `Sync version: ${chalk_1.default.cyan(String(result.syncVersion))}`,
239
- ];
240
- if (excludedErrors.length > 0) {
241
- summary.push(chalk_1.default.yellow(`${excludedErrors.length} path(s) not synced (excluded by server)`));
242
- }
243
- if (realErrors.length > 0) {
244
- summary.push(`Errors: ${chalk_1.default.red(String(realErrors.length))}`);
245
- for (const err of realErrors) {
246
- summary.push(chalk_1.default.red(` ! ${err}`));
247
- }
248
- }
249
- p.note(summary.join('\n'), 'Create-Push Summary');
250
- p.outro(`Your local config has been updated. Use ${chalk_1.default.cyan('platform:push')} for future syncs.`);
251
- }
252
- catch (err) {
253
- spinner.stop('Create-push failed.');
254
- p.cancel(err.message || 'Failed to create project');
255
- process.exit(1);
256
- }
257
- });
258
- }
259
- // ─────────────────────────────────────────────────────────────────────────
260
- // Normal push flow: push changes to an existing linked project
261
- // ─────────────────────────────────────────────────────────────────────────
262
- function handleExistingPush(handoff, baseUrl, token, projectId, force) {
263
- return __awaiter(this, void 0, void 0, function* () {
264
- // Preview what will be pushed
265
- const diff = (0, sync_1.computePushDiff)(handoff.workingPath);
266
- if (diff.added.length === 0 && diff.modified.length === 0 && diff.deleted.length === 0) {
267
- p.note('No changes detected.', 'Push');
268
- p.outro('Everything is up to date.');
269
- return;
270
- }
271
- // Show the changes summary
272
- const changeLines = [];
273
- if (diff.added.length > 0) {
274
- changeLines.push(chalk_1.default.green(` + ${diff.added.length} new file(s)`));
275
- for (const f of diff.added.slice(0, 10)) {
276
- changeLines.push(chalk_1.default.dim(` + ${f}`));
277
- }
278
- if (diff.added.length > 10) {
279
- changeLines.push(chalk_1.default.dim(` ... and ${diff.added.length - 10} more`));
280
- }
281
- }
282
- if (diff.modified.length > 0) {
283
- changeLines.push(chalk_1.default.yellow(` ~ ${diff.modified.length} modified file(s)`));
284
- for (const f of diff.modified.slice(0, 10)) {
285
- changeLines.push(chalk_1.default.dim(` ~ ${f}`));
286
- }
287
- if (diff.modified.length > 10) {
288
- changeLines.push(chalk_1.default.dim(` ... and ${diff.modified.length - 10} more`));
289
- }
290
- }
291
- if (diff.deleted.length > 0) {
292
- changeLines.push(chalk_1.default.red(` - ${diff.deleted.length} deleted file(s)`));
293
- for (const f of diff.deleted.slice(0, 10)) {
294
- changeLines.push(chalk_1.default.dim(` - ${f}`));
295
- }
296
- if (diff.deleted.length > 10) {
297
- changeLines.push(chalk_1.default.dim(` ... and ${diff.deleted.length - 10} more`));
298
- }
299
- }
300
- p.note(changeLines.join('\n'), 'Changes to push');
301
- // Confirm unless --force
302
- if (!force) {
303
- const confirmed = yield p.confirm({
304
- message: 'Push these changes?',
305
- });
306
- if (p.isCancel(confirmed) || !confirmed) {
307
- p.cancel('Push cancelled.');
308
- process.exit(0);
309
- }
310
- }
311
- const spinner = p.spinner();
312
- try {
313
- spinner.start('Pushing changes...');
314
- const result = yield (0, sync_1.pushProject)(baseUrl, token, projectId, handoff.workingPath, {
315
- force,
316
- onProgress: (msg) => spinner.message(msg),
317
- });
318
- spinner.stop('Push complete.');
319
- // Separate excluded-path warnings from real errors
320
- const excludedErrors = result.errors.filter((e) => /excluded\s*path/i.test(e));
321
- const realErrors = result.errors.filter((e) => !/excluded\s*path/i.test(e));
322
- const summary = [
323
- `Uploaded: ${chalk_1.default.green(String(result.uploaded.length))} file(s)`,
324
- `Deleted: ${chalk_1.default.red(String(result.deleted.length))} file(s)`,
325
- `Sync version: ${chalk_1.default.cyan(String(result.version))}`,
326
- ];
327
- if (excludedErrors.length > 0) {
328
- summary.push(chalk_1.default.yellow(`${excludedErrors.length} path(s) not synced (excluded by server)`));
329
- }
330
- if (realErrors.length > 0) {
331
- summary.push(`Errors: ${chalk_1.default.red(String(realErrors.length))}`);
332
- for (const err of realErrors) {
333
- summary.push(chalk_1.default.red(` ! ${err}`));
334
- }
335
- }
336
- p.note(summary.join('\n'), 'Push Summary');
337
- p.outro('Remote project updated.');
338
- }
339
- catch (err) {
340
- spinner.stop('Push failed.');
341
- if (err instanceof client_1.PlatformApiError && err.statusCode === 409) {
342
- p.cancel([
343
- 'Version conflict! The remote project has changed since your last pull.',
344
- `Run ${chalk_1.default.cyan('handoff-app platform:pull')} first, then try pushing again.`,
345
- ].join('\n'));
346
- }
347
- else {
348
- p.cancel(err.message || 'Push failed');
349
- }
350
- process.exit(1);
351
- }
352
- });
353
- }
354
- // ─────────────────────────────────────────────────────────────────────────
355
- // Config helpers
356
- // ─────────────────────────────────────────────────────────────────────────
357
- /**
358
- * Update the local handoff config file with platformProjectId and platformUrl
359
- * after a successful create-push. Reuses the same injection logic as platform:init.
360
- */
361
- function updateLocalConfig(workingDir, projectId, platformUrl) {
362
- return __awaiter(this, void 0, void 0, function* () {
363
- const existingConfigFile = CONFIG_FILES.find((file) => fs_extra_1.default.existsSync(path_1.default.resolve(workingDir, file)));
364
- if (existingConfigFile) {
365
- const configPath = path_1.default.resolve(workingDir, existingConfigFile);
366
- const ext = path_1.default.extname(configPath);
367
- if (ext === '.json') {
368
- const config = fs_extra_1.default.readJsonSync(configPath);
369
- config.platformProjectId = projectId;
370
- config.platformUrl = platformUrl;
371
- fs_extra_1.default.writeJsonSync(configPath, config, { spaces: 2 });
372
- }
373
- else {
374
- let content = fs_extra_1.default.readFileSync(configPath, 'utf8');
375
- content = upsertConfigField(content, 'platformProjectId', JSON.stringify(projectId));
376
- content = upsertConfigField(content, 'platformUrl', JSON.stringify(platformUrl));
377
- fs_extra_1.default.writeFileSync(configPath, content, 'utf8');
378
- }
379
- }
380
- else {
381
- // Create a minimal config
382
- const configPath = path_1.default.resolve(workingDir, 'handoff.config.js');
383
- fs_extra_1.default.writeFileSync(configPath, `module.exports = {\n platformProjectId: ${JSON.stringify(projectId)},\n platformUrl: ${JSON.stringify(platformUrl)},\n};\n`);
384
- }
385
- });
386
- }
387
- /**
388
- * Insert or replace a top-level field in a JS config export.
389
- */
390
- function upsertConfigField(source, fieldName, value) {
391
- const existingPattern = new RegExp(`(${fieldName}\\s*:\\s*)([^,}\\n]+)(,?)`);
392
- if (existingPattern.test(source)) {
393
- return source.replace(existingPattern, `$1${value}$3`);
394
- }
395
- const closingBrace = /(\n)(};?\s*$)/;
396
- if (closingBrace.test(source)) {
397
- source = ensureTrailingComma(source);
398
- return source.replace(closingBrace, `$1 ${fieldName}: ${value},\n$2`);
399
- }
400
- return source + `\n// Added by platform:push — could not auto-inject:\n// ${fieldName}: ${value}\n`;
401
- }
402
- /**
403
- * Ensure the last property line before the closing brace has a trailing comma.
404
- */
405
- function ensureTrailingComma(source) {
406
- const lines = source.split('\n');
407
- let closingIdx = -1;
408
- for (let i = lines.length - 1; i >= 0; i--) {
409
- if (/^};?\s*$/.test(lines[i])) {
410
- closingIdx = i;
411
- break;
412
- }
413
- }
414
- if (closingIdx <= 0)
415
- return source;
416
- let lastPropIdx = closingIdx - 1;
417
- while (lastPropIdx >= 0 && (/^\s*$/.test(lines[lastPropIdx]) || /^\s*\/\//.test(lines[lastPropIdx]))) {
418
- lastPropIdx--;
419
- }
420
- if (lastPropIdx < 0)
421
- return source;
422
- const stripped = lines[lastPropIdx].replace(/\s*\/\/.*$/, '').trimEnd();
423
- if (stripped.length > 0 && !stripped.endsWith(',') && !stripped.endsWith('{')) {
424
- lines[lastPropIdx] = lines[lastPropIdx].replace(/(\S)(\s*(?:\/\/.*)?)$/, '$1,$2');
425
- }
426
- return lines.join('\n');
427
- }
428
- exports.default = command;
@@ -1,122 +0,0 @@
1
- export interface DeviceCodeResponse {
2
- deviceCode: string;
3
- userCode: string;
4
- verificationUrl: string;
5
- expiresIn: number;
6
- interval: number;
7
- }
8
- export interface TokenPollPending {
9
- status: 'pending';
10
- }
11
- export interface TokenPollApproved {
12
- status: 'approved';
13
- token: string;
14
- user: {
15
- id: string;
16
- name: string | null;
17
- email: string;
18
- image: string | null;
19
- };
20
- }
21
- export type TokenPollResponse = TokenPollPending | TokenPollApproved;
22
- export interface CliProject {
23
- id: string;
24
- name: string;
25
- slug: string;
26
- orgId: string;
27
- orgName: string;
28
- orgSlug: string;
29
- role: string;
30
- syncVersion: number;
31
- }
32
- export interface ManifestFile {
33
- etag: string;
34
- size: number;
35
- lastModified: string;
36
- }
37
- export interface SyncManifest {
38
- projectId: string;
39
- version: number;
40
- prefix: string;
41
- files: Record<string, ManifestFile>;
42
- }
43
- export interface PushResult {
44
- success: boolean;
45
- version: number;
46
- uploaded: string[];
47
- deleted: string[];
48
- errors: string[];
49
- files: Record<string, ManifestFile>;
50
- }
51
- export interface ConflictResponse {
52
- error: 'conflict';
53
- message: string;
54
- currentVersion: number;
55
- files: Record<string, ManifestFile>;
56
- }
57
- export interface CreatePushResult {
58
- project: {
59
- id: string;
60
- name: string;
61
- slug: string;
62
- orgId: string;
63
- [key: string]: unknown;
64
- };
65
- syncVersion: number;
66
- uploaded: number;
67
- errors: string[];
68
- files: Record<string, ManifestFile>;
69
- }
70
- declare class PlatformApiError extends Error {
71
- statusCode: number;
72
- constructor(statusCode: number, message: string);
73
- }
74
- /**
75
- * Initiate the device-code auth flow.
76
- */
77
- export declare function createDeviceCode(baseUrl: string): Promise<DeviceCodeResponse>;
78
- /**
79
- * Poll for an approved token. Resolves when approved or rejects on
80
- * expiry / error.
81
- */
82
- export declare function pollForToken(baseUrl: string, deviceCode: string, interval: number, expiresIn: number): Promise<TokenPollApproved>;
83
- /**
84
- * Revoke (logout) the current CLI token.
85
- */
86
- export declare function revokeToken(baseUrl: string, token: string): Promise<void>;
87
- /**
88
- * List all projects accessible to the authenticated user.
89
- */
90
- export declare function listProjects(baseUrl: string, token: string): Promise<CliProject[]>;
91
- /**
92
- * Get the file manifest for a project.
93
- */
94
- export declare function getManifest(baseUrl: string, token: string, projectId: string): Promise<SyncManifest>;
95
- /**
96
- * Download a single file from the project by relative path.
97
- * Returns the file content as a Buffer.
98
- */
99
- export declare function downloadFile(baseUrl: string, token: string, projectId: string, filePath: string): Promise<Buffer>;
100
- /**
101
- * Push file changes to the project. Uses multipart/form-data.
102
- *
103
- * @param files Map of relative path -> file Buffer
104
- * @param deleted Array of relative paths to delete
105
- * @returns PushResult on success
106
- * @throws PlatformApiError with status 409 on version conflict
107
- */
108
- export declare function pushChanges(baseUrl: string, token: string, projectId: string, baseVersion: number, files: Map<string, Buffer>, deleted: string[]): Promise<PushResult>;
109
- /**
110
- * Create a new project and push all files in a single request.
111
- * Used for onboarding existing handoff-app projects to the platform.
112
- *
113
- * @param orgId Organization to create the project in
114
- * @param name Optional project name (falls back to app.title in config)
115
- * @param figmaProjectId Optional Figma file ID
116
- * @param files Map of relative path -> file Buffer
117
- */
118
- export declare function createPush(baseUrl: string, token: string, orgId: string, files: Map<string, Buffer>, options?: {
119
- name?: string;
120
- figmaProjectId?: string;
121
- }): Promise<CreatePushResult>;
122
- export { PlatformApiError };