@grida/refig 0.0.1 → 0.0.3
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 +42 -35
- package/dist/browser.mjs +1 -1
- package/dist/{chunk-PK5L35ID.mjs → chunk-YI7PIULZ.mjs} +88 -10
- package/dist/cli.mjs +42 -19
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@grida/refig`
|
|
2
2
|
|
|
3
|
-
> **re**nder **fig**ma — headless Figma renderer (Node.js + browser) in the spirit of [`resvg
|
|
3
|
+
> **re**nder **fig**ma — headless Figma renderer (Node.js + browser) in the spirit of [`resvg`](https://github.com/linebender/resvg)
|
|
4
4
|
|
|
5
5
|
Render Figma documents to **PNG, JPEG, WebP, PDF, and SVG** in **Node.js (no browser required)** or directly in the **browser**.
|
|
6
6
|
|
|
@@ -194,8 +194,9 @@ pnpm add -g @grida/refig
|
|
|
194
194
|
Or run without installing:
|
|
195
195
|
|
|
196
196
|
```sh
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
# Instant usage (writes to OS temp dir; output path printed)
|
|
198
|
+
npx @grida/refig <input> --node <node-id> --format png
|
|
199
|
+
pnpm dlx @grida/refig <input> --export-all
|
|
199
200
|
```
|
|
200
201
|
|
|
201
202
|
### Usage
|
|
@@ -211,44 +212,50 @@ Using a directory avoids passing the document and images separately.
|
|
|
211
212
|
|
|
212
213
|
```sh
|
|
213
214
|
# Single node (default)
|
|
214
|
-
|
|
215
|
+
# - Without --out: writes to OS temp dir (requires --format)
|
|
216
|
+
# - With --out: format inferred from file extension unless --format is provided
|
|
217
|
+
refig <input> --node <node-id> --format <fmt> [options]
|
|
218
|
+
refig <input> --node <node-id> --out <path> [--format <fmt>] [options]
|
|
215
219
|
|
|
216
220
|
# With images directory (REST JSON only; IMAGE fills rendered from local files)
|
|
217
|
-
refig <input> --images <dir> --node <node-id> --
|
|
221
|
+
refig <input> --images <dir> --node <node-id> --format <fmt> [options]
|
|
222
|
+
refig <input> --images <dir> --node <node-id> --out <path> [--format <fmt>] [options]
|
|
218
223
|
|
|
219
224
|
# Directory input: document.json + images/ under one folder
|
|
220
|
-
refig ./my-figma-export --node "1:23" --
|
|
225
|
+
refig ./my-figma-export --node "1:23" --format png
|
|
221
226
|
|
|
222
227
|
# Export all nodes that have exportSettings (REST JSON or .fig)
|
|
223
|
-
refig <input> --export-all --out <output-dir>
|
|
228
|
+
refig <input> --export-all [--out <output-dir>]
|
|
224
229
|
```
|
|
225
230
|
|
|
226
231
|
### Examples
|
|
227
232
|
|
|
228
233
|
```sh
|
|
229
|
-
#
|
|
230
|
-
refig ./design.fig --node "1:23" --
|
|
231
|
-
|
|
232
|
-
# Render from REST API JSON
|
|
233
|
-
refig ./figma-response.json --node "1:23" --out ./out.svg
|
|
234
|
+
# Instant usage: omit --out to write to OS temp directory (output path printed)
|
|
235
|
+
refig ./design.fig --node "1:23" --format png
|
|
236
|
+
refig ./figma-response.json --node "1:23" --format svg
|
|
234
237
|
|
|
235
238
|
# Directory with document.json (and optionally images/): one path instead of response + --images
|
|
236
|
-
refig ./my-figma-export --node "1:23" --
|
|
239
|
+
refig ./my-figma-export --node "1:23" --format png
|
|
237
240
|
# (my-figma-export/document.json, my-figma-export/images/)
|
|
238
241
|
|
|
239
242
|
# Explicit images directory (when not using a project directory)
|
|
240
|
-
refig ./figma-response.json --images ./downloaded-images --node "1:23" --
|
|
243
|
+
refig ./figma-response.json --images ./downloaded-images --node "1:23" --format png
|
|
241
244
|
|
|
242
245
|
# Export all: render every node that has export settings (see below)
|
|
243
|
-
refig ./figma-response.json --export-all
|
|
244
|
-
refig ./design.fig --export-all
|
|
246
|
+
refig ./figma-response.json --export-all
|
|
247
|
+
refig ./design.fig --export-all
|
|
245
248
|
|
|
246
249
|
# Scale 2x, custom dimensions
|
|
247
|
-
refig ./design.fig --node "1:23" --
|
|
250
|
+
refig ./design.fig --node "1:23" --format png --width 512 --height 512 --scale 2
|
|
251
|
+
|
|
252
|
+
# Deterministic output: provide --out (useful for CI or saving into a known path)
|
|
253
|
+
refig ./design.fig --node "1:23" --out ./out.png
|
|
254
|
+
refig ./figma-response.json --export-all --out ./exports
|
|
248
255
|
|
|
249
256
|
# No-install (run without installing)
|
|
250
|
-
npx @grida/refig ./design.fig --node "1:23" --
|
|
251
|
-
pnpm dlx @grida/refig ./design.fig --
|
|
257
|
+
npx @grida/refig ./design.fig --node "1:23" --format png
|
|
258
|
+
pnpm dlx @grida/refig ./design.fig --export-all
|
|
252
259
|
```
|
|
253
260
|
|
|
254
261
|
### Quick test via `figma_archive.py` (REST API → `document.json` + `images/`)
|
|
@@ -275,10 +282,10 @@ This writes:
|
|
|
275
282
|
|
|
276
283
|
```sh
|
|
277
284
|
# Single node
|
|
278
|
-
refig ./my-figma-export --node "1:23" --
|
|
285
|
+
refig ./my-figma-export --node "1:23" --format png
|
|
279
286
|
|
|
280
287
|
# Or export everything with Figma export presets
|
|
281
|
-
refig ./my-figma-export --export-all
|
|
288
|
+
refig ./my-figma-export --export-all
|
|
282
289
|
```
|
|
283
290
|
|
|
284
291
|
### Export all (`--export-all`)
|
|
@@ -291,17 +298,17 @@ With **`--export-all`**, refig walks the document and renders every node that ha
|
|
|
291
298
|
|
|
292
299
|
### Flags
|
|
293
300
|
|
|
294
|
-
| Flag | Required | Default | Description
|
|
295
|
-
| ---------------- | -------- | ------------------------------- |
|
|
296
|
-
| `<input>` | yes | | Path to `.fig`, JSON file, or directory containing `document.json` (and optionally `images/`)
|
|
297
|
-
| `--images <dir>` | no | | Directory of image assets for REST document (ignored if `<input>` is a dir with `images/`)
|
|
298
|
-
| `--node <id>` | yes\* | | Figma node ID to render (\*omit when using `--export-all`)
|
|
299
|
-
| `--out <path>` |
|
|
300
|
-
| `--export-all` | no | | Export every node with exportSettings (REST JSON or .fig); `--out` is a directory
|
|
301
|
-
| `--format <fmt>` | no | inferred from `--out` extension | `png`, `jpeg`, `webp`, `pdf`, `svg` (single-node only)
|
|
302
|
-
| `--width <px>` | no | `1024` | Viewport width (single-node only)
|
|
303
|
-
| `--height <px>` | no | `1024` | Viewport height (single-node only)
|
|
304
|
-
| `--scale <n>` | no | `1` | Raster scale factor (single-node only)
|
|
301
|
+
| Flag | Required | Default | Description |
|
|
302
|
+
| ---------------- | -------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
303
|
+
| `<input>` | yes | | Path to `.fig`, JSON file, or directory containing `document.json` (and optionally `images/`) |
|
|
304
|
+
| `--images <dir>` | no | | Directory of image assets for REST document (ignored if `<input>` is a dir with `images/`) |
|
|
305
|
+
| `--node <id>` | yes\* | | Figma node ID to render (\*omit when using `--export-all`) |
|
|
306
|
+
| `--out <path>` | no | OS temp dir when omitted | Output file path (single node) or output directory (`--export-all`). When omitted, writes to the OS temp directory (valid with `--export-all` or with both `--format` and `--node`). |
|
|
307
|
+
| `--export-all` | no | | Export every node with exportSettings (REST JSON or .fig); `--out` is a directory |
|
|
308
|
+
| `--format <fmt>` | no | inferred from `--out` extension | `png`, `jpeg`, `webp`, `pdf`, `svg` (single-node only; required when `--out` is omitted) |
|
|
309
|
+
| `--width <px>` | no | `1024` | Viewport width (single-node only) |
|
|
310
|
+
| `--height <px>` | no | `1024` | Viewport height (single-node only) |
|
|
311
|
+
| `--scale <n>` | no | `1` | Raster scale factor (single-node only) |
|
|
305
312
|
|
|
306
313
|
## Architecture
|
|
307
314
|
|
|
@@ -332,9 +339,9 @@ REST JSON ───┘
|
|
|
332
339
|
**CLI** — You can pass images in two ways:
|
|
333
340
|
|
|
334
341
|
- **`--images <dir>`** — Explicit images directory. Files are keyed by filename without extension (e.g. `a1b2c3d4.png` → ref `a1b2c3d4`). Use when the document is a separate file:
|
|
335
|
-
`refig ./figma-response.json --images ./downloaded-images --node "1:23" --
|
|
342
|
+
`refig ./figma-response.json --images ./downloaded-images --node "1:23" --format png`
|
|
336
343
|
- **Directory input** — Pass a single directory that contains **`document.json`** (REST response) and optionally **`images/`**. No need to pass `--images` separately:
|
|
337
|
-
`refig ./my-figma-export --node "1:23" --
|
|
344
|
+
`refig ./my-figma-export --node "1:23" --format png`
|
|
338
345
|
(expects `my-figma-export/document.json` and, if present, `my-figma-export/images/`.)
|
|
339
346
|
|
|
340
347
|
For **`.fig`** input, images are embedded in the file; no extra images directory is needed. For **REST** input, use `--images` or a project directory with `images/` to render IMAGE fills correctly.
|
|
@@ -372,7 +379,7 @@ From the package root:
|
|
|
372
379
|
|
|
373
380
|
1. Install dependencies and build: `pnpm install && pnpm build`
|
|
374
381
|
2. Link the package so the `refig` CLI is available: `pnpm link --global`
|
|
375
|
-
3. Run the `refig` command from anywhere to test (e.g. `refig ./fixture.json --node "1:1" --
|
|
382
|
+
3. Run the `refig` command from anywhere to test (e.g. `refig ./fixture.json --node "1:1" --format png`)
|
|
376
383
|
|
|
377
384
|
To unlink: `pnpm unlink --global`.
|
|
378
385
|
|
package/dist/browser.mjs
CHANGED
|
@@ -13749,11 +13749,19 @@ var iofigma;
|
|
|
13749
13749
|
function hasGeometryTrait(node2) {
|
|
13750
13750
|
return "fillGeometry" in node2 || "strokeGeometry" in node2;
|
|
13751
13751
|
}
|
|
13752
|
+
function getParentBounds(node2) {
|
|
13753
|
+
const box = "absoluteBoundingBox" in node2 ? node2.absoluteBoundingBox : void 0;
|
|
13754
|
+
const sz = "size" in node2 ? node2.size : void 0;
|
|
13755
|
+
return {
|
|
13756
|
+
width: box?.width ?? sz?.x ?? 0,
|
|
13757
|
+
height: box?.height ?? sz?.y ?? 0
|
|
13758
|
+
};
|
|
13759
|
+
}
|
|
13752
13760
|
function createVectorNodeFromPath(pathData, geometry, parentNode, childId, name, options) {
|
|
13753
13761
|
if (!pathData) return null;
|
|
13754
13762
|
try {
|
|
13755
13763
|
const vectorNetwork = index_default2.fromSVGPathData(pathData);
|
|
13756
|
-
const
|
|
13764
|
+
const { width, height } = getParentBounds(parentNode);
|
|
13757
13765
|
const strokeAsFill = options.strokeAsFill === true;
|
|
13758
13766
|
return {
|
|
13759
13767
|
id: childId,
|
|
@@ -13767,10 +13775,10 @@ var iofigma;
|
|
|
13767
13775
|
}),
|
|
13768
13776
|
...positioning_trait({
|
|
13769
13777
|
relativeTransform: [
|
|
13770
|
-
[1, 0,
|
|
13771
|
-
[0, 1,
|
|
13778
|
+
[1, 0, 0],
|
|
13779
|
+
[0, 1, 0]
|
|
13772
13780
|
],
|
|
13773
|
-
size: { x:
|
|
13781
|
+
size: { x: width, y: height }
|
|
13774
13782
|
}),
|
|
13775
13783
|
...strokeAsFill ? {
|
|
13776
13784
|
...fills_trait(
|
|
@@ -13794,8 +13802,8 @@ var iofigma;
|
|
|
13794
13802
|
..."effects" in parentNode && parentNode.effects ? effects_trait(parentNode.effects) : effects_trait(void 0),
|
|
13795
13803
|
type: "vector",
|
|
13796
13804
|
vector_network: vectorNetwork,
|
|
13797
|
-
layout_target_width:
|
|
13798
|
-
layout_target_height:
|
|
13805
|
+
layout_target_width: width,
|
|
13806
|
+
layout_target_height: height,
|
|
13799
13807
|
fill_rule: map.windingRuleMap[geometry.windingRule] ?? "nonzero"
|
|
13800
13808
|
};
|
|
13801
13809
|
} catch (e) {
|
|
@@ -13806,13 +13814,73 @@ var iofigma;
|
|
|
13806
13814
|
return null;
|
|
13807
13815
|
}
|
|
13808
13816
|
}
|
|
13817
|
+
function createPathNodeFromPath(pathData, geometry, parentNode, childId, name, options) {
|
|
13818
|
+
if (!pathData) return null;
|
|
13819
|
+
try {
|
|
13820
|
+
const { width, height } = getParentBounds(parentNode);
|
|
13821
|
+
const strokeAsFill = options.strokeAsFill === true;
|
|
13822
|
+
return {
|
|
13823
|
+
id: childId,
|
|
13824
|
+
...base_node_trait({
|
|
13825
|
+
name,
|
|
13826
|
+
visible: "visible" in parentNode ? parentNode.visible : true,
|
|
13827
|
+
locked: "locked" in parentNode ? parentNode.locked : false,
|
|
13828
|
+
rotation: 0,
|
|
13829
|
+
opacity: "opacity" in parentNode && parentNode.opacity !== void 0 ? parentNode.opacity : 1,
|
|
13830
|
+
blendMode: "blendMode" in parentNode && parentNode.blendMode ? parentNode.blendMode : "NORMAL"
|
|
13831
|
+
}),
|
|
13832
|
+
...positioning_trait({
|
|
13833
|
+
relativeTransform: [
|
|
13834
|
+
[1, 0, 0],
|
|
13835
|
+
[0, 1, 0]
|
|
13836
|
+
],
|
|
13837
|
+
size: { x: width, y: height }
|
|
13838
|
+
}),
|
|
13839
|
+
...strokeAsFill ? {
|
|
13840
|
+
...fills_trait(
|
|
13841
|
+
parentNode.strokes ?? [],
|
|
13842
|
+
context,
|
|
13843
|
+
imageRefsUsed
|
|
13844
|
+
),
|
|
13845
|
+
...stroke_trait(
|
|
13846
|
+
{ strokes: [], strokeWeight: 0 },
|
|
13847
|
+
context,
|
|
13848
|
+
imageRefsUsed
|
|
13849
|
+
)
|
|
13850
|
+
} : {
|
|
13851
|
+
...options.useFill ? fills_trait(parentNode.fills, context, imageRefsUsed) : {},
|
|
13852
|
+
...options.useStroke ? stroke_trait(parentNode, context, imageRefsUsed) : stroke_trait(
|
|
13853
|
+
{ strokes: [], strokeWeight: 0 },
|
|
13854
|
+
context,
|
|
13855
|
+
imageRefsUsed
|
|
13856
|
+
)
|
|
13857
|
+
},
|
|
13858
|
+
..."effects" in parentNode && parentNode.effects ? effects_trait(parentNode.effects) : effects_trait(void 0),
|
|
13859
|
+
type: "path",
|
|
13860
|
+
data: pathData,
|
|
13861
|
+
layout_target_width: width,
|
|
13862
|
+
layout_target_height: height,
|
|
13863
|
+
fill_rule: map.windingRuleMap[geometry.windingRule] ?? "nonzero"
|
|
13864
|
+
};
|
|
13865
|
+
} catch (e) {
|
|
13866
|
+
console.warn(`Failed to create path node (${name}):`, e);
|
|
13867
|
+
return null;
|
|
13868
|
+
}
|
|
13869
|
+
}
|
|
13809
13870
|
function processFillGeometries(node2, parentGridaId, nodeTypeName) {
|
|
13810
13871
|
if (!node2.fillGeometry?.length) return [];
|
|
13811
13872
|
const childIds = [];
|
|
13812
13873
|
node2.fillGeometry.forEach((geometry, idx) => {
|
|
13813
13874
|
const childId = `${parentGridaId}_fill_${idx}`;
|
|
13814
13875
|
const name = `${node2.name || nodeTypeName} Fill ${idx + 1}`;
|
|
13815
|
-
const childNode =
|
|
13876
|
+
const childNode = context.prefer_path_for_geometry ? createPathNodeFromPath(
|
|
13877
|
+
geometry.path ?? "",
|
|
13878
|
+
geometry,
|
|
13879
|
+
node2,
|
|
13880
|
+
childId,
|
|
13881
|
+
name,
|
|
13882
|
+
{ useFill: true, useStroke: false }
|
|
13883
|
+
) : createVectorNodeFromPath(
|
|
13816
13884
|
geometry.path ?? "",
|
|
13817
13885
|
geometry,
|
|
13818
13886
|
node2,
|
|
@@ -13833,7 +13901,14 @@ var iofigma;
|
|
|
13833
13901
|
node2.strokeGeometry.forEach((geometry, idx) => {
|
|
13834
13902
|
const childId = `${parentGridaId}_stroke_${idx}`;
|
|
13835
13903
|
const name = `${node2.name || nodeTypeName} Stroke ${idx + 1}`;
|
|
13836
|
-
const childNode =
|
|
13904
|
+
const childNode = context.prefer_path_for_geometry ? createPathNodeFromPath(
|
|
13905
|
+
geometry.path ?? "",
|
|
13906
|
+
geometry,
|
|
13907
|
+
node2,
|
|
13908
|
+
childId,
|
|
13909
|
+
name,
|
|
13910
|
+
{ useFill: false, useStroke: false, strokeAsFill: true }
|
|
13911
|
+
) : createVectorNodeFromPath(
|
|
13837
13912
|
geometry.path ?? "",
|
|
13838
13913
|
geometry,
|
|
13839
13914
|
node2,
|
|
@@ -14082,7 +14157,7 @@ var iofigma;
|
|
|
14082
14157
|
case "REGULAR_POLYGON":
|
|
14083
14158
|
case "STAR":
|
|
14084
14159
|
case "VECTOR": {
|
|
14085
|
-
const useRestVectorNetwork = context.disable_volatile_apis !== true && "vectorNetwork" in node && node.vectorNetwork != null;
|
|
14160
|
+
const useRestVectorNetwork = context.prefer_path_for_geometry !== true && context.disable_volatile_apis !== true && "vectorNetwork" in node && node.vectorNetwork != null;
|
|
14086
14161
|
if (useRestVectorNetwork) {
|
|
14087
14162
|
try {
|
|
14088
14163
|
const ir = restful2.map.normalizeRestVectorNetworkToIR(
|
|
@@ -15866,7 +15941,8 @@ function exportSettingToRenderOptions(node, setting) {
|
|
|
15866
15941
|
JPG: "jpeg",
|
|
15867
15942
|
PNG: "png",
|
|
15868
15943
|
SVG: "svg",
|
|
15869
|
-
PDF: "pdf"
|
|
15944
|
+
PDF: "pdf",
|
|
15945
|
+
WEBP: "webp"
|
|
15870
15946
|
};
|
|
15871
15947
|
const format = formatMap[setting.format] ?? "png";
|
|
15872
15948
|
const constraint = setting.constraint;
|
|
@@ -15951,6 +16027,7 @@ function restJsonToSceneJson(json, rootNodeId, images) {
|
|
|
15951
16027
|
const resolveImageSrc = images && (Object.keys(images).length > 0 ? (ref) => ref in images ? `res://images/${ref}` : null : void 0);
|
|
15952
16028
|
const buildContext = (overrides) => ({
|
|
15953
16029
|
gradient_id_generator: baseGradientGen,
|
|
16030
|
+
prefer_path_for_geometry: true,
|
|
15954
16031
|
...resolveImageSrc && { resolve_image_src: resolveImageSrc },
|
|
15955
16032
|
...overrides
|
|
15956
16033
|
});
|
|
@@ -16089,6 +16166,7 @@ function figBytesToSceneJson(figBytes, rootNodeId) {
|
|
|
16089
16166
|
node_id_generator: () => `refig-${++counter}`,
|
|
16090
16167
|
gradient_id_generator: () => `grad-${++counter}`,
|
|
16091
16168
|
preserve_figma_ids: true,
|
|
16169
|
+
prefer_path_for_geometry: true,
|
|
16092
16170
|
...resolveImageSrc && { resolve_image_src: resolveImageSrc }
|
|
16093
16171
|
};
|
|
16094
16172
|
const { document: packed } = iofigma.kiwi.convertPageToScene(page, context);
|
package/dist/cli.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
exportSettingToRenderOptions,
|
|
7
7
|
figFileToRestLikeDocument,
|
|
8
8
|
iofigma
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-YI7PIULZ.mjs";
|
|
10
10
|
|
|
11
11
|
// cli.ts
|
|
12
12
|
import {
|
|
@@ -15,9 +15,11 @@ import {
|
|
|
15
15
|
mkdirSync,
|
|
16
16
|
writeFileSync,
|
|
17
17
|
existsSync,
|
|
18
|
-
statSync
|
|
18
|
+
statSync,
|
|
19
|
+
mkdtempSync
|
|
19
20
|
} from "fs";
|
|
20
21
|
import path from "path";
|
|
22
|
+
import { tmpdir } from "os";
|
|
21
23
|
import { program } from "commander";
|
|
22
24
|
var FORMAT_SET = /* @__PURE__ */ new Set(["png", "jpeg", "webp", "pdf", "svg"]);
|
|
23
25
|
var DOCUMENT_JSON = "document.json";
|
|
@@ -31,6 +33,7 @@ var EXT_BY_FORMAT = {
|
|
|
31
33
|
png: "png",
|
|
32
34
|
jpeg: "jpeg",
|
|
33
35
|
jpg: "jpeg",
|
|
36
|
+
webp: "webp",
|
|
34
37
|
svg: "svg",
|
|
35
38
|
pdf: "pdf"
|
|
36
39
|
};
|
|
@@ -174,9 +177,9 @@ async function main() {
|
|
|
174
177
|
).argument(
|
|
175
178
|
"<input>",
|
|
176
179
|
"Path to .fig, JSON file (REST API response), or directory containing document.json (and optionally images/)"
|
|
177
|
-
).
|
|
180
|
+
).option(
|
|
178
181
|
"--out <path>",
|
|
179
|
-
"Output file path (single node) or output directory (--export-all)"
|
|
182
|
+
"Output file path (single node) or output directory (--export-all); when omitted, uses OS temp directory (valid with --export-all or with both --format and --node)"
|
|
180
183
|
).option(
|
|
181
184
|
"--images <dir>",
|
|
182
185
|
"Directory of image files for REST API document (optional; not used if <input> is a dir with images/)"
|
|
@@ -198,9 +201,6 @@ async function main() {
|
|
|
198
201
|
const exportAll = options.exportAll === true;
|
|
199
202
|
const nodeId = String(options.node ?? "").trim();
|
|
200
203
|
const explicitImagesDir = typeof options.images === "string" ? options.images : void 0;
|
|
201
|
-
if (!outPath) {
|
|
202
|
-
program.error("--out is required");
|
|
203
|
-
}
|
|
204
204
|
const { documentPath, imagesDir, isRestJson } = resolveInput(
|
|
205
205
|
input.trim(),
|
|
206
206
|
explicitImagesDir
|
|
@@ -209,17 +209,20 @@ async function main() {
|
|
|
209
209
|
if (nodeId) {
|
|
210
210
|
program.error("--node must not be used with --export-all");
|
|
211
211
|
}
|
|
212
|
-
const outDir =
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
212
|
+
const outDir = outPath ? (() => {
|
|
213
|
+
const resolved = path.resolve(outPath);
|
|
214
|
+
if (existsSync(resolved)) {
|
|
215
|
+
const stat = statSync(resolved);
|
|
216
|
+
if (!stat.isDirectory()) {
|
|
217
|
+
program.error(
|
|
218
|
+
"--out must be a directory when using --export-all"
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
mkdirSync(resolved, { recursive: true });
|
|
219
223
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
224
|
+
return resolved;
|
|
225
|
+
})() : mkdtempSync(path.join(tmpdir(), "refig-export-"));
|
|
223
226
|
await runExportAll(
|
|
224
227
|
documentPath,
|
|
225
228
|
outDir,
|
|
@@ -234,8 +237,28 @@ async function main() {
|
|
|
234
237
|
const width = Number(options.width ?? 1024);
|
|
235
238
|
const height = Number(options.height ?? 1024);
|
|
236
239
|
const scale = Number(options.scale ?? 1);
|
|
237
|
-
|
|
238
|
-
|
|
240
|
+
const formatOption = typeof options.format === "string" ? options.format : void 0;
|
|
241
|
+
let singleOutPath;
|
|
242
|
+
if (outPath) {
|
|
243
|
+
singleOutPath = path.resolve(outPath);
|
|
244
|
+
} else {
|
|
245
|
+
if (!formatOption) {
|
|
246
|
+
program.error(
|
|
247
|
+
"When --out is omitted, both --node and --format are required"
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
const format = formatOption.toLowerCase();
|
|
251
|
+
if (!FORMAT_SET.has(format)) {
|
|
252
|
+
program.error(`Unsupported --format "${format}"`);
|
|
253
|
+
}
|
|
254
|
+
const ext = EXT_BY_FORMAT[format] ?? "png";
|
|
255
|
+
singleOutPath = path.join(
|
|
256
|
+
tmpdir(),
|
|
257
|
+
`refig-${sanitizeForFilename(nodeId)}.${ext}`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
await runSingleNode(documentPath, nodeId, singleOutPath, {
|
|
261
|
+
format: formatOption,
|
|
239
262
|
width,
|
|
240
263
|
height,
|
|
241
264
|
scale,
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grida/refig",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Headless Figma renderer — render .fig and REST API JSON to PNG/JPEG/WebP/PDF/SVG",
|
|
6
6
|
"keywords": [
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"access": "public"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@grida/canvas-wasm": "0.90.0-canary.
|
|
58
|
+
"@grida/canvas-wasm": "0.90.0-canary.8",
|
|
59
59
|
"commander": "^12.1.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|