@grida/refig 0.0.0 → 0.0.1

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 CHANGED
@@ -1,10 +1,28 @@
1
1
  # `@grida/refig`
2
2
 
3
- > **re**nder **fig**ma — headless Figma renderer in the spirit of [`resvg`](https://github.com/nicolo-ribaudo/resvg-js)
3
+ > **re**nder **fig**ma — headless Figma renderer (Node.js + browser) in the spirit of [`resvg-js`](https://github.com/nicolo-ribaudo/resvg-js)
4
4
 
5
- Render Figma documents to **PNG, JPEG, WebP, PDF, and SVG** without Figma Desktop and without a browser.
5
+ Render Figma documents to **PNG, JPEG, WebP, PDF, and SVG** in **Node.js (no browser required)** or directly in the **browser**.
6
6
 
7
- Pass a `.fig` file or a Figma REST API JSON response, pick a node, get pixels.
7
+ Use a `.fig` export (offline) or a Figma REST API file JSON response (`GET /v1/files/:key`), pick a node ID, and get pixels.
8
+
9
+ ## Features (checklist)
10
+
11
+ - [x] Render from **`.fig` files** (offline / no API calls)
12
+ - [x] Render from **Figma REST API JSON** (bring your own auth + HTTP client)
13
+ - [x] Output formats: **PNG, JPEG, WebP, PDF, SVG**
14
+ - [x] **CLI** (`refig`) and **library API** (`FigmaDocument`, `FigmaRenderer`)
15
+ - [x] **Node.js** + **browser** entrypoints (`@grida/refig`, `@grida/refig/browser`)
16
+ - [x] IMAGE fills supported via **embedded `.fig` images** or a **local `images/` directory** for REST JSON
17
+ - [x] Batch export with **`--export-all`** (renders nodes with Figma export presets)
18
+ - [x] WASM + Skia-backed renderer via `@grida/canvas-wasm`
19
+
20
+ ## Use cases
21
+
22
+ - Export assets in CI (deterministic, no network calls required)
23
+ - Generate thumbnails / previews from `.fig` or REST JSON
24
+ - Offline / air-gapped rendering from `.fig` exports
25
+ - In-browser previews with `@grida/refig/browser`
8
26
 
9
27
  ## Install
10
28
 
@@ -26,7 +44,7 @@ Both entrypoints export the same core API (`FigmaDocument`, `FigmaRenderer`, typ
26
44
  ### Render from a `.fig` file
27
45
 
28
46
  ```ts
29
- import { readFileSync, writeFileSync } from "node:fs";
47
+ import { writeFileSync } from "node:fs";
30
48
  import { FigmaDocument, FigmaRenderer } from "@grida/refig";
31
49
 
32
50
  const doc = FigmaDocument.fromFile("path/to/file.fig");
@@ -92,6 +110,7 @@ renderer.dispose();
92
110
  ```ts
93
111
  import { FigmaDocument, FigmaRenderer } from "@grida/refig/browser";
94
112
 
113
+ // `file` is a File from <input type="file">, drag-and-drop, etc.
95
114
  // Uint8Array from a File input, fetch(), or drag-and-drop
96
115
  const figBytes: Uint8Array = await file
97
116
  .arrayBuffer()
@@ -172,6 +191,13 @@ interface RefigRenderResult {
172
191
  pnpm add -g @grida/refig
173
192
  ```
174
193
 
194
+ Or run without installing:
195
+
196
+ ```sh
197
+ npx @grida/refig <input> --node <node-id> --out <path>
198
+ pnpm dlx @grida/refig <input> --node <node-id> --out <path>
199
+ ```
200
+
175
201
  ### Usage
176
202
 
177
203
  **`<input>`** can be:
@@ -220,10 +246,41 @@ refig ./design.fig --export-all --out ./exports
220
246
  # Scale 2x, custom dimensions
221
247
  refig ./design.fig --node "1:23" --out ./out.png --width 512 --height 512 --scale 2
222
248
 
223
- # No-install (CI one-liner)
249
+ # No-install (run without installing)
250
+ npx @grida/refig ./design.fig --node "1:23" --out ./out.png
224
251
  pnpm dlx @grida/refig ./design.fig --node "1:23" --out ./out.png
225
252
  ```
226
253
 
254
+ ### Quick test via `figma_archive.py` (REST API → `document.json` + `images/`)
255
+
256
+ If you want an end-to-end test from a real Figma file using the REST API, you can generate a local “project directory” that refig can consume directly.
257
+
258
+ 1. Archive a Figma file (stdlib-only Python script):
259
+
260
+ - Script: [`figma_archive.py` (gist)](https://gist.github.com/softmarshmallow/27ad65dfa5babc2c67b41740f1f05791)
261
+ - (For repo contributors, it’s also in this monorepo at `.tools/figma_archive.py`.)
262
+ - Save the script locally as `figma_archive.py`, then run:
263
+
264
+ ```sh
265
+ # File key is the "<key>" part of `https://www.figma.com/file/<key>/...`
266
+ python3 figma_archive.py --x-figma-token "<token>" --filekey "<key>" --archive-dir ./my-figma-export
267
+ ```
268
+
269
+ This writes:
270
+
271
+ - `./my-figma-export/document.json` (with `geometry=paths`)
272
+ - `./my-figma-export/images/<ref>.<ext>` (image fills downloaded from `/v1/files/:key/images`)
273
+
274
+ 2. Render using the directory as `<input>`:
275
+
276
+ ```sh
277
+ # Single node
278
+ refig ./my-figma-export --node "1:23" --out ./out.png
279
+
280
+ # Or export everything with Figma export presets
281
+ refig ./my-figma-export --export-all --out ./exports
282
+ ```
283
+
227
284
  ### Export all (`--export-all`)
228
285
 
229
286
  With **`--export-all`**, refig walks the document and renders every node that has [Figma export settings](https://www.figma.com/developers/api#exportsetting-type) — one file per (node, setting), using that setting’s format, suffix, and constraint. Both **REST API JSON** (e.g. `GET /v1/files/:key`) and **`.fig` files** are supported when the file includes export settings.
@@ -282,15 +339,6 @@ REST JSON ───┘
282
339
 
283
340
  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.
284
341
 
285
- ## Features
286
-
287
- - **Multiple output formats** — `png`, `jpeg`, `webp`, `pdf`, `svg`
288
- - **`.fig` file input** — render from exported `.fig` without API calls
289
- - **REST API JSON input** — render from document JSON you already have
290
- - **CI-friendly** — headless, deterministic, no browser required
291
- - **Browser-compatible** — `@grida/refig/browser` works in any modern browser
292
- - **WASM-powered** — Skia-backed rendering for pixel-accurate output
293
-
294
342
  ## Not planned
295
343
 
296
344
  - **Figma API fetching / auth** — bring your own tokens and HTTP client
@@ -316,7 +364,7 @@ Yes. Import from `@grida/refig/browser`. The core renderer uses `@grida/canvas-w
316
364
 
317
365
  ### What about fonts?
318
366
 
319
- The WASM runtime ships with embedded fallback fonts. Custom font loading (Google Fonts, local directories) is planned but not yet available.
367
+ The WASM runtime ships with embedded fallback fonts (Geist / Geist Mono). **`loadFigmaDefaultFonts`** is enabled by default: the renderer loads the Figma default font set (Inter, Noto Sans KR/JP/SC, and optionally Noto Sans TC/HK and Noto Color Emoji) from CDN and registers them as fallbacks before the first render, so mixed-script and CJK text avoid tofu. Set **`loadFigmaDefaultFonts: false`** to disable (e.g. to avoid network or use only embedded fonts). Custom or other Google Fonts are **not** loaded by the renderer; the user is responsible for fetching font bytes and registering them with the canvas if needed.
320
368
 
321
369
  ## Contributing
322
370
 
@@ -14,6 +14,14 @@ interface RefigRendererOptions {
14
14
  * @default true
15
15
  */
16
16
  useEmbeddedFonts?: boolean;
17
+ /**
18
+ * When true (default), the renderer ensures Figma default fonts (Inter, Noto Sans KR/JP/SC, etc.)
19
+ * are loaded from CDN and registered with the canvas before any scene is loaded.
20
+ * Reduces tofu for mixed-script and CJK text. Set to false to skip (e.g. to avoid network or use only embedded fonts).
21
+ * Custom fonts remain the user's responsibility.
22
+ * @default true
23
+ */
24
+ loadFigmaDefaultFonts?: boolean;
17
25
  /**
18
26
  * Map of Figma image ref (hash) to image bytes.
19
27
  * Used for REST API and .fig input so IMAGE fills render correctly.
package/dist/browser.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  collectExportsFromDocument,
5
5
  exportSettingToRenderOptions,
6
6
  resolveMimeType
7
- } from "./chunk-DAHUXARL.mjs";
7
+ } from "./chunk-PK5L35ID.mjs";
8
8
  export {
9
9
  FigmaDocument,
10
10
  FigmaRenderer,
@@ -13393,6 +13393,61 @@ var iofigma;
13393
13393
  ...map2.blendModeMap,
13394
13394
  PASS_THROUGH: "pass-through"
13395
13395
  };
13396
+ function normalizeRestVectorNetworkToIR(rest) {
13397
+ if (!Array.isArray(rest.vertices) || !Array.isArray(rest.segments) || !Array.isArray(rest.regions)) {
13398
+ return null;
13399
+ }
13400
+ const vertexCount = rest.vertices.length;
13401
+ const segmentCount = rest.segments.length;
13402
+ for (const seg of rest.segments) {
13403
+ if (typeof seg.start !== "number" || typeof seg.end !== "number" || seg.start < 0 || seg.start >= vertexCount || seg.end < 0 || seg.end >= vertexCount || !seg.startTangent || !seg.endTangent) {
13404
+ return null;
13405
+ }
13406
+ }
13407
+ for (const region of rest.regions) {
13408
+ if (!Array.isArray(region.loops)) return null;
13409
+ for (const loop of region.loops) {
13410
+ if (!Array.isArray(loop)) return null;
13411
+ for (const segIdx of loop) {
13412
+ if (typeof segIdx !== "number" || segIdx < 0 || segIdx >= segmentCount) {
13413
+ return null;
13414
+ }
13415
+ }
13416
+ }
13417
+ }
13418
+ const vertices = rest.vertices.map(
13419
+ (v) => ({
13420
+ x: v.position?.x ?? 0,
13421
+ y: v.position?.y ?? 0,
13422
+ styleID: 0
13423
+ })
13424
+ );
13425
+ const segments = rest.segments.map(
13426
+ (seg) => ({
13427
+ styleID: 0,
13428
+ start: {
13429
+ vertex: seg.start,
13430
+ dx: seg.startTangent.x,
13431
+ dy: seg.startTangent.y
13432
+ },
13433
+ end: {
13434
+ vertex: seg.end,
13435
+ dx: seg.endTangent.x,
13436
+ dy: seg.endTangent.y
13437
+ }
13438
+ })
13439
+ );
13440
+ const regions = rest.regions.map(
13441
+ (region) => {
13442
+ const wr = region.windingRule?.toUpperCase?.();
13443
+ const windingRule = wr === "EVENODD" || wr === "ODD" ? "ODD" : "NONZERO";
13444
+ const loops = region.loops.map((loop) => ({ segments: loop }));
13445
+ return { styleID: 0, windingRule, loops };
13446
+ }
13447
+ );
13448
+ return { vertices, segments, regions };
13449
+ }
13450
+ map2.normalizeRestVectorNetworkToIR = normalizeRestVectorNetworkToIR;
13396
13451
  })(map = restful2.map || (restful2.map = {}));
13397
13452
  let factory;
13398
13453
  ((factory2) => {
@@ -13699,6 +13754,7 @@ var iofigma;
13699
13754
  try {
13700
13755
  const vectorNetwork = index_default2.fromSVGPathData(pathData);
13701
13756
  const bbox = index_default2.getBBox(vectorNetwork);
13757
+ const strokeAsFill = options.strokeAsFill === true;
13702
13758
  return {
13703
13759
  id: childId,
13704
13760
  ...base_node_trait({
@@ -13716,12 +13772,25 @@ var iofigma;
13716
13772
  ],
13717
13773
  size: { x: bbox.width, y: bbox.height }
13718
13774
  }),
13719
- ...options.useFill ? fills_trait(parentNode.fills, context, imageRefsUsed) : {},
13720
- ...options.useStroke ? stroke_trait(parentNode, context, imageRefsUsed) : stroke_trait(
13721
- { strokes: [], strokeWeight: 0 },
13722
- context,
13723
- imageRefsUsed
13724
- ),
13775
+ ...strokeAsFill ? {
13776
+ ...fills_trait(
13777
+ parentNode.strokes ?? [],
13778
+ context,
13779
+ imageRefsUsed
13780
+ ),
13781
+ ...stroke_trait(
13782
+ { strokes: [], strokeWeight: 0 },
13783
+ context,
13784
+ imageRefsUsed
13785
+ )
13786
+ } : {
13787
+ ...options.useFill ? fills_trait(parentNode.fills, context, imageRefsUsed) : {},
13788
+ ...options.useStroke ? stroke_trait(parentNode, context, imageRefsUsed) : stroke_trait(
13789
+ { strokes: [], strokeWeight: 0 },
13790
+ context,
13791
+ imageRefsUsed
13792
+ )
13793
+ },
13725
13794
  ..."effects" in parentNode && parentNode.effects ? effects_trait(parentNode.effects) : effects_trait(void 0),
13726
13795
  type: "vector",
13727
13796
  vector_network: vectorNetwork,
@@ -13770,7 +13839,7 @@ var iofigma;
13770
13839
  node2,
13771
13840
  childId,
13772
13841
  name,
13773
- { useFill: false, useStroke: true }
13842
+ { useFill: false, useStroke: false, strokeAsFill: true }
13774
13843
  );
13775
13844
  if (childNode) {
13776
13845
  nodes[childId] = childNode;
@@ -14013,6 +14082,37 @@ var iofigma;
14013
14082
  case "REGULAR_POLYGON":
14014
14083
  case "STAR":
14015
14084
  case "VECTOR": {
14085
+ const useRestVectorNetwork = context.disable_volatile_apis !== true && "vectorNetwork" in node && node.vectorNetwork != null;
14086
+ if (useRestVectorNetwork) {
14087
+ try {
14088
+ const ir = restful2.map.normalizeRestVectorNetworkToIR(
14089
+ node.vectorNetwork
14090
+ );
14091
+ if (ir) {
14092
+ const gridaVectorNetwork = {
14093
+ vertices: ir.vertices.map((v) => [v.x, v.y]),
14094
+ segments: ir.segments.map((seg) => ({
14095
+ a: seg.start.vertex,
14096
+ b: seg.end.vertex,
14097
+ ta: [seg.start.dx, seg.start.dy],
14098
+ tb: [seg.end.dx, seg.end.dy]
14099
+ }))
14100
+ };
14101
+ return {
14102
+ id: gridaId,
14103
+ ...base_node_trait(node),
14104
+ ...positioning_trait(node),
14105
+ ...fills_trait(node.fills, context, imageRefsUsed),
14106
+ ...stroke_trait(node, context, imageRefsUsed),
14107
+ ...corner_radius_trait(node),
14108
+ ...effects_trait(node.effects),
14109
+ type: "vector",
14110
+ vector_network: gridaVectorNetwork
14111
+ };
14112
+ }
14113
+ } catch {
14114
+ }
14115
+ }
14016
14116
  return {
14017
14117
  id: gridaId,
14018
14118
  ...base_node_trait(node),
@@ -14363,7 +14463,11 @@ var iofigma;
14363
14463
  const f = fmt(s.imageType);
14364
14464
  if (!f) return null;
14365
14465
  const c = s.constraint;
14366
- return { format: f, suffix: s.suffix ?? "", constraint: { type: cstr(c?.type), value: c?.value ?? 1 } };
14466
+ return {
14467
+ format: f,
14468
+ suffix: s.suffix ?? "",
14469
+ constraint: { type: cstr(c?.type), value: c?.value ?? 1 }
14470
+ };
14367
14471
  }).filter((x) => x !== null);
14368
14472
  return exportSettings.length ? { exportSettings } : {};
14369
14473
  }
@@ -15607,6 +15711,60 @@ var grida;
15607
15711
  })(grida || (grida = {}));
15608
15712
  var cloneWithUndefinedValues = (obj) => Object.fromEntries(Object.keys(obj).map((key) => [key, void 0]));
15609
15713
 
15714
+ // figma-default-fonts.ts
15715
+ var FIGMA_DEFAULT_FONT_ENTRIES = [
15716
+ {
15717
+ family: "Inter",
15718
+ url: "https://fonts.gstatic.com/s/inter/v19/UcCo3FwrK3iLTfvlaQc78lA2.ttf"
15719
+ },
15720
+ {
15721
+ family: "Noto Sans KR",
15722
+ url: "https://fonts.gstatic.com/s/notosanskr/v37/PbykFmXiEBPT4ITbgNA5Cgm21nTs4JMMuA.ttf"
15723
+ },
15724
+ {
15725
+ family: "Noto Sans JP",
15726
+ url: "https://fonts.gstatic.com/s/notosansjp/v54/-F62fjtqLzI2JPCgQBnw7HFoxgIO2lZ9hg.ttf"
15727
+ },
15728
+ {
15729
+ family: "Noto Sans SC",
15730
+ url: "https://fonts.gstatic.com/s/notosanssc/v38/k3kXo84MPvpLmixcA63oeALhKYiJ-Q7m8w.ttf"
15731
+ },
15732
+ {
15733
+ family: "Noto Sans TC",
15734
+ url: "https://fonts.gstatic.com/s/notosanstc/v37/-nF7OG829Oofr2wohFbTp9iFPysLA_ZJ1g.ttf"
15735
+ },
15736
+ {
15737
+ family: "Noto Sans HK",
15738
+ url: "https://fonts.gstatic.com/s/notosanshk/v33/nKKQ-GM_FYFRJvXzVXaAPe9hNHB3Eu7mOQ.ttf"
15739
+ },
15740
+ {
15741
+ family: "Noto Color Emoji",
15742
+ url: "https://fonts.gstatic.com/s/notocoloremoji/v35/Yq6P-KqIXTD0t4D9z1ESnKM3-HpFab5s79iz64w.ttf"
15743
+ }
15744
+ ];
15745
+ var FIGMA_DEFAULT_FALLBACK_ORDER = [
15746
+ "Inter",
15747
+ "Noto Sans KR",
15748
+ "Noto Sans JP",
15749
+ "Noto Sans SC",
15750
+ "Noto Sans TC",
15751
+ "Noto Sans HK",
15752
+ "Noto Color Emoji"
15753
+ ];
15754
+ async function ensureFigmaDefaultFonts(canvas) {
15755
+ for (const entry of FIGMA_DEFAULT_FONT_ENTRIES) {
15756
+ const res = await fetch(entry.url);
15757
+ if (!res.ok) {
15758
+ throw new Error(
15759
+ `Figma default font fetch failed: ${entry.family} ${res.status} ${res.statusText}`
15760
+ );
15761
+ }
15762
+ const buffer = await res.arrayBuffer();
15763
+ canvas.addFont(entry.family, new Uint8Array(buffer));
15764
+ }
15765
+ canvas.setFallbackFonts(FIGMA_DEFAULT_FALLBACK_ORDER);
15766
+ }
15767
+
15610
15768
  // lib.ts
15611
15769
  var FigmaDocument = class {
15612
15770
  /**
@@ -15960,6 +16118,11 @@ var FigmaRenderer = class {
15960
16118
  height,
15961
16119
  useEmbeddedFonts: this.options.useEmbeddedFonts ?? true
15962
16120
  });
16121
+ if (this.options.loadFigmaDefaultFonts !== false) {
16122
+ await ensureFigmaDefaultFonts(
16123
+ this._canvas
16124
+ );
16125
+ }
15963
16126
  return this._canvas;
15964
16127
  }
15965
16128
  loadScene(canvas, nodeId) {
package/dist/cli.mjs CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  exportSettingToRenderOptions,
7
7
  figFileToRestLikeDocument,
8
8
  iofigma
9
- } from "./chunk-DAHUXARL.mjs";
9
+ } from "./chunk-PK5L35ID.mjs";
10
10
 
11
11
  // cli.ts
12
12
  import {
@@ -80,7 +80,7 @@ function exportAllOutputBasename(nodeId, suffix, format) {
80
80
  const name = safeSuffix ? `${safeId}_${safeSuffix}` : safeId;
81
81
  return `${name}.${ext}`;
82
82
  }
83
- async function runExportAll(documentPath, outDir, imagesDir) {
83
+ async function runExportAll(documentPath, outDir, imagesDir, skipDefaultFonts) {
84
84
  const isFig = documentPath.toLowerCase().endsWith(".fig");
85
85
  let document;
86
86
  let items;
@@ -109,6 +109,9 @@ async function runExportAll(documentPath, outDir, imagesDir) {
109
109
  rendererOptions = { images: readImagesFromDir(imagesDir) };
110
110
  }
111
111
  }
112
+ if (skipDefaultFonts || process.env.REFIG_SKIP_DEFAULT_FONTS === "1") {
113
+ rendererOptions = { ...rendererOptions, loadFigmaDefaultFonts: false };
114
+ }
112
115
  if (items.length === 0) {
113
116
  process.stdout.write("No nodes with export settings found.\n");
114
117
  return;
@@ -144,6 +147,9 @@ async function runSingleNode(documentPath, nodeId, outPath, opts) {
144
147
  const isJson = documentPath.toLowerCase().endsWith(".json");
145
148
  const document = isJson ? new FigmaDocument(JSON.parse(readFileSync(documentPath, "utf8"))) : new FigmaDocument(new Uint8Array(readFileSync(documentPath)));
146
149
  const rendererOptions = isJson && opts.imagesDir ? { images: readImagesFromDir(opts.imagesDir) } : {};
150
+ if (opts.skipDefaultFonts || process.env.REFIG_SKIP_DEFAULT_FONTS === "1") {
151
+ rendererOptions.loadFigmaDefaultFonts = false;
152
+ }
147
153
  const renderer = new FigmaRenderer(document, rendererOptions);
148
154
  try {
149
155
  const result = await renderer.render(nodeId, {
@@ -183,7 +189,10 @@ async function main() {
183
189
  ).option(
184
190
  "--format <fmt>",
185
191
  "png | jpeg | webp | pdf | svg (single-node only; default: from --out extension)"
186
- ).option("--width <px>", "Viewport width (single-node only)", "1024").option("--height <px>", "Viewport height (single-node only)", "1024").option("--scale <n>", "Raster scale factor (single-node only)", "1").action(
192
+ ).option("--width <px>", "Viewport width (single-node only)", "1024").option("--height <px>", "Viewport height (single-node only)", "1024").option("--scale <n>", "Raster scale factor (single-node only)", "1").option(
193
+ "--skip-default-fonts",
194
+ "Do not load Figma default fonts (same as REFIG_SKIP_DEFAULT_FONTS=1)"
195
+ ).action(
187
196
  async (input, options) => {
188
197
  const outPath = String(options.out ?? "").trim();
189
198
  const exportAll = options.exportAll === true;
@@ -211,7 +220,12 @@ async function main() {
211
220
  } else {
212
221
  mkdirSync(outDir, { recursive: true });
213
222
  }
214
- await runExportAll(documentPath, outDir, imagesDir);
223
+ await runExportAll(
224
+ documentPath,
225
+ outDir,
226
+ imagesDir,
227
+ options.skipDefaultFonts === true
228
+ );
215
229
  return;
216
230
  }
217
231
  if (!nodeId) {
@@ -225,7 +239,8 @@ async function main() {
225
239
  width,
226
240
  height,
227
241
  scale,
228
- imagesDir
242
+ imagesDir,
243
+ skipDefaultFonts: options.skipDefaultFonts === true
229
244
  });
230
245
  }
231
246
  );
package/dist/index.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  collectExportsFromDocument,
5
5
  exportSettingToRenderOptions,
6
6
  resolveMimeType
7
- } from "./chunk-DAHUXARL.mjs";
7
+ } from "./chunk-PK5L35ID.mjs";
8
8
 
9
9
  // index.ts
10
10
  import { readFileSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grida/refig",
3
- "version": "0.0.0",
3
+ "version": "0.0.1",
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": [
@@ -21,7 +21,7 @@
21
21
  "wasm",
22
22
  "webp"
23
23
  ],
24
- "homepage": "https://github.com/gridaco/grida/tree/main/packages/grida-canvas-sdk-render-figma",
24
+ "homepage": "https://grida.co/docs/packages/@grida/refig",
25
25
  "bugs": "https://github.com/gridaco/grida/issues",
26
26
  "license": "MIT",
27
27
  "author": "softmarshmallow",
@@ -55,11 +55,11 @@
55
55
  "access": "public"
56
56
  },
57
57
  "dependencies": {
58
- "@grida/canvas-wasm": "0.90.0-canary.6",
58
+ "@grida/canvas-wasm": "0.90.0-canary.7",
59
59
  "commander": "^12.1.0"
60
60
  },
61
61
  "devDependencies": {
62
- "@figma/rest-api-spec": "0.35.0",
62
+ "@figma/rest-api-spec": "0.36.0",
63
63
  "fflate": "^0.8.2",
64
64
  "tsup": "^8.5.0"
65
65
  },