figma-metadata-extractor 1.0.0 → 1.0.2
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/README.md +78 -1
- package/dist/extractors/built-in.d.ts +56 -0
- package/dist/extractors/design-extractor.d.ts +6 -0
- package/dist/extractors/index.d.ts +4 -0
- package/dist/extractors/node-walker.d.ts +15 -0
- package/dist/extractors/types.d.ts +72 -0
- package/dist/index.cjs +52 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +52 -0
- package/dist/lib.d.ts +92 -0
- package/dist/services/figma.d.ts +75 -0
- package/dist/transformers/component.d.ts +26 -0
- package/dist/transformers/effects.d.ts +8 -0
- package/dist/transformers/layout.d.ts +26 -0
- package/dist/transformers/style.d.ts +119 -0
- package/dist/transformers/text.d.ts +30 -0
- package/dist/utils/common.d.ts +69 -0
- package/dist/utils/fetch-with-retry.d.ts +12 -0
- package/dist/utils/identity.d.ts +23 -0
- package/dist/utils/image-processing.d.ts +57 -0
- package/dist/utils/logger.d.ts +6 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npm install figma-metadata-extractor
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
|
-
import { getFigmaMetadata, downloadFigmaImages } from 'figma-metadata-extractor';
|
|
14
|
+
import { getFigmaMetadata, downloadFigmaImages, downloadFigmaFrameImage } from 'figma-metadata-extractor';
|
|
15
15
|
|
|
16
16
|
// Extract metadata from a Figma file
|
|
17
17
|
const metadata = await getFigmaMetadata(
|
|
@@ -45,6 +45,20 @@ const images = await downloadFigmaImages(
|
|
|
45
45
|
);
|
|
46
46
|
|
|
47
47
|
console.log(images); // Array of download results
|
|
48
|
+
|
|
49
|
+
// Download a single frame image from a Figma URL
|
|
50
|
+
const frameImage = await downloadFigmaFrameImage(
|
|
51
|
+
'https://figma.com/file/ABC123/My-Design?node-id=1234-5678',
|
|
52
|
+
{
|
|
53
|
+
apiKey: 'your-figma-api-key',
|
|
54
|
+
localPath: './assets/frames',
|
|
55
|
+
fileName: 'my-frame.png',
|
|
56
|
+
format: 'png', // or 'svg'
|
|
57
|
+
pngScale: 2
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
console.log(frameImage.filePath); // Path to downloaded image
|
|
48
62
|
```
|
|
49
63
|
|
|
50
64
|
## API Reference
|
|
@@ -90,6 +104,25 @@ Downloads SVG and PNG images from a Figma file.
|
|
|
90
104
|
|
|
91
105
|
**Returns:** Promise<FigmaImageResult[]>
|
|
92
106
|
|
|
107
|
+
### `downloadFigmaFrameImage(figmaUrl, options)`
|
|
108
|
+
|
|
109
|
+
Downloads a single frame image from a Figma URL that contains a node-id parameter.
|
|
110
|
+
|
|
111
|
+
**Parameters:**
|
|
112
|
+
- `figmaUrl` (string): The Figma URL with node-id parameter (e.g., `https://figma.com/file/ABC123/My-Design?node-id=1234-5678`)
|
|
113
|
+
- `options` (FigmaFrameImageOptions): Configuration options
|
|
114
|
+
|
|
115
|
+
**Options:**
|
|
116
|
+
- `apiKey?: string` - Figma API key (Personal Access Token)
|
|
117
|
+
- `oauthToken?: string` - Figma OAuth Bearer token
|
|
118
|
+
- `useOAuth?: boolean` - Whether to use OAuth instead of API key
|
|
119
|
+
- `localPath: string` - Absolute path to save the image
|
|
120
|
+
- `fileName: string` - Local filename (must end with .png or .svg)
|
|
121
|
+
- `format?: 'png' | 'svg'` - Image format to download (default: 'png')
|
|
122
|
+
- `pngScale?: number` - Export scale for PNG images (default: 2)
|
|
123
|
+
|
|
124
|
+
**Returns:** Promise<FigmaImageResult>
|
|
125
|
+
|
|
93
126
|
## Authentication
|
|
94
127
|
|
|
95
128
|
You need either a Figma API key or OAuth token:
|
|
@@ -104,6 +137,50 @@ You need either a Figma API key or OAuth token:
|
|
|
104
137
|
2. Use the bearer token in the `oauthToken` option
|
|
105
138
|
3. Set `useOAuth: true`
|
|
106
139
|
|
|
140
|
+
## Usage Examples
|
|
141
|
+
|
|
142
|
+
### Download Frame Image from Figma URL
|
|
143
|
+
|
|
144
|
+
The easiest way to download a frame image is to copy the Figma URL directly from your browser when viewing a specific frame:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { downloadFigmaFrameImage } from 'figma-metadata-extractor';
|
|
148
|
+
|
|
149
|
+
// Copy this URL from Figma when viewing a frame
|
|
150
|
+
const figmaUrl = 'https://www.figma.com/design/ABC123/My-Design?node-id=1234-5678&t=xyz123';
|
|
151
|
+
|
|
152
|
+
const result = await downloadFigmaFrameImage(figmaUrl, {
|
|
153
|
+
apiKey: 'your-figma-api-key',
|
|
154
|
+
localPath: './downloads',
|
|
155
|
+
fileName: 'my-frame.png',
|
|
156
|
+
format: 'png',
|
|
157
|
+
pngScale: 2 // High resolution
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
console.log(`Downloaded to: ${result.filePath}`);
|
|
161
|
+
console.log(`Dimensions: ${result.finalDimensions.width}x${result.finalDimensions.height}`);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Download Multiple Frame Images
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { downloadFigmaImages } from 'figma-metadata-extractor';
|
|
168
|
+
|
|
169
|
+
// For multiple frames, use the batch download function
|
|
170
|
+
const results = await downloadFigmaImages(
|
|
171
|
+
'https://figma.com/file/ABC123/My-Design',
|
|
172
|
+
[
|
|
173
|
+
{ nodeId: '1234:5678', fileName: 'frame1.png' },
|
|
174
|
+
{ nodeId: '9876:5432', fileName: 'frame2.svg' },
|
|
175
|
+
{ nodeId: '1111:2222', fileName: 'frame3.png' }
|
|
176
|
+
],
|
|
177
|
+
{
|
|
178
|
+
apiKey: 'your-figma-api-key',
|
|
179
|
+
localPath: './frames'
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
```
|
|
183
|
+
|
|
107
184
|
## Advanced Usage
|
|
108
185
|
|
|
109
186
|
The library also exports the underlying extractor system for custom processing:
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ExtractorFn, SimplifiedNode } from "./types.js";
|
|
2
|
+
import type { Node as FigmaDocumentNode } from "@figma/rest-api-spec";
|
|
3
|
+
/**
|
|
4
|
+
* Extracts layout-related properties from a node.
|
|
5
|
+
*/
|
|
6
|
+
export declare const layoutExtractor: ExtractorFn;
|
|
7
|
+
/**
|
|
8
|
+
* Extracts text content and text styling from a node.
|
|
9
|
+
*/
|
|
10
|
+
export declare const textExtractor: ExtractorFn;
|
|
11
|
+
/**
|
|
12
|
+
* Extracts visual appearance properties (fills, strokes, effects, opacity, border radius).
|
|
13
|
+
*/
|
|
14
|
+
export declare const visualsExtractor: ExtractorFn;
|
|
15
|
+
/**
|
|
16
|
+
* Extracts component-related properties from INSTANCE nodes.
|
|
17
|
+
*/
|
|
18
|
+
export declare const componentExtractor: ExtractorFn;
|
|
19
|
+
/**
|
|
20
|
+
* All extractors - replicates the current parseNode behavior.
|
|
21
|
+
*/
|
|
22
|
+
export declare const allExtractors: ExtractorFn[];
|
|
23
|
+
/**
|
|
24
|
+
* Layout and text only - useful for content analysis and layout planning.
|
|
25
|
+
*/
|
|
26
|
+
export declare const layoutAndText: ExtractorFn[];
|
|
27
|
+
/**
|
|
28
|
+
* Text content only - useful for content audits and copy extraction.
|
|
29
|
+
*/
|
|
30
|
+
export declare const contentOnly: ExtractorFn[];
|
|
31
|
+
/**
|
|
32
|
+
* Visuals only - useful for design system analysis and style extraction.
|
|
33
|
+
*/
|
|
34
|
+
export declare const visualsOnly: ExtractorFn[];
|
|
35
|
+
/**
|
|
36
|
+
* Layout only - useful for structure analysis.
|
|
37
|
+
*/
|
|
38
|
+
export declare const layoutOnly: ExtractorFn[];
|
|
39
|
+
/**
|
|
40
|
+
* Node types that can be exported as SVG images.
|
|
41
|
+
* When a FRAME, GROUP, or INSTANCE contains only these types, we can collapse it to IMAGE-SVG.
|
|
42
|
+
* Note: FRAME/GROUP/INSTANCE are NOT included here—they're only eligible if collapsed to IMAGE-SVG.
|
|
43
|
+
*/
|
|
44
|
+
export declare const SVG_ELIGIBLE_TYPES: Set<string>;
|
|
45
|
+
/**
|
|
46
|
+
* afterChildren callback that collapses SVG-heavy containers to IMAGE-SVG.
|
|
47
|
+
*
|
|
48
|
+
* If a FRAME, GROUP, or INSTANCE contains only SVG-eligible children, the parent
|
|
49
|
+
* is marked as IMAGE-SVG and children are omitted, reducing payload size.
|
|
50
|
+
*
|
|
51
|
+
* @param node - Original Figma node
|
|
52
|
+
* @param result - SimplifiedNode being built
|
|
53
|
+
* @param children - Processed children
|
|
54
|
+
* @returns Children to include (empty array if collapsed)
|
|
55
|
+
*/
|
|
56
|
+
export declare function collapseSvgContainers(node: FigmaDocumentNode, result: SimplifiedNode, children: SimplifiedNode[]): SimplifiedNode[];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { GetFileResponse, GetFileNodesResponse } from "@figma/rest-api-spec";
|
|
2
|
+
import type { ExtractorFn, TraversalOptions, SimplifiedDesign } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Extract a complete SimplifiedDesign from raw Figma API response using extractors.
|
|
5
|
+
*/
|
|
6
|
+
export declare function simplifyRawFigmaObject(apiResponse: GetFileResponse | GetFileNodesResponse, nodeExtractors: ExtractorFn[], options?: TraversalOptions): SimplifiedDesign;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { ExtractorFn, TraversalContext, TraversalOptions, GlobalVars, StyleTypes, } from "./types.js";
|
|
2
|
+
export { extractFromDesign } from "./node-walker.js";
|
|
3
|
+
export { simplifyRawFigmaObject } from "./design-extractor.js";
|
|
4
|
+
export { layoutExtractor, textExtractor, visualsExtractor, componentExtractor, allExtractors, layoutAndText, contentOnly, visualsOnly, layoutOnly, collapseSvgContainers, SVG_ELIGIBLE_TYPES, } from "./built-in.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Node as FigmaDocumentNode } from "@figma/rest-api-spec";
|
|
2
|
+
import type { ExtractorFn, TraversalOptions, GlobalVars, SimplifiedNode } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Extract data from Figma nodes using a flexible, single-pass approach.
|
|
5
|
+
*
|
|
6
|
+
* @param nodes - The Figma nodes to process
|
|
7
|
+
* @param extractors - Array of extractor functions to apply during traversal
|
|
8
|
+
* @param options - Traversal options (filtering, depth limits, etc.)
|
|
9
|
+
* @param globalVars - Global variables for style deduplication
|
|
10
|
+
* @returns Object containing processed nodes and updated global variables
|
|
11
|
+
*/
|
|
12
|
+
export declare function extractFromDesign(nodes: FigmaDocumentNode[], extractors: ExtractorFn[], options?: TraversalOptions, globalVars?: GlobalVars): {
|
|
13
|
+
nodes: SimplifiedNode[];
|
|
14
|
+
globalVars: GlobalVars;
|
|
15
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Node as FigmaDocumentNode, Style } from "@figma/rest-api-spec";
|
|
2
|
+
import type { SimplifiedTextStyle } from "~/transformers/text.js";
|
|
3
|
+
import type { SimplifiedLayout } from "~/transformers/layout.js";
|
|
4
|
+
import type { SimplifiedFill, SimplifiedStroke } from "~/transformers/style.js";
|
|
5
|
+
import type { SimplifiedEffects } from "~/transformers/effects.js";
|
|
6
|
+
import type { ComponentProperties, SimplifiedComponentDefinition, SimplifiedComponentSetDefinition } from "~/transformers/component.js";
|
|
7
|
+
export type StyleTypes = SimplifiedTextStyle | SimplifiedFill[] | SimplifiedLayout | SimplifiedStroke | SimplifiedEffects | string;
|
|
8
|
+
export type GlobalVars = {
|
|
9
|
+
styles: Record<string, StyleTypes>;
|
|
10
|
+
};
|
|
11
|
+
export interface TraversalContext {
|
|
12
|
+
globalVars: GlobalVars & {
|
|
13
|
+
extraStyles?: Record<string, Style>;
|
|
14
|
+
};
|
|
15
|
+
currentDepth: number;
|
|
16
|
+
parent?: FigmaDocumentNode;
|
|
17
|
+
}
|
|
18
|
+
export interface TraversalOptions {
|
|
19
|
+
maxDepth?: number;
|
|
20
|
+
nodeFilter?: (node: FigmaDocumentNode) => boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Called after children are processed, allowing modification of the parent node
|
|
23
|
+
* and control over which children to include in the output.
|
|
24
|
+
*
|
|
25
|
+
* @param node - Original Figma node
|
|
26
|
+
* @param result - SimplifiedNode being built (can be mutated)
|
|
27
|
+
* @param children - Processed children
|
|
28
|
+
* @returns Children to include (return empty array to omit children)
|
|
29
|
+
*/
|
|
30
|
+
afterChildren?: (node: FigmaDocumentNode, result: SimplifiedNode, children: SimplifiedNode[]) => SimplifiedNode[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* An extractor function that can modify a SimplifiedNode during traversal.
|
|
34
|
+
*
|
|
35
|
+
* @param node - The current Figma node being processed
|
|
36
|
+
* @param result - SimplifiedNode object being built—this can be mutated inside the extractor
|
|
37
|
+
* @param context - Traversal context including globalVars and parent info. This can also be mutated inside the extractor.
|
|
38
|
+
*/
|
|
39
|
+
export type ExtractorFn = (node: FigmaDocumentNode, result: SimplifiedNode, context: TraversalContext) => void;
|
|
40
|
+
export interface SimplifiedDesign {
|
|
41
|
+
name: string;
|
|
42
|
+
nodes: SimplifiedNode[];
|
|
43
|
+
components: Record<string, SimplifiedComponentDefinition>;
|
|
44
|
+
componentSets: Record<string, SimplifiedComponentSetDefinition>;
|
|
45
|
+
globalVars: GlobalVars;
|
|
46
|
+
}
|
|
47
|
+
export interface SimplifiedNode {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
type: string;
|
|
51
|
+
text?: string;
|
|
52
|
+
textStyle?: string;
|
|
53
|
+
fills?: string;
|
|
54
|
+
styles?: string;
|
|
55
|
+
strokes?: string;
|
|
56
|
+
strokeWeight?: string;
|
|
57
|
+
strokeDashes?: number[];
|
|
58
|
+
strokeWeights?: string;
|
|
59
|
+
effects?: string;
|
|
60
|
+
opacity?: number;
|
|
61
|
+
borderRadius?: string;
|
|
62
|
+
layout?: string;
|
|
63
|
+
componentId?: string;
|
|
64
|
+
componentProperties?: ComponentProperties[];
|
|
65
|
+
children?: SimplifiedNode[];
|
|
66
|
+
}
|
|
67
|
+
export interface BoundingBox {
|
|
68
|
+
x: number;
|
|
69
|
+
y: number;
|
|
70
|
+
width: number;
|
|
71
|
+
height: number;
|
|
72
|
+
}
|
package/dist/index.cjs
CHANGED
|
@@ -1446,10 +1446,62 @@ async function downloadFigmaImages(figmaUrl, nodes, options) {
|
|
|
1446
1446
|
throw new Error(`Failed to download images: ${error instanceof Error ? error.message : String(error)}`);
|
|
1447
1447
|
}
|
|
1448
1448
|
}
|
|
1449
|
+
async function downloadFigmaFrameImage(figmaUrl, options) {
|
|
1450
|
+
const {
|
|
1451
|
+
apiKey,
|
|
1452
|
+
oauthToken,
|
|
1453
|
+
useOAuth = false,
|
|
1454
|
+
pngScale = 2,
|
|
1455
|
+
localPath,
|
|
1456
|
+
fileName,
|
|
1457
|
+
format = "png"
|
|
1458
|
+
} = options;
|
|
1459
|
+
if (!apiKey && !oauthToken) {
|
|
1460
|
+
throw new Error("Either apiKey or oauthToken is required");
|
|
1461
|
+
}
|
|
1462
|
+
const urlMatch = figmaUrl.match(/figma\.com\/(file|design)\/([a-zA-Z0-9]+)/);
|
|
1463
|
+
if (!urlMatch) {
|
|
1464
|
+
throw new Error("Invalid Figma URL format");
|
|
1465
|
+
}
|
|
1466
|
+
const fileKey = urlMatch[2];
|
|
1467
|
+
const nodeIdMatch = figmaUrl.match(/node-id=([^&]+)/);
|
|
1468
|
+
if (!nodeIdMatch) {
|
|
1469
|
+
throw new Error("No frame node-id found in URL. Please provide a Figma URL with a node-id parameter (e.g., ?node-id=123-456)");
|
|
1470
|
+
}
|
|
1471
|
+
const nodeId = nodeIdMatch[1].replace(/-/g, ":");
|
|
1472
|
+
const expectedExtension = `.${format}`;
|
|
1473
|
+
if (!fileName.toLowerCase().endsWith(expectedExtension)) {
|
|
1474
|
+
throw new Error(`Filename must end with ${expectedExtension} for ${format} format`);
|
|
1475
|
+
}
|
|
1476
|
+
const figmaService = new FigmaService({
|
|
1477
|
+
figmaApiKey: apiKey || "",
|
|
1478
|
+
figmaOAuthToken: oauthToken || "",
|
|
1479
|
+
useOAuth: useOAuth && !!oauthToken
|
|
1480
|
+
});
|
|
1481
|
+
try {
|
|
1482
|
+
Logger.log(`Downloading ${format.toUpperCase()} image for frame ${nodeId} from file ${fileKey}`);
|
|
1483
|
+
const imageNode = {
|
|
1484
|
+
nodeId,
|
|
1485
|
+
fileName
|
|
1486
|
+
};
|
|
1487
|
+
const results = await figmaService.downloadImages(fileKey, localPath, [imageNode], {
|
|
1488
|
+
pngScale: format === "png" ? pngScale : void 0
|
|
1489
|
+
});
|
|
1490
|
+
if (results.length === 0) {
|
|
1491
|
+
throw new Error(`Failed to download image for frame ${nodeId}`);
|
|
1492
|
+
}
|
|
1493
|
+
Logger.log(`Successfully downloaded frame image to: ${results[0].filePath}`);
|
|
1494
|
+
return results[0];
|
|
1495
|
+
} catch (error) {
|
|
1496
|
+
Logger.error(`Error downloading frame image from ${fileKey}:`, error);
|
|
1497
|
+
throw new Error(`Failed to download frame image: ${error instanceof Error ? error.message : String(error)}`);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1449
1500
|
exports.allExtractors = allExtractors;
|
|
1450
1501
|
exports.collapseSvgContainers = collapseSvgContainers;
|
|
1451
1502
|
exports.componentExtractor = componentExtractor;
|
|
1452
1503
|
exports.contentOnly = contentOnly;
|
|
1504
|
+
exports.downloadFigmaFrameImage = downloadFigmaFrameImage;
|
|
1453
1505
|
exports.downloadFigmaImages = downloadFigmaImages;
|
|
1454
1506
|
exports.extractFromDesign = extractFromDesign;
|
|
1455
1507
|
exports.getFigmaMetadata = getFigmaMetadata;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { getFigmaMetadata, downloadFigmaImages, downloadFigmaFrameImage, type FigmaMetadataOptions, type FigmaImageOptions, type FigmaFrameImageOptions, type FigmaImageNode, type FigmaMetadataResult, type FigmaImageResult, } from "./lib.js";
|
|
2
|
+
export type { SimplifiedDesign } from "./extractors/types.js";
|
|
3
|
+
export type { ExtractorFn, TraversalContext, TraversalOptions, GlobalVars, StyleTypes, } from "./extractors/index.js";
|
|
4
|
+
export { extractFromDesign, simplifyRawFigmaObject, layoutExtractor, textExtractor, visualsExtractor, componentExtractor, allExtractors, layoutAndText, contentOnly, visualsOnly, layoutOnly, collapseSvgContainers, } from "./extractors/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1444,11 +1444,63 @@ async function downloadFigmaImages(figmaUrl, nodes, options) {
|
|
|
1444
1444
|
throw new Error(`Failed to download images: ${error instanceof Error ? error.message : String(error)}`);
|
|
1445
1445
|
}
|
|
1446
1446
|
}
|
|
1447
|
+
async function downloadFigmaFrameImage(figmaUrl, options) {
|
|
1448
|
+
const {
|
|
1449
|
+
apiKey,
|
|
1450
|
+
oauthToken,
|
|
1451
|
+
useOAuth = false,
|
|
1452
|
+
pngScale = 2,
|
|
1453
|
+
localPath,
|
|
1454
|
+
fileName,
|
|
1455
|
+
format = "png"
|
|
1456
|
+
} = options;
|
|
1457
|
+
if (!apiKey && !oauthToken) {
|
|
1458
|
+
throw new Error("Either apiKey or oauthToken is required");
|
|
1459
|
+
}
|
|
1460
|
+
const urlMatch = figmaUrl.match(/figma\.com\/(file|design)\/([a-zA-Z0-9]+)/);
|
|
1461
|
+
if (!urlMatch) {
|
|
1462
|
+
throw new Error("Invalid Figma URL format");
|
|
1463
|
+
}
|
|
1464
|
+
const fileKey = urlMatch[2];
|
|
1465
|
+
const nodeIdMatch = figmaUrl.match(/node-id=([^&]+)/);
|
|
1466
|
+
if (!nodeIdMatch) {
|
|
1467
|
+
throw new Error("No frame node-id found in URL. Please provide a Figma URL with a node-id parameter (e.g., ?node-id=123-456)");
|
|
1468
|
+
}
|
|
1469
|
+
const nodeId = nodeIdMatch[1].replace(/-/g, ":");
|
|
1470
|
+
const expectedExtension = `.${format}`;
|
|
1471
|
+
if (!fileName.toLowerCase().endsWith(expectedExtension)) {
|
|
1472
|
+
throw new Error(`Filename must end with ${expectedExtension} for ${format} format`);
|
|
1473
|
+
}
|
|
1474
|
+
const figmaService = new FigmaService({
|
|
1475
|
+
figmaApiKey: apiKey || "",
|
|
1476
|
+
figmaOAuthToken: oauthToken || "",
|
|
1477
|
+
useOAuth: useOAuth && !!oauthToken
|
|
1478
|
+
});
|
|
1479
|
+
try {
|
|
1480
|
+
Logger.log(`Downloading ${format.toUpperCase()} image for frame ${nodeId} from file ${fileKey}`);
|
|
1481
|
+
const imageNode = {
|
|
1482
|
+
nodeId,
|
|
1483
|
+
fileName
|
|
1484
|
+
};
|
|
1485
|
+
const results = await figmaService.downloadImages(fileKey, localPath, [imageNode], {
|
|
1486
|
+
pngScale: format === "png" ? pngScale : void 0
|
|
1487
|
+
});
|
|
1488
|
+
if (results.length === 0) {
|
|
1489
|
+
throw new Error(`Failed to download image for frame ${nodeId}`);
|
|
1490
|
+
}
|
|
1491
|
+
Logger.log(`Successfully downloaded frame image to: ${results[0].filePath}`);
|
|
1492
|
+
return results[0];
|
|
1493
|
+
} catch (error) {
|
|
1494
|
+
Logger.error(`Error downloading frame image from ${fileKey}:`, error);
|
|
1495
|
+
throw new Error(`Failed to download frame image: ${error instanceof Error ? error.message : String(error)}`);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1447
1498
|
export {
|
|
1448
1499
|
allExtractors,
|
|
1449
1500
|
collapseSvgContainers,
|
|
1450
1501
|
componentExtractor,
|
|
1451
1502
|
contentOnly,
|
|
1503
|
+
downloadFigmaFrameImage,
|
|
1452
1504
|
downloadFigmaImages,
|
|
1453
1505
|
extractFromDesign,
|
|
1454
1506
|
getFigmaMetadata,
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export interface FigmaMetadataOptions {
|
|
2
|
+
/** The Figma API key (Personal Access Token) */
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
/** The Figma OAuth Bearer token */
|
|
5
|
+
oauthToken?: string;
|
|
6
|
+
/** Whether to use OAuth instead of API key */
|
|
7
|
+
useOAuth?: boolean;
|
|
8
|
+
/** Output format for the metadata */
|
|
9
|
+
outputFormat?: "json" | "yaml" | "object";
|
|
10
|
+
/** Maximum depth to traverse the node tree */
|
|
11
|
+
depth?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface FigmaImageOptions {
|
|
14
|
+
/** Export scale for PNG images (defaults to 2) */
|
|
15
|
+
pngScale?: number;
|
|
16
|
+
/** The absolute path to the directory where images should be stored */
|
|
17
|
+
localPath: string;
|
|
18
|
+
}
|
|
19
|
+
export interface FigmaImageNode {
|
|
20
|
+
/** The ID of the Figma node, formatted as '1234:5678' */
|
|
21
|
+
nodeId: string;
|
|
22
|
+
/** If a node has an imageRef fill, include this variable */
|
|
23
|
+
imageRef?: string;
|
|
24
|
+
/** The local filename for saving the image (must end with .png or .svg) */
|
|
25
|
+
fileName: string;
|
|
26
|
+
/** Whether this image needs cropping based on its transform matrix */
|
|
27
|
+
needsCropping?: boolean;
|
|
28
|
+
/** Figma transform matrix for image cropping */
|
|
29
|
+
cropTransform?: number[][];
|
|
30
|
+
/** Whether this image requires dimension information for CSS variables */
|
|
31
|
+
requiresImageDimensions?: boolean;
|
|
32
|
+
/** Suffix to add to filename for unique cropped images */
|
|
33
|
+
filenameSuffix?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface FigmaMetadataResult {
|
|
36
|
+
metadata: any;
|
|
37
|
+
nodes: any[];
|
|
38
|
+
globalVars: any;
|
|
39
|
+
}
|
|
40
|
+
export interface FigmaImageResult {
|
|
41
|
+
filePath: string;
|
|
42
|
+
finalDimensions: {
|
|
43
|
+
width: number;
|
|
44
|
+
height: number;
|
|
45
|
+
};
|
|
46
|
+
wasCropped: boolean;
|
|
47
|
+
cssVariables?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface FigmaFrameImageOptions {
|
|
50
|
+
/** The Figma API key (Personal Access Token) */
|
|
51
|
+
apiKey?: string;
|
|
52
|
+
/** The Figma OAuth Bearer token */
|
|
53
|
+
oauthToken?: string;
|
|
54
|
+
/** Whether to use OAuth instead of API key */
|
|
55
|
+
useOAuth?: boolean;
|
|
56
|
+
/** Export scale for PNG images (defaults to 2) */
|
|
57
|
+
pngScale?: number;
|
|
58
|
+
/** The absolute path to the directory where the image should be stored */
|
|
59
|
+
localPath: string;
|
|
60
|
+
/** The filename for the downloaded image (must end with .png or .svg) */
|
|
61
|
+
fileName: string;
|
|
62
|
+
/** Image format to download (defaults to 'png') */
|
|
63
|
+
format?: 'png' | 'svg';
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Extract metadata from a Figma file or specific nodes
|
|
67
|
+
*
|
|
68
|
+
* @param figmaUrl - The Figma file URL (e.g., https://figma.com/file/ABC123/...)
|
|
69
|
+
* @param options - Configuration options including API credentials
|
|
70
|
+
* @returns Promise resolving to the extracted metadata
|
|
71
|
+
*/
|
|
72
|
+
export declare function getFigmaMetadata(figmaUrl: string, options?: FigmaMetadataOptions): Promise<FigmaMetadataResult | string>;
|
|
73
|
+
/**
|
|
74
|
+
* Download images from a Figma file
|
|
75
|
+
*
|
|
76
|
+
* @param figmaUrl - The Figma file URL
|
|
77
|
+
* @param nodes - Array of image nodes to download
|
|
78
|
+
* @param options - Configuration options including API credentials and local path
|
|
79
|
+
* @returns Promise resolving to array of download results
|
|
80
|
+
*/
|
|
81
|
+
export declare function downloadFigmaImages(figmaUrl: string, nodes: FigmaImageNode[], options: FigmaMetadataOptions & FigmaImageOptions): Promise<FigmaImageResult[]>;
|
|
82
|
+
/**
|
|
83
|
+
* Download a frame image from a Figma URL
|
|
84
|
+
*
|
|
85
|
+
* @param figmaUrl - The Figma URL containing the frame (with node-id parameter)
|
|
86
|
+
* @param options - Configuration options including API credentials, local path, and filename
|
|
87
|
+
* @returns Promise resolving to the download result
|
|
88
|
+
*/
|
|
89
|
+
export declare function downloadFigmaFrameImage(figmaUrl: string, options: FigmaFrameImageOptions): Promise<FigmaImageResult>;
|
|
90
|
+
export type { SimplifiedDesign } from "./extractors/types.js";
|
|
91
|
+
export type { ExtractorFn, TraversalContext, TraversalOptions, GlobalVars, StyleTypes, } from "./extractors/index.js";
|
|
92
|
+
export { extractFromDesign, simplifyRawFigmaObject, layoutExtractor, textExtractor, visualsExtractor, componentExtractor, allExtractors, layoutAndText, contentOnly, visualsOnly, layoutOnly, collapseSvgContainers, } from "./extractors/index.js";
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { GetFileResponse, GetFileNodesResponse } from "@figma/rest-api-spec";
|
|
2
|
+
import { type ImageProcessingResult } from "~/utils/image-processing.js";
|
|
3
|
+
export type FigmaAuthOptions = {
|
|
4
|
+
figmaApiKey: string;
|
|
5
|
+
figmaOAuthToken: string;
|
|
6
|
+
useOAuth: boolean;
|
|
7
|
+
};
|
|
8
|
+
type SvgOptions = {
|
|
9
|
+
outlineText: boolean;
|
|
10
|
+
includeId: boolean;
|
|
11
|
+
simplifyStroke: boolean;
|
|
12
|
+
};
|
|
13
|
+
export declare class FigmaService {
|
|
14
|
+
private readonly apiKey;
|
|
15
|
+
private readonly oauthToken;
|
|
16
|
+
private readonly useOAuth;
|
|
17
|
+
private readonly baseUrl;
|
|
18
|
+
constructor({ figmaApiKey, figmaOAuthToken, useOAuth }: FigmaAuthOptions);
|
|
19
|
+
private getAuthHeaders;
|
|
20
|
+
/**
|
|
21
|
+
* Filters out null values from Figma image responses. This ensures we only work with valid image URLs.
|
|
22
|
+
*/
|
|
23
|
+
private filterValidImages;
|
|
24
|
+
private request;
|
|
25
|
+
/**
|
|
26
|
+
* Builds URL query parameters for SVG image requests.
|
|
27
|
+
*/
|
|
28
|
+
private buildSvgQueryParams;
|
|
29
|
+
/**
|
|
30
|
+
* Gets download URLs for image fills without downloading them.
|
|
31
|
+
*
|
|
32
|
+
* @returns Map of imageRef to download URL
|
|
33
|
+
*/
|
|
34
|
+
getImageFillUrls(fileKey: string): Promise<Record<string, string>>;
|
|
35
|
+
/**
|
|
36
|
+
* Gets download URLs for rendered nodes without downloading them.
|
|
37
|
+
*
|
|
38
|
+
* @returns Map of node ID to download URL
|
|
39
|
+
*/
|
|
40
|
+
getNodeRenderUrls(fileKey: string, nodeIds: string[], format: "png" | "svg", options?: {
|
|
41
|
+
pngScale?: number;
|
|
42
|
+
svgOptions?: SvgOptions;
|
|
43
|
+
}): Promise<Record<string, string>>;
|
|
44
|
+
/**
|
|
45
|
+
* Download images method with post-processing support for cropping and returning image dimensions.
|
|
46
|
+
*
|
|
47
|
+
* Supports:
|
|
48
|
+
* - Image fills vs rendered nodes (based on imageRef vs nodeId)
|
|
49
|
+
* - PNG vs SVG format (based on filename extension)
|
|
50
|
+
* - Image cropping based on transform matrices
|
|
51
|
+
* - CSS variable generation for image dimensions
|
|
52
|
+
*
|
|
53
|
+
* @returns Array of local file paths for successfully downloaded images
|
|
54
|
+
*/
|
|
55
|
+
downloadImages(fileKey: string, localPath: string, items: Array<{
|
|
56
|
+
imageRef?: string;
|
|
57
|
+
nodeId?: string;
|
|
58
|
+
fileName: string;
|
|
59
|
+
needsCropping?: boolean;
|
|
60
|
+
cropTransform?: any;
|
|
61
|
+
requiresImageDimensions?: boolean;
|
|
62
|
+
}>, options?: {
|
|
63
|
+
pngScale?: number;
|
|
64
|
+
svgOptions?: SvgOptions;
|
|
65
|
+
}): Promise<ImageProcessingResult[]>;
|
|
66
|
+
/**
|
|
67
|
+
* Get raw Figma API response for a file (for use with flexible extractors)
|
|
68
|
+
*/
|
|
69
|
+
getRawFile(fileKey: string, depth?: number | null): Promise<GetFileResponse>;
|
|
70
|
+
/**
|
|
71
|
+
* Get raw Figma API response for specific nodes (for use with flexible extractors)
|
|
72
|
+
*/
|
|
73
|
+
getRawNode(fileKey: string, nodeId: string, depth?: number | null): Promise<GetFileNodesResponse>;
|
|
74
|
+
}
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Component, ComponentPropertyType, ComponentSet } from "@figma/rest-api-spec";
|
|
2
|
+
export interface ComponentProperties {
|
|
3
|
+
name: string;
|
|
4
|
+
value: string;
|
|
5
|
+
type: ComponentPropertyType;
|
|
6
|
+
}
|
|
7
|
+
export interface SimplifiedComponentDefinition {
|
|
8
|
+
id: string;
|
|
9
|
+
key: string;
|
|
10
|
+
name: string;
|
|
11
|
+
componentSetId?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface SimplifiedComponentSetDefinition {
|
|
14
|
+
id: string;
|
|
15
|
+
key: string;
|
|
16
|
+
name: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Remove unnecessary component properties and convert to simplified format.
|
|
21
|
+
*/
|
|
22
|
+
export declare function simplifyComponents(aggregatedComponents: Record<string, Component>): Record<string, SimplifiedComponentDefinition>;
|
|
23
|
+
/**
|
|
24
|
+
* Remove unnecessary component set properties and convert to simplified format.
|
|
25
|
+
*/
|
|
26
|
+
export declare function simplifyComponentSets(aggregatedComponentSets: Record<string, ComponentSet>): Record<string, SimplifiedComponentSetDefinition>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Node as FigmaDocumentNode } from "@figma/rest-api-spec";
|
|
2
|
+
export type SimplifiedEffects = {
|
|
3
|
+
boxShadow?: string;
|
|
4
|
+
filter?: string;
|
|
5
|
+
backdropFilter?: string;
|
|
6
|
+
textShadow?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function buildSimplifiedEffects(n: FigmaDocumentNode): SimplifiedEffects;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Node as FigmaDocumentNode } from "@figma/rest-api-spec";
|
|
2
|
+
export interface SimplifiedLayout {
|
|
3
|
+
mode: "none" | "row" | "column";
|
|
4
|
+
justifyContent?: "flex-start" | "flex-end" | "center" | "space-between" | "baseline" | "stretch";
|
|
5
|
+
alignItems?: "flex-start" | "flex-end" | "center" | "space-between" | "baseline" | "stretch";
|
|
6
|
+
alignSelf?: "flex-start" | "flex-end" | "center" | "stretch";
|
|
7
|
+
wrap?: boolean;
|
|
8
|
+
gap?: string;
|
|
9
|
+
locationRelativeToParent?: {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
};
|
|
13
|
+
dimensions?: {
|
|
14
|
+
width?: number;
|
|
15
|
+
height?: number;
|
|
16
|
+
aspectRatio?: number;
|
|
17
|
+
};
|
|
18
|
+
padding?: string;
|
|
19
|
+
sizing?: {
|
|
20
|
+
horizontal?: "fixed" | "fill" | "hug";
|
|
21
|
+
vertical?: "fixed" | "fill" | "hug";
|
|
22
|
+
};
|
|
23
|
+
overflowScroll?: ("x" | "y")[];
|
|
24
|
+
position?: "absolute";
|
|
25
|
+
}
|
|
26
|
+
export declare function buildSimplifiedLayout(n: FigmaDocumentNode, parent?: FigmaDocumentNode): SimplifiedLayout;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { Node as FigmaDocumentNode, Paint, RGBA, Transform } from "@figma/rest-api-spec";
|
|
2
|
+
export type CSSRGBAColor = `rgba(${number}, ${number}, ${number}, ${number})`;
|
|
3
|
+
export type CSSHexColor = `#${string}`;
|
|
4
|
+
export interface ColorValue {
|
|
5
|
+
hex: CSSHexColor;
|
|
6
|
+
opacity: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Simplified image fill with CSS properties and processing metadata
|
|
10
|
+
*
|
|
11
|
+
* This type represents an image fill that can be used as either:
|
|
12
|
+
* - background-image (when parent node has children)
|
|
13
|
+
* - <img> tag (when parent node has no children)
|
|
14
|
+
*
|
|
15
|
+
* The CSS properties are mutually exclusive based on usage context.
|
|
16
|
+
*/
|
|
17
|
+
export type SimplifiedImageFill = {
|
|
18
|
+
type: "IMAGE";
|
|
19
|
+
imageRef: string;
|
|
20
|
+
scaleMode: "FILL" | "FIT" | "TILE" | "STRETCH";
|
|
21
|
+
/**
|
|
22
|
+
* For TILE mode, the scaling factor relative to original image size
|
|
23
|
+
*/
|
|
24
|
+
scalingFactor?: number;
|
|
25
|
+
backgroundSize?: string;
|
|
26
|
+
backgroundRepeat?: string;
|
|
27
|
+
isBackground?: boolean;
|
|
28
|
+
objectFit?: string;
|
|
29
|
+
imageDownloadArguments?: {
|
|
30
|
+
/**
|
|
31
|
+
* Whether image needs cropping based on transform
|
|
32
|
+
*/
|
|
33
|
+
needsCropping: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Whether CSS variables for dimensions are needed to calculate the background size for TILE mode
|
|
36
|
+
*
|
|
37
|
+
* Figma bases scalingFactor on the image's original size. In CSS, background size (as a percentage)
|
|
38
|
+
* is calculated based on the size of the container. We need to pass back the original dimensions
|
|
39
|
+
* after processing to calculate the intended background size when translated to code.
|
|
40
|
+
*/
|
|
41
|
+
requiresImageDimensions: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Figma's transform matrix for Sharp processing
|
|
44
|
+
*/
|
|
45
|
+
cropTransform?: Transform;
|
|
46
|
+
/**
|
|
47
|
+
* Suggested filename suffix to make cropped images unique
|
|
48
|
+
* When the same imageRef is used multiple times with different crops,
|
|
49
|
+
* this helps avoid overwriting conflicts
|
|
50
|
+
*/
|
|
51
|
+
filenameSuffix?: string;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
export type SimplifiedGradientFill = {
|
|
55
|
+
type: "GRADIENT_LINEAR" | "GRADIENT_RADIAL" | "GRADIENT_ANGULAR" | "GRADIENT_DIAMOND";
|
|
56
|
+
gradient: string;
|
|
57
|
+
};
|
|
58
|
+
export type SimplifiedPatternFill = {
|
|
59
|
+
type: "PATTERN";
|
|
60
|
+
patternSource: {
|
|
61
|
+
/**
|
|
62
|
+
* Hardcode to expect PNG for now, consider SVG detection in the future.
|
|
63
|
+
*
|
|
64
|
+
* SVG detection is a bit challenging because the nodeId in question isn't
|
|
65
|
+
* guaranteed to be included in the response we're working with. No guaranteed
|
|
66
|
+
* way to look into it and see if it's only composed of vector shapes.
|
|
67
|
+
*/
|
|
68
|
+
type: "IMAGE-PNG";
|
|
69
|
+
nodeId: string;
|
|
70
|
+
};
|
|
71
|
+
backgroundRepeat: string;
|
|
72
|
+
backgroundSize: string;
|
|
73
|
+
backgroundPosition: string;
|
|
74
|
+
};
|
|
75
|
+
export type SimplifiedFill = SimplifiedImageFill | SimplifiedGradientFill | SimplifiedPatternFill | CSSRGBAColor | CSSHexColor;
|
|
76
|
+
export type SimplifiedStroke = {
|
|
77
|
+
colors: SimplifiedFill[];
|
|
78
|
+
strokeWeight?: string;
|
|
79
|
+
strokeDashes?: number[];
|
|
80
|
+
strokeWeights?: string;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Build simplified stroke information from a Figma node
|
|
84
|
+
*
|
|
85
|
+
* @param n - The Figma node to extract stroke information from
|
|
86
|
+
* @param hasChildren - Whether the node has children (affects paint processing)
|
|
87
|
+
* @returns Simplified stroke object with colors and properties
|
|
88
|
+
*/
|
|
89
|
+
export declare function buildSimplifiedStrokes(n: FigmaDocumentNode, hasChildren?: boolean): SimplifiedStroke;
|
|
90
|
+
/**
|
|
91
|
+
* Convert a Figma paint (solid, image, gradient) to a SimplifiedFill
|
|
92
|
+
* @param raw - The Figma paint to convert
|
|
93
|
+
* @param hasChildren - Whether the node has children (determines CSS properties)
|
|
94
|
+
* @returns The converted SimplifiedFill
|
|
95
|
+
*/
|
|
96
|
+
export declare function parsePaint(raw: Paint, hasChildren?: boolean): SimplifiedFill;
|
|
97
|
+
/**
|
|
98
|
+
* Convert hex color value and opacity to rgba format
|
|
99
|
+
* @param hex - Hexadecimal color value (e.g., "#FF0000" or "#F00")
|
|
100
|
+
* @param opacity - Opacity value (0-1)
|
|
101
|
+
* @returns Color string in rgba format
|
|
102
|
+
*/
|
|
103
|
+
export declare function hexToRgba(hex: string, opacity?: number): string;
|
|
104
|
+
/**
|
|
105
|
+
* Convert color from RGBA to { hex, opacity }
|
|
106
|
+
*
|
|
107
|
+
* @param color - The color to convert, including alpha channel
|
|
108
|
+
* @param opacity - The opacity of the color, if not included in alpha channel
|
|
109
|
+
* @returns The converted color
|
|
110
|
+
**/
|
|
111
|
+
export declare function convertColor(color: RGBA, opacity?: number): ColorValue;
|
|
112
|
+
/**
|
|
113
|
+
* Convert color from Figma RGBA to rgba(#, #, #, #) CSS format
|
|
114
|
+
*
|
|
115
|
+
* @param color - The color to convert, including alpha channel
|
|
116
|
+
* @param opacity - The opacity of the color, if not included in alpha channel
|
|
117
|
+
* @returns The converted color
|
|
118
|
+
**/
|
|
119
|
+
export declare function formatRGBAColor(color: RGBA, opacity?: number): CSSRGBAColor;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Node as FigmaDocumentNode } from "@figma/rest-api-spec";
|
|
2
|
+
export type SimplifiedTextStyle = Partial<{
|
|
3
|
+
fontFamily: string;
|
|
4
|
+
fontWeight: number;
|
|
5
|
+
fontSize: number;
|
|
6
|
+
lineHeight: string;
|
|
7
|
+
letterSpacing: string;
|
|
8
|
+
textCase: string;
|
|
9
|
+
textAlignHorizontal: string;
|
|
10
|
+
textAlignVertical: string;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function isTextNode(n: FigmaDocumentNode): n is Extract<FigmaDocumentNode, {
|
|
13
|
+
type: "TEXT";
|
|
14
|
+
}>;
|
|
15
|
+
export declare function hasTextStyle(n: FigmaDocumentNode): n is FigmaDocumentNode & {
|
|
16
|
+
style: Extract<FigmaDocumentNode, {
|
|
17
|
+
style: any;
|
|
18
|
+
}>["style"];
|
|
19
|
+
};
|
|
20
|
+
export declare function extractNodeText(n: FigmaDocumentNode): string | undefined;
|
|
21
|
+
export declare function extractTextStyle(n: FigmaDocumentNode): Partial<{
|
|
22
|
+
fontFamily: string;
|
|
23
|
+
fontWeight: number;
|
|
24
|
+
fontSize: number;
|
|
25
|
+
lineHeight: string;
|
|
26
|
+
letterSpacing: string;
|
|
27
|
+
textCase: string;
|
|
28
|
+
textAlignHorizontal: string;
|
|
29
|
+
textAlignVertical: string;
|
|
30
|
+
}> | undefined;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export type StyleId = `${string}_${string}` & {
|
|
2
|
+
__brand: "StyleId";
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Download Figma image and save it locally
|
|
6
|
+
* @param fileName - The filename to save as
|
|
7
|
+
* @param localPath - The local path to save to
|
|
8
|
+
* @param imageUrl - Image URL (images[nodeId])
|
|
9
|
+
* @returns A Promise that resolves to the full file path where the image was saved
|
|
10
|
+
* @throws Error if download fails
|
|
11
|
+
*/
|
|
12
|
+
export declare function downloadFigmaImage(fileName: string, localPath: string, imageUrl: string): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Remove keys with empty arrays or empty objects from an object.
|
|
15
|
+
* @param input - The input object or value.
|
|
16
|
+
* @returns The processed object or the original value.
|
|
17
|
+
*/
|
|
18
|
+
export declare function removeEmptyKeys<T>(input: T): T;
|
|
19
|
+
/**
|
|
20
|
+
* Generate a 6-character random variable ID
|
|
21
|
+
* @param prefix - ID prefix
|
|
22
|
+
* @returns A 6-character random ID string with prefix
|
|
23
|
+
*/
|
|
24
|
+
export declare function generateVarId(prefix?: string): StyleId;
|
|
25
|
+
/**
|
|
26
|
+
* Generate a CSS shorthand for values that come with top, right, bottom, and left
|
|
27
|
+
*
|
|
28
|
+
* input: { top: 10, right: 10, bottom: 10, left: 10 }
|
|
29
|
+
* output: "10px"
|
|
30
|
+
*
|
|
31
|
+
* input: { top: 10, right: 20, bottom: 10, left: 20 }
|
|
32
|
+
* output: "10px 20px"
|
|
33
|
+
*
|
|
34
|
+
* input: { top: 10, right: 20, bottom: 30, left: 40 }
|
|
35
|
+
* output: "10px 20px 30px 40px"
|
|
36
|
+
*
|
|
37
|
+
* @param values - The values to generate the shorthand for
|
|
38
|
+
* @returns The generated shorthand
|
|
39
|
+
*/
|
|
40
|
+
export declare function generateCSSShorthand(values: {
|
|
41
|
+
top: number;
|
|
42
|
+
right: number;
|
|
43
|
+
bottom: number;
|
|
44
|
+
left: number;
|
|
45
|
+
}, { ignoreZero, suffix, }?: {
|
|
46
|
+
/**
|
|
47
|
+
* If true and all values are 0, return undefined. Defaults to true.
|
|
48
|
+
*/
|
|
49
|
+
ignoreZero?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* The suffix to add to the shorthand. Defaults to "px".
|
|
52
|
+
*/
|
|
53
|
+
suffix?: string;
|
|
54
|
+
}): string | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Check if an element is visible
|
|
57
|
+
* @param element - The item to check
|
|
58
|
+
* @returns True if the item is visible, false otherwise
|
|
59
|
+
*/
|
|
60
|
+
export declare function isVisible(element: {
|
|
61
|
+
visible?: boolean;
|
|
62
|
+
}): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Rounds a number to two decimal places, suitable for pixel value processing.
|
|
65
|
+
* @param num The number to be rounded.
|
|
66
|
+
* @returns The rounded number with two decimal places.
|
|
67
|
+
* @throws TypeError If the input is not a valid number
|
|
68
|
+
*/
|
|
69
|
+
export declare function pixelRound(num: number): number;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type RequestOptions = RequestInit & {
|
|
2
|
+
/**
|
|
3
|
+
* Force format of headers to be a record of strings, e.g. { "Authorization": "Bearer 123" }
|
|
4
|
+
*
|
|
5
|
+
* Avoids complexity of needing to deal with `instanceof Headers`, which is not supported in some environments.
|
|
6
|
+
*/
|
|
7
|
+
headers?: Record<string, string>;
|
|
8
|
+
};
|
|
9
|
+
export declare function fetchWithRetry<T extends {
|
|
10
|
+
status?: number;
|
|
11
|
+
}>(url: string, options?: RequestOptions): Promise<T>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Rectangle, HasLayoutTrait, StrokeWeights, HasFramePropertiesTrait } from "@figma/rest-api-spec";
|
|
2
|
+
import { isTruthy } from "remeda";
|
|
3
|
+
import type { CSSHexColor, CSSRGBAColor } from "~/transformers/style.js";
|
|
4
|
+
export { isTruthy };
|
|
5
|
+
export declare function hasValue<K extends PropertyKey, T>(key: K, obj: unknown, typeGuard?: (val: unknown) => val is T): obj is Record<K, T>;
|
|
6
|
+
export declare function isFrame(val: unknown): val is HasFramePropertiesTrait;
|
|
7
|
+
export declare function isLayout(val: unknown): val is HasLayoutTrait;
|
|
8
|
+
/**
|
|
9
|
+
* Checks if:
|
|
10
|
+
* 1. A node is a child to an auto layout frame
|
|
11
|
+
* 2. The child adheres to the auto layout rules—i.e. it's not absolutely positioned
|
|
12
|
+
*
|
|
13
|
+
* @param node - The node to check.
|
|
14
|
+
* @param parent - The parent node.
|
|
15
|
+
* @returns True if the node is a child of an auto layout frame, false otherwise.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isInAutoLayoutFlow(node: unknown, parent: unknown): boolean;
|
|
18
|
+
export declare function isStrokeWeights(val: unknown): val is StrokeWeights;
|
|
19
|
+
export declare function isRectangle<T, K extends string>(key: K, obj: T): obj is T & {
|
|
20
|
+
[P in K]: Rectangle;
|
|
21
|
+
};
|
|
22
|
+
export declare function isRectangleCornerRadii(val: unknown): val is number[];
|
|
23
|
+
export declare function isCSSColorValue(val: unknown): val is CSSRGBAColor | CSSHexColor;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Transform } from "@figma/rest-api-spec";
|
|
2
|
+
/**
|
|
3
|
+
* Apply crop transform to an image based on Figma's transformation matrix
|
|
4
|
+
* @param imagePath - Path to the original image file
|
|
5
|
+
* @param cropTransform - Figma transform matrix [[scaleX, skewX, translateX], [skewY, scaleY, translateY]]
|
|
6
|
+
* @returns Promise<string> - Path to the cropped image
|
|
7
|
+
*/
|
|
8
|
+
export declare function applyCropTransform(imagePath: string, cropTransform: Transform): Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* Get image dimensions from a file
|
|
11
|
+
* @param imagePath - Path to the image file
|
|
12
|
+
* @returns Promise<{width: number, height: number}>
|
|
13
|
+
*/
|
|
14
|
+
export declare function getImageDimensions(imagePath: string): Promise<{
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
}>;
|
|
18
|
+
export type ImageProcessingResult = {
|
|
19
|
+
filePath: string;
|
|
20
|
+
originalDimensions: {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
};
|
|
24
|
+
finalDimensions: {
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
};
|
|
28
|
+
wasCropped: boolean;
|
|
29
|
+
cropRegion?: {
|
|
30
|
+
left: number;
|
|
31
|
+
top: number;
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
};
|
|
35
|
+
cssVariables?: string;
|
|
36
|
+
processingLog: string[];
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Enhanced image download with post-processing
|
|
40
|
+
* @param fileName - The filename to save as
|
|
41
|
+
* @param localPath - The local path to save to
|
|
42
|
+
* @param imageUrl - Image URL
|
|
43
|
+
* @param needsCropping - Whether to apply crop transform
|
|
44
|
+
* @param cropTransform - Transform matrix for cropping
|
|
45
|
+
* @param requiresImageDimensions - Whether to generate dimension metadata
|
|
46
|
+
* @returns Promise<ImageProcessingResult> - Detailed processing information
|
|
47
|
+
*/
|
|
48
|
+
export declare function downloadAndProcessImage(fileName: string, localPath: string, imageUrl: string, needsCropping?: boolean, cropTransform?: Transform, requiresImageDimensions?: boolean): Promise<ImageProcessingResult>;
|
|
49
|
+
/**
|
|
50
|
+
* Create CSS custom properties for image dimensions
|
|
51
|
+
* @param imagePath - Path to the image file
|
|
52
|
+
* @returns Promise<string> - CSS custom properties
|
|
53
|
+
*/
|
|
54
|
+
export declare function generateImageCSSVariables({ width, height, }: {
|
|
55
|
+
width: number;
|
|
56
|
+
height: number;
|
|
57
|
+
}): string;
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "figma-metadata-extractor",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Extract metadata and download images from Figma files. A standalone library for accessing Figma design data programmatically.",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Extract metadata and download images from Figma files. A standalone library for accessing Figma design data and downloading frame images programmatically.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
11
12
|
"import": "./dist/index.js",
|
|
12
|
-
"require": "./dist/index.cjs"
|
|
13
|
-
"types": "./dist/index.d.ts"
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"example.js"
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
|
-
"build": "vite build && tsc --
|
|
22
|
+
"build": "vite build && tsc --project tsconfig.declarations.json",
|
|
23
23
|
"typecheck": "tsc --noEmit",
|
|
24
24
|
"test": "jest",
|
|
25
25
|
"dev": "vite build --watch",
|