@akesson/figma-explorer 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,198 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-10-28
11
+
12
+ ### Added
13
+
14
+ - **Cached files are checked against Figma before they're served.** Designers
15
+ edit files while you work, and until now a cached file was served as-is
16
+ until the next `cache prefetch` — days out of date with no warning. Every
17
+ command that reads cached file data now compares the file's Figma `version`
18
+ (a ~1 KB `/meta` request) at most once every 5 minutes per file, and
19
+ refetches a changed file before answering; stderr says
20
+ `cache: N files changed on Figma (…) — refetching…`. Cross-file `find` and
21
+ `ls` at `--depth 2`+ check all the files they read. If the check or the
22
+ refetch fails, the cached copy is served with a note rather than an error.
23
+ `--cache-only` makes no requests and notes data past the 5-minute window.
24
+ The first run after upgrading refetches each file once, since older caches
25
+ don't record a version.
26
+
27
+ - **Comments are kept current too.** A new comment doesn't change a file's
28
+ Figma `version`, so the check above never saw it, and `comments` kept
29
+ serving whatever the last `cache prefetch` fetched. `comments` and
30
+ `node-info` (when its output includes comments) now re-fetch a file's
31
+ comments if they haven't been checked in the last 5 minutes. If that fails,
32
+ the cached comments are served with a note on stderr, and the next attempt
33
+ waits for the next 5-minute window. `find` and `ls` still read the cached
34
+ comments as-is, since they span every file.
35
+
36
+ - **Local variables are refreshed with the file.** On accounts with the
37
+ Variables REST API (Enterprise), the cached variables were only re-fetched
38
+ by `cache prefetch`, so after a designer's change `node-info` could pair
39
+ the new design with old variable values. A file that already has cached
40
+ variables now re-fetches them whenever the file itself is refetched. If
41
+ that fails, the old values are kept and `node-info` says on stderr that they
42
+ may be out of date. Files that never had variables (every file on other
43
+ plans) make no extra request.
44
+
45
+ - **New and deleted files show up without a prefetch.** The version check
46
+ only covers files already in the cache, so a file a designer added to a
47
+ folder never appeared in `ls` or `find` until the next `cache prefetch`, and
48
+ a deleted one kept showing. `ls` (root or a folder) and `find` without
49
+ `--in` now check each configured folder's file list at most once every 5
50
+ minutes (one request per folder). New files are fetched, files gone from
51
+ Figma are dropped, and renamed or moved files get their new name or folder.
52
+ stderr reports what changed. Only folders that already have cached files
53
+ get new files fetched automatically, so the first `ls` on an empty cache
54
+ doesn't quietly turn into a full prefetch. `--cache-only` skips the check.
55
+
56
+ - **`node-info file:N` now shows each named style's value.** Every entry in
57
+ the file summary's `styles` list carries a `value`: a FILL style's hex (or
58
+ its paint list for gradients and stacked paints), a TEXT style's font
59
+ family, weight, size, line height and letter spacing, an EFFECT style's
60
+ shadow list. Figma returns no values for styles, so each is read off the
61
+ first visible node applying it, offline from the full-file sidecar; a style
62
+ no visible node uses has no `value`. Library copies are flagged
63
+ `remote: true`, since one can share a name with a local style yet differ.
64
+ `node-info file:N --only styles` gives just this table.
65
+
66
+ ### Removed
67
+
68
+ - **`tokens` is gone.** Its output wasn't usable as design tokens: outside
69
+ `--scope file` it named colors after the layers they were found on
70
+ (`--color-rectangle-13`, `--color-vector`), and it read only fill and text
71
+ styles, so effect styles (shadows, focus rings) never appeared even with
72
+ `--only shadows`. Use instead:
73
+ - a file's named styles and their values → `node-info file:N --only styles`
74
+ (above), which covers effect styles too;
75
+ - the colors, type and effects a frame uses → `node-info file:N:x:y`, which
76
+ lists them with the named style each one comes from.
77
+ - **`context` is gone.** It bundled a tree, a screenshot, `tokens` output and
78
+ `assets` into one directory. Run the parts directly: `node-info` for the
79
+ structure, `screenshot --out` for the image, `assets --out-dir` for icons
80
+ and images.
81
+
82
+ ### Fixed
83
+
84
+ - **macOS binaries are now codesigned and notarized.** Downloading a release
85
+ tarball from GitHub in a browser previously produced "cannot be opened
86
+ because the developer cannot be verified" on first run. Binaries are now
87
+ signed with a Developer ID certificate under the hardened runtime and
88
+ notarized with Apple. Homebrew, npm, and the shell installer were never
89
+ affected (none of them set the quarantine attribute).
90
+ - **Region comments keep their region.** A comment pinned to a region of a
91
+ frame (rather than a point) lost its region size and pin corner, because
92
+ the generated client matched it to the point shape first. Cached comments
93
+ correct themselves on their next refresh.
94
+ - **`file:N` ids are assigned in the same order on every machine.** A fresh
95
+ `cache prefetch` numbered files in filesystem order, so `file:1` could name
96
+ different files on different machines. Files are now numbered by folder,
97
+ then name. Ids already assigned don't change.
98
+
99
+ ## [0.2.3] - 2026-09-23
100
+
101
+ ### Fixed
102
+
103
+ - **`cache prefetch` now backfills a missing `.full.json.gz` sidecar.** A file
104
+ first cached by opening it via URL (`ls <url>`, `node-info <url>`, …) has a
105
+ payload but no full sidecar, and prefetch used to count it as up to date
106
+ forever — so `node-info --cache-only` never worked offline for it. Prefetch
107
+ now refetches an unchanged file whose full sidecar is missing or stamped
108
+ with another schema version (unless `--no-full`).
109
+ - **Root `ls` no longer lists folders with no cached files.** A folder dropped
110
+ from `FIGMA_PROJECTS_IDS` keeps its `proj:N` id (synthetic ids are stable),
111
+ and root `ls` rendered it as an empty project named by its raw folder id
112
+ (e.g. `proj:3 "571382610"`). Such folders are now omitted; `ls proj:N` still
113
+ works, and `cache prefetch --folder-ids <id>` brings a folder back with its
114
+ real name.
115
+ - **`cache prefetch`, root `ls` refresh and `ls proj:N` refresh no longer 403
116
+ with a Figma token created after 2026-08-03.** Figma renamed projects to
117
+ folders; new personal access tokens carry `folders:read` instead of
118
+ `projects:read`, and the deprecated `GET /v1/projects/{id}/files` rejects
119
+ them (`Invalid scope … requires the file_read or files:read or projects:read
120
+ scope`). Listings now use `GET /v2/folders/{id}/files`. Folder ids are the
121
+ old project ids, so `FIGMA_PROJECTS_IDS`, `proj:N` and existing cache metas
122
+ are unchanged; `--project-ids` gained a `--folder-ids` alias. A 403 from v2
123
+ falls back to v1 once (pre-rename tokens are documented to keep working
124
+ there) and prints a hint to regenerate the token. `figma-api` was
125
+ regenerated from the 2026-08-11 upstream spec, which adds `folders_api`;
126
+ `figma-get` gained `folder-files`, `folder-folders`, `folder-meta` and
127
+ `team-folders`, and marks `project-files` / `team-projects` deprecated.
128
+
129
+ ### Changed
130
+
131
+ - **`node-info` output is ~4–5× smaller with nothing implementation-relevant
132
+ dropped.** Measured on a 475-node screen: 634 KB → 141 KB YAML. What changed
133
+ in the curated view (`--raw` is untouched):
134
+ - Hidden (`visible: false`) children are pruned and listed on the parent as
135
+ `hidden_children: [{id, name, type}]` — they were 65% of the output and,
136
+ worse, their descendants were indistinguishable from visible nodes.
137
+ `--include-hidden` restores them; a hidden target is always rendered, and
138
+ so is a hidden layer inside a COMPONENT definition whose visibility is
139
+ wired to a BOOLEAN property (its `property_refs` are the wiring an
140
+ implementer needs). A comment anchored to a pruned node is tagged
141
+ `anchor_rendered: false`.
142
+ - Defaults are elided: `constraints: LEFT/TOP`, `layout.wrap: NO_WRAP`,
143
+ axis `sizing: AUTO` / `align: MIN`, text alignment LEFT/TOP,
144
+ `line_height_unit: PIXELS`.
145
+ - `bounds` is parent-relative on descendants; flow children of an
146
+ auto-layout parent carry only `width`/`height`. A CANVAS has no box, so
147
+ a page's children are relative to the canvas origin (Figma's absolute
148
+ coordinates). `size` appears only when it differs from the bounding box
149
+ (rotation).
150
+ - Colors are `hex` only; the float channels are gone from fills, strokes,
151
+ gradient stops and effects.
152
+ - Text style drops `font_post_script_name`, `line_height_percent`,
153
+ `line_height_percent_font_size` (all restate other fields).
154
+ - `layout_child.sizing` is one `"H/V"` string; padding is one number when
155
+ uniform, else `[top, right, bottom, left]`.
156
+ - `component.component_properties` is split into `variants` and
157
+ `properties` with plain scalar values and Figma's `#n:m` name suffixes
158
+ stripped (kept when stripping would collide). An INSTANCE_SWAP prop keeps
159
+ its allowed swap targets as `preferred`. `property_refs` is emitted on
160
+ the target and inside component definitions only.
161
+ - Geometry leaves below the target (VECTOR, BOOLEAN_OPERATION, STAR, LINE,
162
+ REGULAR_POLYGON) drop only the layout block that cannot apply to them —
163
+ `constraints` when they are flow children of an auto-layout parent,
164
+ `layout_child` when the parent has no auto-layout — so a FILL-width
165
+ divider LINE keeps its sizing; fills and strokes stay so icon color
166
+ tokens remain visible.
167
+ - Bound variables are referenced by short handles (`v1`, `v2`, … in
168
+ first-seen order). The top-level `variables` block is keyed by handle and
169
+ always carries the raw `id`, plus name/values when the Variables sidecar
170
+ is available. The 60-character `VariableID:` strings no longer repeat
171
+ per use.
172
+ - Node-level `bound_variables` no longer repeats `fills[i]`/`strokes[i]`
173
+ entries that the paint itself already carries as `bound_variable` (half
174
+ of the map on a real screen). Entries the paints don't mirror are kept.
175
+ - Floats are rounded to 3 decimals (`14.40007495880127` → `14.4`): Figma
176
+ stores f32 and the REST API prints it as f64. The top-level `variables`
177
+ block gets the same treatment.
178
+ - `--no-variables` once again omits the `variables` block entirely; when a
179
+ sidecar is present but some handles point at library-owned variables, a
180
+ `variables_note` says how many could not be resolved locally.
181
+ - **YAML output uses flow style for leaf containers** (`bounds: {width: 24,
182
+ height: 24}`, `padding: [0, 12, 0, 12]`, one record per row in lists)
183
+ instead of `serde_yaml`'s all-block rendering, via the new `yaml_out`
184
+ printer. Still standard YAML; a `node-info` view goes from 4,564 to 2,464
185
+ lines and 31.3k to 27.8k tokens. Non-ASCII keys are no longer escaped
186
+ (`💠 Before Icon`, not `\u{1f4a0} Before Icon`) and integral floats print
187
+ as `12`, not `12.0`. Multi-line text renders as a `|-` literal block.
188
+ Quoting is conservative enough for YAML 1.1 parsers (PyYAML, Psych):
189
+ `687:45` and `10:5` are quoted (base 60 would read them as integers),
190
+ `1,000` and `2026-9-5` too, large floats print as `1.5e+20`, a leading
191
+ `:`/`?` is quoted inside `{}`/`[]`, and U+2028/U+2029, NEL, DEL and the C1
192
+ range are `\uXXXX`-escaped rather than written raw (libyaml refuses the
193
+ whole document otherwise — Shift+Enter in a Figma text layer produces
194
+ U+2028). Applies to every command's YAML output.
195
+ - **`--json` is compact**, not pretty-printed. Pretty JSON was the most
196
+ token-expensive rendering of all (37% more than the YAML default on the
197
+ same view); `jq` does not care.
198
+ - `scripts/node_info_lossless.py` — checks a curated view against `--raw` for
199
+ the same target (node set, hidden listing, bounds reconstruction, paints,
200
+ layout, text, component props, variable handles).
201
+
10
202
  ## [0.2.2] - 2026-08-22
