@figma/code-connect 1.4.3 → 1.4.5
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/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +61 -10
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/connect_template.d.ts +36 -0
- package/dist/commands/connect_template.d.ts.map +1 -0
- package/dist/commands/connect_template.js +324 -0
- package/dist/commands/connect_template.js.map +1 -0
- package/dist/commands/preview_utils.d.ts +49 -0
- package/dist/commands/preview_utils.d.ts.map +1 -0
- package/dist/commands/preview_utils.js +451 -0
- package/dist/commands/preview_utils.js.map +1 -0
- package/dist/connect/batch_templates.d.ts +11 -0
- package/dist/connect/batch_templates.d.ts.map +1 -0
- package/dist/connect/batch_templates.js +70 -0
- package/dist/connect/batch_templates.js.map +1 -0
- package/dist/connect/create.d.ts.map +1 -1
- package/dist/connect/create.js +13 -0
- package/dist/connect/create.js.map +1 -1
- package/dist/connect/create_template.d.ts +22 -0
- package/dist/connect/create_template.d.ts.map +1 -0
- package/dist/connect/create_template.js +195 -0
- package/dist/connect/create_template.js.map +1 -0
- package/dist/connect/delete_docs.d.ts +2 -1
- package/dist/connect/delete_docs.d.ts.map +1 -1
- package/dist/connect/delete_docs.js +2 -2
- package/dist/connect/delete_docs.js.map +1 -1
- package/dist/connect/figma_rest_api.d.ts +1 -5
- package/dist/connect/figma_rest_api.d.ts.map +1 -1
- package/dist/connect/figma_rest_api.js +9 -3
- package/dist/connect/figma_rest_api.js.map +1 -1
- package/dist/connect/project.d.ts +9 -7
- package/dist/connect/project.d.ts.map +1 -1
- package/dist/connect/project.js +39 -25
- package/dist/connect/project.js.map +1 -1
- package/dist/connect/raw_templates.d.ts +8 -1
- package/dist/connect/raw_templates.d.ts.map +1 -1
- package/dist/connect/raw_templates.js +20 -7
- package/dist/connect/raw_templates.js.map +1 -1
- package/dist/connect/upload.d.ts +3 -1
- package/dist/connect/upload.d.ts.map +1 -1
- package/dist/connect/upload.js +23 -8
- package/dist/connect/upload.js.map +1 -1
- package/dist/connect/validation.d.ts +2 -2
- package/dist/connect/validation.d.ts.map +1 -1
- package/dist/connect/validation.js +4 -4
- package/dist/connect/validation.js.map +1 -1
- package/dist/connect/wizard/helpers.js.map +1 -1
- package/dist/storybook/convert.d.ts.map +1 -1
- package/dist/storybook/convert.js +1 -1
- package/dist/storybook/convert.js.map +1 -1
- package/figma-types.d.ts +3 -0
- package/package.json +8 -4
|
@@ -0,0 +1,451 @@
|
|
|
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 () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.collectNodesToPreview = collectNodesToPreview;
|
|
40
|
+
exports.filterTemplatesForNodes = filterTemplatesForNodes;
|
|
41
|
+
exports.getPrettierParserMap = getPrettierParserMap;
|
|
42
|
+
exports.isPrettierParseable = isPrettierParseable;
|
|
43
|
+
exports.formatSnippet = formatSnippet;
|
|
44
|
+
exports.displayResults = displayResults;
|
|
45
|
+
exports.handlePreview = handlePreview;
|
|
46
|
+
const fs_1 = __importDefault(require("fs"));
|
|
47
|
+
const path_1 = __importDefault(require("path"));
|
|
48
|
+
const prettier = __importStar(require("prettier"));
|
|
49
|
+
const connect_1 = require("./connect");
|
|
50
|
+
const logging_1 = require("../common/logging");
|
|
51
|
+
const validation_1 = require("../connect/validation");
|
|
52
|
+
const project_1 = require("../connect/project");
|
|
53
|
+
const figma_rest_api_1 = require("../connect/figma_rest_api");
|
|
54
|
+
const fetch_1 = require("../common/fetch");
|
|
55
|
+
/**
|
|
56
|
+
* Collect nodes to preview from file arguments.
|
|
57
|
+
* Matches by exact path first, then by basename.
|
|
58
|
+
*/
|
|
59
|
+
async function collectNodesToPreview(files, allCodeConnectObjects, dir, cmd) {
|
|
60
|
+
const nodesToPreview = [];
|
|
61
|
+
for (const filePath of files) {
|
|
62
|
+
const resolvedPath = path_1.default.isAbsolute(filePath) ? filePath : path_1.default.resolve(dir, filePath);
|
|
63
|
+
// Try exact path first
|
|
64
|
+
if (fs_1.default.existsSync(resolvedPath) && fs_1.default.statSync(resolvedPath).isFile()) {
|
|
65
|
+
const docs = allCodeConnectObjects.filter((d) => path_1.default.resolve(d._codeConnectFilePath || '') === resolvedPath);
|
|
66
|
+
if (docs.length > 0) {
|
|
67
|
+
if (docs.length === 1) {
|
|
68
|
+
logging_1.logger.info(`Found: ${filePath}`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
logging_1.logger.info(`Found ${docs.length} component definition(s) in ${filePath}`);
|
|
72
|
+
}
|
|
73
|
+
for (const doc of docs) {
|
|
74
|
+
const parsed = (0, validation_1.parseFigmaNode)(cmd.verbose, doc, true);
|
|
75
|
+
if (parsed) {
|
|
76
|
+
nodesToPreview.push({
|
|
77
|
+
fileKey: parsed.fileKey,
|
|
78
|
+
nodeId: parsed.nodeId,
|
|
79
|
+
url: doc.figmaNode,
|
|
80
|
+
filePath: path_1.default.relative(dir, doc._codeConnectFilePath || ''),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
logging_1.logger.error(`Failed to parse figmaNode from file: ${filePath}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
logging_1.logger.error(`Not a valid Code Connect file: ${filePath}`);
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
// Fall back to basename matching
|
|
94
|
+
const fileName = path_1.default.basename(filePath);
|
|
95
|
+
if (cmd.verbose) {
|
|
96
|
+
logging_1.logger.debug(`Searching for files matching: ${fileName}`);
|
|
97
|
+
logging_1.logger.debug(`Total Code Connect objects: ${allCodeConnectObjects.length}`);
|
|
98
|
+
}
|
|
99
|
+
const matches = allCodeConnectObjects.filter((d) => path_1.default.basename(d._codeConnectFilePath || '') === fileName);
|
|
100
|
+
if (matches.length === 0) {
|
|
101
|
+
logging_1.logger.error(`No files found matching: ${filePath}`);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (matches.length === 1) {
|
|
105
|
+
logging_1.logger.info(`Found: ${matches[0]._codeConnectFilePath}`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const uniqueFiles = new Set(matches.map((d) => d._codeConnectFilePath));
|
|
109
|
+
if (uniqueFiles.size === 1) {
|
|
110
|
+
logging_1.logger.info(`Found ${matches.length} component definition(s) in ${path_1.default.relative(dir, Array.from(uniqueFiles)[0] || '')}`);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
logging_1.logger.info(`Found ${matches.length} component definition(s) in ${uniqueFiles.size} files:`);
|
|
114
|
+
for (const fp of uniqueFiles) {
|
|
115
|
+
const count = matches.filter((d) => d._codeConnectFilePath === fp).length;
|
|
116
|
+
logging_1.logger.info(` - ${path_1.default.relative(dir, fp || '')} (${count} definition${count > 1 ? 's' : ''})`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const doc of matches) {
|
|
121
|
+
const parsed = (0, validation_1.parseFigmaNode)(cmd.verbose, doc, true);
|
|
122
|
+
if (parsed) {
|
|
123
|
+
nodesToPreview.push({
|
|
124
|
+
fileKey: parsed.fileKey,
|
|
125
|
+
nodeId: parsed.nodeId,
|
|
126
|
+
url: doc.figmaNode,
|
|
127
|
+
filePath: path_1.default.relative(dir, doc._codeConnectFilePath || ''),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return nodesToPreview;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Filter templates to only those matching the requested node IDs.
|
|
136
|
+
*/
|
|
137
|
+
function filterTemplatesForNodes(nodeIds, allTemplates) {
|
|
138
|
+
return allTemplates.filter((template) => {
|
|
139
|
+
if (!template.figmaNode)
|
|
140
|
+
return false;
|
|
141
|
+
const nodeIdMatch = template.figmaNode.match(/node-id=([^&\s]+)/);
|
|
142
|
+
if (!nodeIdMatch?.[1])
|
|
143
|
+
return false;
|
|
144
|
+
const templateNodeId = nodeIdMatch[1].replace(/-/g, ':');
|
|
145
|
+
return nodeIds.includes(templateNodeId);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Aliases for Code Connect labels that don't directly match a Prettier language name.
|
|
150
|
+
*/
|
|
151
|
+
const CC_LABEL_ALIASES = {
|
|
152
|
+
react: 'typescript',
|
|
153
|
+
code: 'typescript',
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Map from Code Connect language labels to Prettier parser names,
|
|
157
|
+
* built lazily from Prettier's getSupportInfo() on first access.
|
|
158
|
+
* Lazy to avoid triggering plugin loading at module import time.
|
|
159
|
+
*/
|
|
160
|
+
let _prettierParserMap = null;
|
|
161
|
+
function getPrettierParserMap() {
|
|
162
|
+
if (_prettierParserMap)
|
|
163
|
+
return _prettierParserMap;
|
|
164
|
+
const info = prettier.getSupportInfo();
|
|
165
|
+
_prettierParserMap = {};
|
|
166
|
+
for (const lang of info.languages) {
|
|
167
|
+
if (lang.parsers?.[0]) {
|
|
168
|
+
_prettierParserMap[lang.name.toLowerCase()] = lang.parsers[0];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
for (const [alias, target] of Object.entries(CC_LABEL_ALIASES)) {
|
|
172
|
+
if (_prettierParserMap[target]) {
|
|
173
|
+
_prettierParserMap[alias] = _prettierParserMap[target];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return _prettierParserMap;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Check if a snippet can be parsed by Prettier.
|
|
180
|
+
* Returns true for languages without a Prettier parser (nothing to validate).
|
|
181
|
+
*/
|
|
182
|
+
async function isPrettierParseable(snippet, language) {
|
|
183
|
+
const lang = language?.toLowerCase();
|
|
184
|
+
const parser = lang ? getPrettierParserMap()[lang] : 'typescript';
|
|
185
|
+
if (!parser) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
await prettier.format(snippet, { parser });
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Format snippet with Prettier.
|
|
198
|
+
* Returns the snippet unmodified for languages Prettier doesn't support.
|
|
199
|
+
*/
|
|
200
|
+
async function formatSnippet(snippet, language) {
|
|
201
|
+
const lang = language?.toLowerCase();
|
|
202
|
+
const parser = lang ? getPrettierParserMap()[lang] : 'typescript';
|
|
203
|
+
if (!parser) {
|
|
204
|
+
return snippet;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
let formatted = await prettier.format(snippet, {
|
|
208
|
+
parser,
|
|
209
|
+
semi: false,
|
|
210
|
+
singleQuote: true,
|
|
211
|
+
printWidth: 80,
|
|
212
|
+
tabWidth: 2,
|
|
213
|
+
});
|
|
214
|
+
formatted = formatted.replace(/^;\s*/, '');
|
|
215
|
+
return formatted;
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
logging_1.logger.info(`Autoformatting couldn't be applied: language not supported or code is malformed`);
|
|
219
|
+
return snippet;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Display results with terminal colors
|
|
224
|
+
*/
|
|
225
|
+
function displayResults(results) {
|
|
226
|
+
console.log('');
|
|
227
|
+
const successCount = results.filter((r) => r.success).length;
|
|
228
|
+
const errorCount = results.filter((r) => !r.success).length;
|
|
229
|
+
const purple = '\x1b[38;5;93m';
|
|
230
|
+
const red = '\x1b[38;5;196m';
|
|
231
|
+
const gray = '\x1b[38;5;243m';
|
|
232
|
+
const reset = '\x1b[0m';
|
|
233
|
+
const bold = '\x1b[1m';
|
|
234
|
+
for (const result of results) {
|
|
235
|
+
if (result.success && result.snippet) {
|
|
236
|
+
const componentInfo = result.component ? ` ${gray}→ ${result.component}${reset}` : '';
|
|
237
|
+
console.log(`${purple}●${reset} ${bold}${result.filePath}${reset}${componentInfo}`);
|
|
238
|
+
console.log(` ${gray}${result.url}${reset}`);
|
|
239
|
+
console.log('');
|
|
240
|
+
const indentedSnippet = result.snippet
|
|
241
|
+
.trim()
|
|
242
|
+
.split('\n')
|
|
243
|
+
.map((line) => ' ' + line)
|
|
244
|
+
.join('\n');
|
|
245
|
+
console.log(indentedSnippet);
|
|
246
|
+
console.log('');
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
const componentInfo = result.component ? ` ${gray}(${result.component})${reset}` : '';
|
|
250
|
+
console.log(`${red}✕${reset} ${bold}${result.filePath}${reset}${componentInfo}`);
|
|
251
|
+
console.log(` ${gray}${result.url}${reset}`);
|
|
252
|
+
console.log(` ${red}Error:${reset} ${result.error}`);
|
|
253
|
+
console.log('');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
console.log(`${bold}Summary:${reset} ${purple}${successCount} succeeded${reset}, ${errorCount > 0 ? `${red}${errorCount} failed${reset}` : `${gray}${errorCount} failed${reset}`}`);
|
|
257
|
+
}
|
|
258
|
+
// The server caps inbound request bodies at 5MB. The figmaDocs payload
|
|
259
|
+
// (one Code Connect template per requested component) dominates the body size,
|
|
260
|
+
// so for projects with many components we split into smaller chunks and send
|
|
261
|
+
// them in parallel rather than risking a 413.
|
|
262
|
+
const PREVIEW_CHUNK_SIZE = 50;
|
|
263
|
+
// Bound the number of chunks in flight at once. Without this cap, large
|
|
264
|
+
// libraries (~1k+ components) trigger N parallel preview requests, which
|
|
265
|
+
// overwhelms upstream token validation and causes spurious 403 "Invalid
|
|
266
|
+
// token" responses for the chunks that queue up too long.
|
|
267
|
+
const PREVIEW_MAX_CONCURRENCY = 5;
|
|
268
|
+
function chunkArray(arr, size) {
|
|
269
|
+
const chunks = [];
|
|
270
|
+
for (let i = 0; i < arr.length; i += size) {
|
|
271
|
+
chunks.push(arr.slice(i, i + size));
|
|
272
|
+
}
|
|
273
|
+
return chunks;
|
|
274
|
+
}
|
|
275
|
+
async function runInWaves(items, fn, maxConcurrency) {
|
|
276
|
+
const out = [];
|
|
277
|
+
for (let i = 0; i < items.length; i += maxConcurrency) {
|
|
278
|
+
const wave = items.slice(i, i + maxConcurrency);
|
|
279
|
+
const settled = await Promise.all(wave.map(fn));
|
|
280
|
+
out.push(...settled);
|
|
281
|
+
}
|
|
282
|
+
return out;
|
|
283
|
+
}
|
|
284
|
+
function sendPreviewChunk({ chunkNodeIds, requiredTemplates, baseApiUrl, fileKey, accessToken, }) {
|
|
285
|
+
const chunkTemplates = filterTemplatesForNodes(chunkNodeIds, requiredTemplates);
|
|
286
|
+
return fetch_1.request
|
|
287
|
+
.post(`${baseApiUrl}/code_connect/preview_snippets?file_key=${fileKey}`, { nodeIds: chunkNodeIds, figmaDocs: { all: chunkTemplates } }, { headers: (0, figma_rest_api_1.getHeaders)(accessToken) })
|
|
288
|
+
.then((response) => ({ chunkNodeIds, response, error: null }))
|
|
289
|
+
.catch((err) => ({ chunkNodeIds, response: null, error: err }));
|
|
290
|
+
}
|
|
291
|
+
function chunkOutcomeToResults(outcome, nodes, fileKey) {
|
|
292
|
+
const { chunkNodeIds, response, error } = outcome;
|
|
293
|
+
const results = [];
|
|
294
|
+
if (error) {
|
|
295
|
+
const errorMsg = (0, fetch_1.isFetchError)(error)
|
|
296
|
+
? error.data?.message || error.response.statusText
|
|
297
|
+
: String(error);
|
|
298
|
+
// 503 means the server-side killswitch is on — surface the server's
|
|
299
|
+
// message directly without the per-file/per-node noise so the user
|
|
300
|
+
// sees a single clear line during an incident.
|
|
301
|
+
if ((0, fetch_1.isFetchError)(error) && error.response.status === 503) {
|
|
302
|
+
(0, logging_1.exitWithError)(errorMsg);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
logging_1.logger.error(`Failed to preview components in file ${fileKey}: ${errorMsg}`);
|
|
306
|
+
for (const nodeId of chunkNodeIds) {
|
|
307
|
+
for (const node of nodes.filter((n) => n.nodeId === nodeId)) {
|
|
308
|
+
results.push({
|
|
309
|
+
url: node.url,
|
|
310
|
+
nodeId: node.nodeId,
|
|
311
|
+
filePath: node.filePath,
|
|
312
|
+
success: false,
|
|
313
|
+
error: errorMsg,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return results;
|
|
319
|
+
}
|
|
320
|
+
if (response.response.status === 200 && response.data.meta?.results) {
|
|
321
|
+
// Track match index per nodeId so duplicate node IDs get the correct file attribution.
|
|
322
|
+
// The server returns results in the same order as the templates we sent. Each chunk's
|
|
323
|
+
// nodeIds are disjoint (we dedupe before chunking), so per-chunk indexing is correct.
|
|
324
|
+
const nodeMatchIndex = {};
|
|
325
|
+
for (const result of response.data.meta.results) {
|
|
326
|
+
const idx = nodeMatchIndex[result.nodeId] ?? 0;
|
|
327
|
+
const matchingNodes = nodes.filter((n) => n.nodeId === result.nodeId);
|
|
328
|
+
const node = matchingNodes[idx] || matchingNodes[0];
|
|
329
|
+
nodeMatchIndex[result.nodeId] = idx + 1;
|
|
330
|
+
results.push({
|
|
331
|
+
url: node?.url || result.nodeUrl,
|
|
332
|
+
nodeId: result.nodeId,
|
|
333
|
+
filePath: node?.filePath || '',
|
|
334
|
+
success: !result.error && !!result.snippet,
|
|
335
|
+
snippet: result.snippet,
|
|
336
|
+
language: result.language,
|
|
337
|
+
component: result.component,
|
|
338
|
+
error: result.error || (!result.snippet ? 'No snippet returned by server' : undefined),
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
return results;
|
|
342
|
+
}
|
|
343
|
+
for (const nodeId of chunkNodeIds) {
|
|
344
|
+
for (const node of nodes.filter((n) => n.nodeId === nodeId)) {
|
|
345
|
+
results.push({
|
|
346
|
+
url: node.url,
|
|
347
|
+
nodeId: node.nodeId,
|
|
348
|
+
filePath: node.filePath,
|
|
349
|
+
success: false,
|
|
350
|
+
error: `API request failed with status ${response.response.status}`,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return results;
|
|
355
|
+
}
|
|
356
|
+
async function previewFile({ fileKey, nodes, baseApiUrl, accessToken, dir, allCodeConnectObjects, }) {
|
|
357
|
+
const allNodeIds = nodes.map((n) => n.nodeId);
|
|
358
|
+
// Deduplicate — multiple figma.connect() calls may share the same nodeId;
|
|
359
|
+
// the individual templates are sent in figmaDocs and the server iterates per-template.
|
|
360
|
+
const nodeIds = [...new Set(allNodeIds)];
|
|
361
|
+
// Only send templates from the requested files — other files' templates
|
|
362
|
+
// for the same nodeId exist on the server and shouldn't be rendered individually.
|
|
363
|
+
const requestedFilePaths = new Set(nodes.map((n) => path_1.default.resolve(dir, n.filePath)));
|
|
364
|
+
const requiredTemplates = filterTemplatesForNodes(allNodeIds, allCodeConnectObjects).filter((t) => requestedFilePaths.has(path_1.default.resolve(t._codeConnectFilePath || '')));
|
|
365
|
+
const chunks = chunkArray(nodeIds, PREVIEW_CHUNK_SIZE);
|
|
366
|
+
const outcomes = await runInWaves(chunks, (chunkNodeIds) => sendPreviewChunk({ chunkNodeIds, requiredTemplates, baseApiUrl, fileKey, accessToken }), PREVIEW_MAX_CONCURRENCY);
|
|
367
|
+
return outcomes.flatMap((outcome) => chunkOutcomeToResults(outcome, nodes, fileKey));
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Handle the preview command
|
|
371
|
+
*/
|
|
372
|
+
async function handlePreview(files, cmd) {
|
|
373
|
+
(0, connect_1.setupHandler)(cmd);
|
|
374
|
+
const dir = cmd.dir ?? process.cwd();
|
|
375
|
+
const projectInfo = await (0, project_1.getProjectInfo)(dir, cmd.config);
|
|
376
|
+
const accessToken = (0, connect_1.getAccessTokenOrExit)(cmd);
|
|
377
|
+
const outputFormat = cmd.output || 'table';
|
|
378
|
+
const allCodeConnectObjects = await (0, connect_1.getCodeConnectObjects)(cmd, projectInfo, true);
|
|
379
|
+
let nodesToCheck;
|
|
380
|
+
if (files && files.length > 0) {
|
|
381
|
+
nodesToCheck = await collectNodesToPreview(files, allCodeConnectObjects, dir, cmd);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
logging_1.logger.info('Previewing all local Code Connect files...');
|
|
385
|
+
nodesToCheck = [];
|
|
386
|
+
for (const doc of allCodeConnectObjects) {
|
|
387
|
+
const parsed = (0, validation_1.parseFigmaNode)(cmd.verbose, doc, true);
|
|
388
|
+
if (parsed) {
|
|
389
|
+
nodesToCheck.push({
|
|
390
|
+
fileKey: parsed.fileKey,
|
|
391
|
+
nodeId: parsed.nodeId,
|
|
392
|
+
url: doc.figmaNode,
|
|
393
|
+
filePath: path_1.default.relative(dir, doc._codeConnectFilePath || ''),
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (nodesToCheck.length === 0) {
|
|
399
|
+
(0, logging_1.exitWithError)('No valid Code Connect files found to preview');
|
|
400
|
+
}
|
|
401
|
+
logging_1.logger.info(`Previewing ${nodesToCheck.length} component(s)...`);
|
|
402
|
+
// Group nodes by fileKey for batching
|
|
403
|
+
const nodesByFileKey = {};
|
|
404
|
+
for (const node of nodesToCheck) {
|
|
405
|
+
if (!nodesByFileKey[node.fileKey]) {
|
|
406
|
+
nodesByFileKey[node.fileKey] = [];
|
|
407
|
+
}
|
|
408
|
+
nodesByFileKey[node.fileKey].push(node);
|
|
409
|
+
}
|
|
410
|
+
const results = [];
|
|
411
|
+
for (const [fileKey, nodes] of Object.entries(nodesByFileKey)) {
|
|
412
|
+
const baseApiUrl = (0, figma_rest_api_1.getApiUrl)(nodes[0].url, cmd.apiUrl || projectInfo.config.apiUrl);
|
|
413
|
+
const fileResults = await previewFile({
|
|
414
|
+
fileKey,
|
|
415
|
+
nodes,
|
|
416
|
+
baseApiUrl,
|
|
417
|
+
accessToken,
|
|
418
|
+
dir,
|
|
419
|
+
allCodeConnectObjects,
|
|
420
|
+
});
|
|
421
|
+
results.push(...fileResults);
|
|
422
|
+
}
|
|
423
|
+
// Validate snippet syntax
|
|
424
|
+
for (const result of results) {
|
|
425
|
+
if (result.success && result.snippet) {
|
|
426
|
+
const prettierValid = await isPrettierParseable(result.snippet, result.language);
|
|
427
|
+
if (!prettierValid) {
|
|
428
|
+
// Prettier can parse this language but failed — mark as error
|
|
429
|
+
result.success = false;
|
|
430
|
+
result.error = 'Snippet has syntax errors and may not be valid code';
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (outputFormat === 'json') {
|
|
435
|
+
console.log(JSON.stringify(results, null, 2));
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
const formattedResults = await Promise.all(results.map(async (result) => {
|
|
439
|
+
if (result.success && result.snippet) {
|
|
440
|
+
const formattedSnippet = await formatSnippet(result.snippet, result.language);
|
|
441
|
+
return { ...result, snippet: formattedSnippet };
|
|
442
|
+
}
|
|
443
|
+
return result;
|
|
444
|
+
}));
|
|
445
|
+
displayResults(formattedResults);
|
|
446
|
+
}
|
|
447
|
+
if (results.every((r) => !r.success)) {
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
//# sourceMappingURL=preview_utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview_utils.js","sourceRoot":"","sources":["../../src/commands/preview_utils.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs'\nimport path from 'path'\nimport * as prettier from 'prettier'\nimport { BaseCommand, getCodeConnectObjects, getAccessTokenOrExit, setupHandler } from './connect'\nimport { exitWithError, logger } from '../common/logging'\nimport { CodeConnectJSON } from '../connect/figma_connect'\nimport { parseFigmaNode } from '../connect/validation'\nimport { getProjectInfo } from '../connect/project'\nimport { getApiUrl, getHeaders } from '../connect/figma_rest_api'\nimport { request, isFetchError } from '../common/fetch'\n\nexport interface NodeToPreview {\n fileKey: string\n nodeId: string\n url: string\n filePath: string\n}\n\nexport interface PreviewResult {\n url: string\n nodeId: string\n filePath: string\n success: boolean\n snippet?: string\n language?: string\n component?: string\n error?: string\n}\n\n/**\n * Collect nodes to preview from file arguments.\n * Matches by exact path first, then by basename.\n */\nexport async function collectNodesToPreview(\n files: string[],\n allCodeConnectObjects: CodeConnectJSON[],\n dir: string,\n cmd: BaseCommand,\n): Promise<NodeToPreview[]> {\n const nodesToPreview: NodeToPreview[] = []\n\n for (const filePath of files) {\n const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(dir, filePath)\n\n // Try exact path first\n if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isFile()) {\n const docs = allCodeConnectObjects.filter(\n (d) => path.resolve(d._codeConnectFilePath || '') === resolvedPath,\n )\n if (docs.length > 0) {\n if (docs.length === 1) {\n logger.info(`Found: ${filePath}`)\n } else {\n logger.info(`Found ${docs.length} component definition(s) in ${filePath}`)\n }\n for (const doc of docs) {\n const parsed = parseFigmaNode(cmd.verbose, doc, true)\n if (parsed) {\n nodesToPreview.push({\n fileKey: parsed.fileKey,\n nodeId: parsed.nodeId,\n url: doc.figmaNode,\n filePath: path.relative(dir, doc._codeConnectFilePath || ''),\n })\n } else {\n logger.error(`Failed to parse figmaNode from file: ${filePath}`)\n }\n }\n } else {\n logger.error(`Not a valid Code Connect file: ${filePath}`)\n }\n continue\n }\n\n // Fall back to basename matching\n const fileName = path.basename(filePath)\n if (cmd.verbose) {\n logger.debug(`Searching for files matching: ${fileName}`)\n logger.debug(`Total Code Connect objects: ${allCodeConnectObjects.length}`)\n }\n\n const matches = allCodeConnectObjects.filter(\n (d) => path.basename(d._codeConnectFilePath || '') === fileName,\n )\n\n if (matches.length === 0) {\n logger.error(`No files found matching: ${filePath}`)\n continue\n }\n\n if (matches.length === 1) {\n logger.info(`Found: ${matches[0]._codeConnectFilePath}`)\n } else {\n const uniqueFiles = new Set(matches.map((d) => d._codeConnectFilePath))\n if (uniqueFiles.size === 1) {\n logger.info(\n `Found ${matches.length} component definition(s) in ${path.relative(dir, Array.from(uniqueFiles)[0] || '')}`,\n )\n } else {\n logger.info(`Found ${matches.length} component definition(s) in ${uniqueFiles.size} files:`)\n for (const fp of uniqueFiles) {\n const count = matches.filter((d) => d._codeConnectFilePath === fp).length\n logger.info(\n ` - ${path.relative(dir, fp || '')} (${count} definition${count > 1 ? 's' : ''})`,\n )\n }\n }\n }\n\n for (const doc of matches) {\n const parsed = parseFigmaNode(cmd.verbose, doc, true)\n if (parsed) {\n nodesToPreview.push({\n fileKey: parsed.fileKey,\n nodeId: parsed.nodeId,\n url: doc.figmaNode,\n filePath: path.relative(dir, doc._codeConnectFilePath || ''),\n })\n }\n }\n }\n\n return nodesToPreview\n}\n\n/**\n * Filter templates to only those matching the requested node IDs.\n */\nexport function filterTemplatesForNodes(\n nodeIds: string[],\n allTemplates: CodeConnectJSON[],\n): CodeConnectJSON[] {\n return allTemplates.filter((template) => {\n if (!template.figmaNode) return false\n const nodeIdMatch = template.figmaNode.match(/node-id=([^&\\s]+)/)\n if (!nodeIdMatch?.[1]) return false\n const templateNodeId = nodeIdMatch[1].replace(/-/g, ':')\n return nodeIds.includes(templateNodeId)\n })\n}\n\n/**\n * Aliases for Code Connect labels that don't directly match a Prettier language name.\n */\nconst CC_LABEL_ALIASES: Record<string, string> = {\n react: 'typescript',\n code: 'typescript',\n}\n\n/**\n * Map from Code Connect language labels to Prettier parser names,\n * built lazily from Prettier's getSupportInfo() on first access.\n * Lazy to avoid triggering plugin loading at module import time.\n */\nlet _prettierParserMap: Record<string, string> | null = null\n\nexport function getPrettierParserMap(): Record<string, string> {\n if (_prettierParserMap) return _prettierParserMap\n\n const info = prettier.getSupportInfo() as {\n languages: Array<{ name: string; parsers?: string[] }>\n }\n _prettierParserMap = {}\n\n for (const lang of info.languages) {\n if (lang.parsers?.[0]) {\n _prettierParserMap[lang.name.toLowerCase()] = lang.parsers[0]\n }\n }\n\n for (const [alias, target] of Object.entries(CC_LABEL_ALIASES)) {\n if (_prettierParserMap[target]) {\n _prettierParserMap[alias] = _prettierParserMap[target]\n }\n }\n\n return _prettierParserMap\n}\n\n/**\n * Check if a snippet can be parsed by Prettier.\n * Returns true for languages without a Prettier parser (nothing to validate).\n */\nexport async function isPrettierParseable(snippet: string, language?: string): Promise<boolean> {\n const lang = language?.toLowerCase()\n const parser = lang ? getPrettierParserMap()[lang] : 'typescript'\n\n if (!parser) {\n return true\n }\n\n try {\n await prettier.format(snippet, { parser })\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Format snippet with Prettier.\n * Returns the snippet unmodified for languages Prettier doesn't support.\n */\nexport async function formatSnippet(snippet: string, language?: string): Promise<string> {\n const lang = language?.toLowerCase()\n const parser = lang ? getPrettierParserMap()[lang] : 'typescript'\n\n if (!parser) {\n return snippet\n }\n\n try {\n let formatted = await prettier.format(snippet, {\n parser,\n semi: false,\n singleQuote: true,\n printWidth: 80,\n tabWidth: 2,\n })\n formatted = formatted.replace(/^;\\s*/, '')\n return formatted\n } catch {\n logger.info(`Autoformatting couldn't be applied: language not supported or code is malformed`)\n return snippet\n }\n}\n\n/**\n * Display results with terminal colors\n */\nexport function displayResults(results: PreviewResult[]): void {\n console.log('')\n const successCount = results.filter((r) => r.success).length\n const errorCount = results.filter((r) => !r.success).length\n\n const purple = '\\x1b[38;5;93m'\n const red = '\\x1b[38;5;196m'\n const gray = '\\x1b[38;5;243m'\n const reset = '\\x1b[0m'\n const bold = '\\x1b[1m'\n\n for (const result of results) {\n if (result.success && result.snippet) {\n const componentInfo = result.component ? ` ${gray}→ ${result.component}${reset}` : ''\n console.log(`${purple}●${reset} ${bold}${result.filePath}${reset}${componentInfo}`)\n console.log(` ${gray}${result.url}${reset}`)\n console.log('')\n\n const indentedSnippet = result.snippet\n .trim()\n .split('\\n')\n .map((line) => ' ' + line)\n .join('\\n')\n console.log(indentedSnippet)\n console.log('')\n } else {\n const componentInfo = result.component ? ` ${gray}(${result.component})${reset}` : ''\n console.log(`${red}✕${reset} ${bold}${result.filePath}${reset}${componentInfo}`)\n console.log(` ${gray}${result.url}${reset}`)\n console.log(` ${red}Error:${reset} ${result.error}`)\n console.log('')\n }\n }\n\n console.log(\n `${bold}Summary:${reset} ${purple}${successCount} succeeded${reset}, ${errorCount > 0 ? `${red}${errorCount} failed${reset}` : `${gray}${errorCount} failed${reset}`}`,\n )\n}\n\n// The server caps inbound request bodies at 5MB. The figmaDocs payload\n// (one Code Connect template per requested component) dominates the body size,\n// so for projects with many components we split into smaller chunks and send\n// them in parallel rather than risking a 413.\nconst PREVIEW_CHUNK_SIZE = 50\n// Bound the number of chunks in flight at once. Without this cap, large\n// libraries (~1k+ components) trigger N parallel preview requests, which\n// overwhelms upstream token validation and causes spurious 403 \"Invalid\n// token\" responses for the chunks that queue up too long.\nconst PREVIEW_MAX_CONCURRENCY = 5\n\ntype PreviewResponseData = {\n status: number\n error: boolean\n meta: {\n results: Array<{\n nodeId: string\n nodeUrl: string\n snippet?: string\n language?: string\n component?: string\n error?: string\n }>\n }\n}\n\ntype ChunkOutcome = {\n chunkNodeIds: string[]\n response: Awaited<ReturnType<typeof request.post<PreviewResponseData>>> | null\n error: unknown\n}\n\nfunction chunkArray<T>(arr: T[], size: number): T[][] {\n const chunks: T[][] = []\n for (let i = 0; i < arr.length; i += size) {\n chunks.push(arr.slice(i, i + size))\n }\n return chunks\n}\n\nasync function runInWaves<T, R>(\n items: T[],\n fn: (item: T) => Promise<R>,\n maxConcurrency: number,\n): Promise<R[]> {\n const out: R[] = []\n for (let i = 0; i < items.length; i += maxConcurrency) {\n const wave = items.slice(i, i + maxConcurrency)\n const settled = await Promise.all(wave.map(fn))\n out.push(...settled)\n }\n return out\n}\n\nfunction sendPreviewChunk({\n chunkNodeIds,\n requiredTemplates,\n baseApiUrl,\n fileKey,\n accessToken,\n}: {\n chunkNodeIds: string[]\n requiredTemplates: CodeConnectJSON[]\n baseApiUrl: string\n fileKey: string\n accessToken: string\n}): Promise<ChunkOutcome> {\n const chunkTemplates = filterTemplatesForNodes(chunkNodeIds, requiredTemplates)\n return request\n .post<PreviewResponseData>(\n `${baseApiUrl}/code_connect/preview_snippets?file_key=${fileKey}`,\n { nodeIds: chunkNodeIds, figmaDocs: { all: chunkTemplates } },\n { headers: getHeaders(accessToken) },\n )\n .then((response) => ({ chunkNodeIds, response, error: null as unknown }))\n .catch((err: unknown) => ({ chunkNodeIds, response: null, error: err }))\n}\n\nfunction chunkOutcomeToResults(\n outcome: ChunkOutcome,\n nodes: NodeToPreview[],\n fileKey: string,\n): PreviewResult[] {\n const { chunkNodeIds, response, error } = outcome\n const results: PreviewResult[] = []\n\n if (error) {\n const errorMsg = isFetchError(error)\n ? error.data?.message || error.response.statusText\n : String(error)\n // 503 means the server-side killswitch is on — surface the server's\n // message directly without the per-file/per-node noise so the user\n // sees a single clear line during an incident.\n if (isFetchError(error) && error.response.status === 503) {\n exitWithError(errorMsg)\n } else {\n logger.error(`Failed to preview components in file ${fileKey}: ${errorMsg}`)\n for (const nodeId of chunkNodeIds) {\n for (const node of nodes.filter((n) => n.nodeId === nodeId)) {\n results.push({\n url: node.url,\n nodeId: node.nodeId,\n filePath: node.filePath,\n success: false,\n error: errorMsg,\n })\n }\n }\n }\n return results\n }\n\n if (response!.response.status === 200 && response!.data.meta?.results) {\n // Track match index per nodeId so duplicate node IDs get the correct file attribution.\n // The server returns results in the same order as the templates we sent. Each chunk's\n // nodeIds are disjoint (we dedupe before chunking), so per-chunk indexing is correct.\n const nodeMatchIndex: Record<string, number> = {}\n for (const result of response!.data.meta.results) {\n const idx = nodeMatchIndex[result.nodeId] ?? 0\n const matchingNodes = nodes.filter((n) => n.nodeId === result.nodeId)\n const node = matchingNodes[idx] || matchingNodes[0]\n nodeMatchIndex[result.nodeId] = idx + 1\n results.push({\n url: node?.url || result.nodeUrl,\n nodeId: result.nodeId,\n filePath: node?.filePath || '',\n success: !result.error && !!result.snippet,\n snippet: result.snippet,\n language: result.language,\n component: result.component,\n error: result.error || (!result.snippet ? 'No snippet returned by server' : undefined),\n })\n }\n return results\n }\n\n for (const nodeId of chunkNodeIds) {\n for (const node of nodes.filter((n) => n.nodeId === nodeId)) {\n results.push({\n url: node.url,\n nodeId: node.nodeId,\n filePath: node.filePath,\n success: false,\n error: `API request failed with status ${response!.response.status}`,\n })\n }\n }\n return results\n}\n\nasync function previewFile({\n fileKey,\n nodes,\n baseApiUrl,\n accessToken,\n dir,\n allCodeConnectObjects,\n}: {\n fileKey: string\n nodes: NodeToPreview[]\n baseApiUrl: string\n accessToken: string\n dir: string\n allCodeConnectObjects: CodeConnectJSON[]\n}): Promise<PreviewResult[]> {\n const allNodeIds = nodes.map((n) => n.nodeId)\n // Deduplicate — multiple figma.connect() calls may share the same nodeId;\n // the individual templates are sent in figmaDocs and the server iterates per-template.\n const nodeIds = [...new Set(allNodeIds)]\n\n // Only send templates from the requested files — other files' templates\n // for the same nodeId exist on the server and shouldn't be rendered individually.\n const requestedFilePaths = new Set(nodes.map((n) => path.resolve(dir, n.filePath)))\n const requiredTemplates = filterTemplatesForNodes(allNodeIds, allCodeConnectObjects).filter((t) =>\n requestedFilePaths.has(path.resolve(t._codeConnectFilePath || '')),\n )\n\n const chunks = chunkArray(nodeIds, PREVIEW_CHUNK_SIZE)\n const outcomes = await runInWaves(\n chunks,\n (chunkNodeIds) =>\n sendPreviewChunk({ chunkNodeIds, requiredTemplates, baseApiUrl, fileKey, accessToken }),\n PREVIEW_MAX_CONCURRENCY,\n )\n\n return outcomes.flatMap((outcome) => chunkOutcomeToResults(outcome, nodes, fileKey))\n}\n\n/**\n * Handle the preview command\n */\nexport async function handlePreview(files: string[], cmd: BaseCommand & { output?: string }) {\n setupHandler(cmd)\n\n const dir = cmd.dir ?? process.cwd()\n const projectInfo = await getProjectInfo(dir, cmd.config)\n const accessToken = getAccessTokenOrExit(cmd)\n const outputFormat = cmd.output || 'table'\n\n const allCodeConnectObjects = await getCodeConnectObjects(cmd, projectInfo, true)\n\n let nodesToCheck: NodeToPreview[]\n\n if (files && files.length > 0) {\n nodesToCheck = await collectNodesToPreview(files, allCodeConnectObjects, dir, cmd)\n } else {\n logger.info('Previewing all local Code Connect files...')\n nodesToCheck = []\n for (const doc of allCodeConnectObjects) {\n const parsed = parseFigmaNode(cmd.verbose, doc, true)\n if (parsed) {\n nodesToCheck.push({\n fileKey: parsed.fileKey,\n nodeId: parsed.nodeId,\n url: doc.figmaNode,\n filePath: path.relative(dir, doc._codeConnectFilePath || ''),\n })\n }\n }\n }\n\n if (nodesToCheck.length === 0) {\n exitWithError('No valid Code Connect files found to preview')\n }\n\n logger.info(`Previewing ${nodesToCheck.length} component(s)...`)\n\n // Group nodes by fileKey for batching\n const nodesByFileKey: Record<string, NodeToPreview[]> = {}\n for (const node of nodesToCheck) {\n if (!nodesByFileKey[node.fileKey]) {\n nodesByFileKey[node.fileKey] = []\n }\n nodesByFileKey[node.fileKey].push(node)\n }\n\n const results: PreviewResult[] = []\n\n for (const [fileKey, nodes] of Object.entries(nodesByFileKey)) {\n const baseApiUrl = getApiUrl(nodes[0].url, cmd.apiUrl || projectInfo.config.apiUrl)\n const fileResults = await previewFile({\n fileKey,\n nodes,\n baseApiUrl,\n accessToken,\n dir,\n allCodeConnectObjects,\n })\n results.push(...fileResults)\n }\n\n // Validate snippet syntax\n for (const result of results) {\n if (result.success && result.snippet) {\n const prettierValid = await isPrettierParseable(result.snippet, result.language)\n if (!prettierValid) {\n // Prettier can parse this language but failed — mark as error\n result.success = false\n result.error = 'Snippet has syntax errors and may not be valid code'\n }\n }\n }\n\n if (outputFormat === 'json') {\n console.log(JSON.stringify(results, null, 2))\n } else {\n const formattedResults: PreviewResult[] = await Promise.all(\n results.map(async (result) => {\n if (result.success && result.snippet) {\n const formattedSnippet = await formatSnippet(result.snippet, result.language)\n return { ...result, snippet: formattedSnippet }\n }\n return result\n }),\n )\n displayResults(formattedResults)\n }\n\n if (results.every((r) => !r.success)) {\n process.exit(1)\n }\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CodeConnectJSON } from './figma_connect';
|
|
2
|
+
import { CodeConnectConfig } from './project';
|
|
3
|
+
/**
|
|
4
|
+
* Parses a `.figma.batch.json` file and produces CodeConnectJSON objects.
|
|
5
|
+
*
|
|
6
|
+
* For each component entry, delegates to `parseRawFile` with batch overrides
|
|
7
|
+
* that prepend `window['__FIGMA_BATCH'] = <data>` to the template. The figma
|
|
8
|
+
* runtime exposes this as `figma.batch`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseBatchFile(filePath: string, label: string | undefined, config?: CodeConnectConfig, dir?: string): CodeConnectJSON[];
|
|
11
|
+
//# sourceMappingURL=batch_templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch_templates.d.ts","sourceRoot":"","sources":["../../src/connect/batch_templates.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAuC7C;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,CAAC,EAAE,iBAAiB,EAC1B,GAAG,CAAC,EAAE,MAAM,GACX,eAAe,EAAE,CA8CnB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
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.parseBatchFile = parseBatchFile;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const raw_templates_1 = require("./raw_templates");
|
|
10
|
+
/**
|
|
11
|
+
* Resolves and validates the template file path referenced by a batch group.
|
|
12
|
+
* Returns the absolute path or throws a descriptive error.
|
|
13
|
+
*/
|
|
14
|
+
function validateBatchTemplatePath(templateFile, batchDir, batchFilePath) {
|
|
15
|
+
const templatePath = path_1.default.resolve(batchDir, templateFile);
|
|
16
|
+
if (!fs_1.default.existsSync(templatePath)) {
|
|
17
|
+
throw new Error(`Template file not found: ${templatePath} (referenced from ${batchFilePath})`);
|
|
18
|
+
}
|
|
19
|
+
if (!templatePath.endsWith('.figma.batch.ts') && !templatePath.endsWith('.figma.batch.js')) {
|
|
20
|
+
throw new Error(`Template file must have a .figma.batch.ts or .figma.batch.js extension: ${templatePath}`);
|
|
21
|
+
}
|
|
22
|
+
return templatePath;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parses a `.figma.batch.json` file and produces CodeConnectJSON objects.
|
|
26
|
+
*
|
|
27
|
+
* For each component entry, delegates to `parseRawFile` with batch overrides
|
|
28
|
+
* that prepend `window['__FIGMA_BATCH'] = <data>` to the template. The figma
|
|
29
|
+
* runtime exposes this as `figma.batch`.
|
|
30
|
+
*/
|
|
31
|
+
function parseBatchFile(filePath, label, config, dir) {
|
|
32
|
+
const raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(raw);
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
throw new Error(`Failed to parse JSON in ${filePath}: ${e}`);
|
|
39
|
+
}
|
|
40
|
+
// Support both single group (object) and multiple groups (array)
|
|
41
|
+
const groups = Array.isArray(parsed) ? parsed : [parsed];
|
|
42
|
+
const results = [];
|
|
43
|
+
const batchDir = path_1.default.dirname(filePath);
|
|
44
|
+
for (const group of groups) {
|
|
45
|
+
if (!group.templateFile) {
|
|
46
|
+
throw new Error(`Missing required field "templateFile" in ${filePath}`);
|
|
47
|
+
}
|
|
48
|
+
if (!Array.isArray(group.components) || group.components.length === 0) {
|
|
49
|
+
throw new Error(`"components" must be a non-empty array in ${filePath}`);
|
|
50
|
+
}
|
|
51
|
+
const templatePath = validateBatchTemplatePath(group.templateFile, batchDir, filePath);
|
|
52
|
+
for (let i = 0; i < group.components.length; i++) {
|
|
53
|
+
const entry = group.components[i];
|
|
54
|
+
if (!entry.url) {
|
|
55
|
+
const entryName = entry.name || entry.component || `index ${i}`;
|
|
56
|
+
throw new Error(`Missing required field "url" in entry "${entryName}" in ${filePath}`);
|
|
57
|
+
}
|
|
58
|
+
const doc = (0, raw_templates_1.parseRawFile)(templatePath, label, config, dir, {
|
|
59
|
+
url: entry.url,
|
|
60
|
+
source: entry.source,
|
|
61
|
+
component: entry.component,
|
|
62
|
+
batchData: entry,
|
|
63
|
+
batchFilePath: filePath,
|
|
64
|
+
});
|
|
65
|
+
results.push(doc);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return results;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=batch_templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch_templates.js","sourceRoot":"","sources":["../../src/connect/batch_templates.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs'\nimport path from 'path'\nimport { CodeConnectJSON } from './figma_connect'\nimport { CodeConnectConfig } from './project'\nimport { parseRawFile } from './raw_templates'\n\ninterface BatchComponentEntry {\n url: string\n source?: string\n component?: string\n [key: string]: any\n}\n\ninterface BatchGroup {\n templateFile: string\n components: BatchComponentEntry[]\n}\n\n/**\n * Resolves and validates the template file path referenced by a batch group.\n * Returns the absolute path or throws a descriptive error.\n */\nfunction validateBatchTemplatePath(\n templateFile: string,\n batchDir: string,\n batchFilePath: string,\n): string {\n const templatePath = path.resolve(batchDir, templateFile)\n\n if (!fs.existsSync(templatePath)) {\n throw new Error(`Template file not found: ${templatePath} (referenced from ${batchFilePath})`)\n }\n\n if (!templatePath.endsWith('.figma.batch.ts') && !templatePath.endsWith('.figma.batch.js')) {\n throw new Error(\n `Template file must have a .figma.batch.ts or .figma.batch.js extension: ${templatePath}`,\n )\n }\n\n return templatePath\n}\n\n/**\n * Parses a `.figma.batch.json` file and produces CodeConnectJSON objects.\n *\n * For each component entry, delegates to `parseRawFile` with batch overrides\n * that prepend `window['__FIGMA_BATCH'] = <data>` to the template. The figma\n * runtime exposes this as `figma.batch`.\n */\nexport function parseBatchFile(\n filePath: string,\n label: string | undefined,\n config?: CodeConnectConfig,\n dir?: string,\n): CodeConnectJSON[] {\n const raw = fs.readFileSync(filePath, 'utf-8')\n let parsed: any\n try {\n parsed = JSON.parse(raw)\n } catch (e) {\n throw new Error(`Failed to parse JSON in ${filePath}: ${e}`)\n }\n\n // Support both single group (object) and multiple groups (array)\n const groups: BatchGroup[] = Array.isArray(parsed) ? parsed : [parsed]\n\n const results: CodeConnectJSON[] = []\n const batchDir = path.dirname(filePath)\n\n for (const group of groups) {\n if (!group.templateFile) {\n throw new Error(`Missing required field \"templateFile\" in ${filePath}`)\n }\n if (!Array.isArray(group.components) || group.components.length === 0) {\n throw new Error(`\"components\" must be a non-empty array in ${filePath}`)\n }\n\n const templatePath = validateBatchTemplatePath(group.templateFile, batchDir, filePath)\n\n for (let i = 0; i < group.components.length; i++) {\n const entry = group.components[i]\n\n if (!entry.url) {\n const entryName = entry.name || entry.component || `index ${i}`\n throw new Error(`Missing required field \"url\" in entry \"${entryName}\" in ${filePath}`)\n }\n\n const doc = parseRawFile(templatePath, label, config, dir, {\n url: entry.url,\n source: entry.source,\n component: entry.component,\n batchData: entry,\n batchFilePath: filePath,\n })\n\n results.push(doc)\n }\n }\n\n return results\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/connect/create.ts"],"names":[],"mappings":"AAUA,OAAO,EAAqC,WAAW,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/connect/create.ts"],"names":[],"mappings":"AAUA,OAAO,EAAqC,WAAW,EAAE,MAAM,WAAW,CAAA;AAY1E,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEjD,UAAU,gBAAgB;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,WAAW,CAAA;IACxB,GAAG,EAAE,WAAW,CAAA;CACjB;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,UAQlD;AAED,wBAAsB,wBAAwB,CAAC,EAC7C,WAAW,EACX,YAAY,EACZ,OAAO,EACP,MAAM,EACN,WAAW,EACX,GAAG,GACJ,EAAE,gBAAgB,iBA0IlB"}
|
package/dist/connect/create.js
CHANGED
|
@@ -14,6 +14,7 @@ const create_1 = require("../react/create");
|
|
|
14
14
|
const parser_executable_types_1 = require("./parser_executable_types");
|
|
15
15
|
const zod_validation_error_1 = require("zod-validation-error");
|
|
16
16
|
const create_2 = require("../html/create");
|
|
17
|
+
const create_template_1 = require("./create_template");
|
|
17
18
|
const fetch_1 = require("../common/fetch");
|
|
18
19
|
function normalizeComponentName(name) {
|
|
19
20
|
// Convert the string to PascalCase and ensure first character is not a digit
|
|
@@ -84,6 +85,18 @@ async function createCodeConnectFromUrl({ accessToken, figmaNodeUrl, outFile, ou
|
|
|
84
85
|
};
|
|
85
86
|
result = await (0, create_2.createHtmlCodeConnect)(payload);
|
|
86
87
|
}
|
|
88
|
+
else if (!projectInfo.config.parser) {
|
|
89
|
+
const filename = (0, create_template_1.createTemplateCodeConnect)({
|
|
90
|
+
component: componentPayload,
|
|
91
|
+
normalizedName,
|
|
92
|
+
destinationDir: outDir ?? process.env.INIT_CWD ?? process.cwd(),
|
|
93
|
+
destinationFile: outFile,
|
|
94
|
+
});
|
|
95
|
+
result = {
|
|
96
|
+
createdFiles: [{ filePath: filename }],
|
|
97
|
+
messages: [],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
87
100
|
else {
|
|
88
101
|
try {
|
|
89
102
|
const payload = {
|