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
|
@@ -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 };
|