11
203
 
12
204
  ### Fixed
package/README.md CHANGED
@@ -5,14 +5,12 @@ A command-line tool for navigating, inspecting, and exporting from Figma files v
5
5
  ## What it does
6
6
 
7
7
  - **`ls`** / **`find`** — navigate projects, files, and node trees by name or visible text without copy-pasting node ids. `find` speaks web-search syntax: `"exact phrases"`, uppercase `OR`, `-exclusion`.
8
- - **`node-info`** — single-target view with layout, fills, strokes, effects, text, bound variables, comments, and a ready-made figma.com `url:`. Designed to be pasted into an agent prompt.
8
+ - **`node-info`** — single-target view with layout, fills, strokes, effects, text, bound variables, comments, and a ready-made figma.com `url:`. Hidden subtrees are pruned (listed as `hidden_children`), defaults elided, and variables referenced by short handles, so a full screen is a few dozen KB rather than hundreds. Designed to be pasted into an agent prompt. On a file (`node-info file:N --only styles`) it lists the file's named styles with their values — fill hex, text font, shadow effects.
9
9
  - **`comments`** — list a file's comment threads (replies inline, newest first), threads under a node, or a single thread; filter with `--unresolved` / `--since` / `--grep`.
10
10
  - **`mark`** — curated keyword→node bookmarks (`mark add key file:N:x:y --alias …`); `find` and `library search` surface matching marks ahead of their own hits, and `mark:key` resolves anywhere an id is accepted.
11
11
  - **`library search`** — fuzzy search across a team's published component library (components, component sets, styles).
12
12
  - **`screenshot`** — export a node as PNG / JPG / SVG / PDF.
13
- - **`tokens`** — extract design tokens (colors, fonts, sizes, spacing).
14
- - **`assets`** — bulk-export every icon/image below a node.
15
- - **`context`** — aggregate: tree + screenshot + tokens + assets for a node.
13
+ - **`assets`** — bulk-export every icon (SVG) and image (PNG) below a node, with a manifest.
16
14
  - **`cache prefetch`** / **`cache status`** — pre-warm a local cache so subsequent commands are offline-fast, and report what it holds (per-file age, sidecars, catalog) without touching the network.
17
15
 
18
16
  All commands share a tagged-id grammar — `proj:N`, `file:N`, `file:N:x:y`, or a full `figma.com` URL — so you never paste raw node identifiers by hand.
@@ -57,9 +55,11 @@ Get a personal access token at https://www.figma.com/developers/api#access-token
57
55
  export FIGMA_TOKEN=figd_...
58
56
  ```
59
57
 
58
+ Give the token the **File content** (`file_content:read`) and **File metadata** (`file_metadata:read`) read scopes. The metadata scope powers the cheap freshness check (`/v1/files/{key}/meta`); without it, checks fall back to a slower depth-limited file request.
59
+
60
60
  Optional environment variables:
61
61
 
62
- - `FIGMA_PROJECTS_IDS` — comma-separated project ids; needed by `cache prefetch`.
62
+ - `FIGMA_PROJECTS_IDS` — comma-separated Figma folder ids (Figma renamed "projects" to "folders" in August 2026; the numeric ids are unchanged, so existing values keep working); needed by `cache prefetch`.
63
63
  - `FIGMA_TEAM_ID` — needed by `library search` and for the catalog warm in `cache prefetch`.
64
64
  - `FIGMA_EXPLORER_CACHE_DIR` — override the cache location (default: OS cache dir).
65
65
 
@@ -90,8 +90,8 @@ figma-explorer node-info file:2:1094:66591
90
90
  # Search the team component library
91
91
  figma-explorer library search "icon arrow"
92
92
 
93
- # Export everything below a node as PNGs
94
- figma-explorer assets file:2:1094:66591 ./out/
93
+ # Export every icon and image below a node
94
+ figma-explorer assets file:2:1094:66591 --out-dir ./out/
95
95
 
96
96
  # Pre-warm the cache so subsequent reads are offline
97
97
  figma-explorer cache prefetch
@@ -99,12 +99,14 @@ figma-explorer cache prefetch
99
99
 
100
100
  ## Output
101
101
 
102
- YAML by default (terse, agent-friendly). Pass `--json` for full pretty JSON suitable for `jq` pipelines.
102
+ YAML by default (terse, agent-friendly: block style for the tree, flow style `{k: v}` for leaf maps and short lists). Pass `--json` for compact JSON suitable for `jq` pipelines.
103
103
 
104
104
  ## Cache
105
105
 
106
106
  A local rkyv cache lives at `$FIGMA_EXPLORER_CACHE_DIR` or the OS cache dir. It is **per-machine** — rkyv archives are not endianness-portable, so don't sync the cache directory across architectures. `cache clear` removes everything; `cache prefetch` warms it from `FIGMA_PROJECTS_IDS`.
107
107
 
108
+ Designers edit files live, so cached data is checked against Figma before it is served: at most once every 5 minutes per file, a ~1 KB `GET /v1/files/{key}/meta` compares Figma's `version` with the cached one, and a changed file is refetched (payload and `node-info` sidecar together) before the command answers. If the check or refetch fails, the cached copy is served with a note on stderr. `--cache-only` never checks; it notes data older than the window instead.
109
+
108
110
  ## License
109
111
 
110
112
  MIT. See `LICENSE-MIT`.
@@ -19,7 +19,7 @@
19
19
  "hasInstallScript": true,
20
20
  "license": "MIT",
21
21
  "name": "@akesson/figma-explorer",
22
- "version": "0.2.2"
22
+ "version": "0.3.0"
23
23
  },
24
24
  "node_modules/detect-libc": {
25
25
  "engines": {
@@ -48,5 +48,5 @@
48
48
  }
49
49
  },
50
50
  "requires": true,
51
- "version": "0.2.2"
51
+ "version": "0.3.0"
52
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "artifactDownloadUrls": [
3
- "https://github.com/akesson/figma-explorer/releases/download/v0.2.2"
3
+ "https://github.com/akesson/figma-explorer/releases/download/v0.3.0"
4
4
  ],
5
5
  "bin": {
6
6
  "figma-explorer": "run-figma-explorer.js"
@@ -8,7 +8,7 @@
8
8
  "dependencies": {
9
9
  "detect-libc": "^2.1.2"
10
10
  },
11
- "description": "High-level CLI on top of the Figma REST API: name-based navigation, asset extraction, design tokens, and bundled context export.",
11
+ "description": "High-level CLI on top of the Figma REST API: name-based navigation, implementation-ready node views, comments, screenshots, and asset export.",
12
12
  "devDependencies": {
13
13
  "prettier": "^3.8.3"
14
14
  },
@@ -26,7 +26,7 @@
26
26
  "figma",
27
27
  "cli",
28
28
  "design",
29
- "design-tokens"
29
+ "design-to-code"
30
30
  ],
31
31
  "license": "MIT",
32
32
  "name": "@akesson/figma-explorer",
@@ -95,7 +95,7 @@
95
95
  "zipExt": ".tar.xz"
96
96
  }
97
97
  },
98
- "version": "0.2.2",
98
+ "version": "0.3.0",
99
99
  "volta": {
100
100
  "node": "18.14.1",
101
101
  "npm": "9.5.0"