figdiff 0.0.0-stage → 0.1.0-alpha.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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -2
  3. package/dist/action/changed-files.d.ts +14 -0
  4. package/dist/action/changed-files.js +35 -0
  5. package/dist/action/changed-files.js.map +1 -0
  6. package/dist/action/comment.d.ts +28 -0
  7. package/dist/action/comment.js +70 -0
  8. package/dist/action/comment.js.map +1 -0
  9. package/dist/action/github.d.ts +15 -0
  10. package/dist/action/github.js +62 -0
  11. package/dist/action/github.js.map +1 -0
  12. package/dist/action/glob.d.ts +10 -0
  13. package/dist/action/glob.js +45 -0
  14. package/dist/action/glob.js.map +1 -0
  15. package/dist/action/main.d.ts +20 -0
  16. package/dist/action/main.js +111 -0
  17. package/dist/action/main.js.map +1 -0
  18. package/dist/action.d.ts +2 -0
  19. package/dist/action.js +7 -0
  20. package/dist/action.js.map +1 -0
  21. package/dist/cli.d.ts +2 -0
  22. package/dist/cli.js +21 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/diff/diff.d.ts +11 -0
  25. package/dist/diff/diff.js +68 -0
  26. package/dist/diff/diff.js.map +1 -0
  27. package/dist/diff/summary.d.ts +3 -0
  28. package/dist/diff/summary.js +40 -0
  29. package/dist/diff/summary.js.map +1 -0
  30. package/dist/diff/types.d.ts +59 -0
  31. package/dist/diff/types.js +2 -0
  32. package/dist/diff/types.js.map +1 -0
  33. package/dist/git.d.ts +12 -0
  34. package/dist/git.js +32 -0
  35. package/dist/git.js.map +1 -0
  36. package/dist/html.d.ts +25 -0
  37. package/dist/html.js +205 -0
  38. package/dist/html.js.map +1 -0
  39. package/dist/index.d.ts +18 -0
  40. package/dist/index.js +12 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/parser/document.d.ts +12 -0
  43. package/dist/parser/document.js +71 -0
  44. package/dist/parser/document.js.map +1 -0
  45. package/dist/parser/index.d.ts +10 -0
  46. package/dist/parser/index.js +21 -0
  47. package/dist/parser/index.js.map +1 -0
  48. package/dist/render/compare.d.ts +19 -0
  49. package/dist/render/compare.js +61 -0
  50. package/dist/render/compare.js.map +1 -0
  51. package/dist/render/figcraft-cli.d.ts +21 -0
  52. package/dist/render/figcraft-cli.js +63 -0
  53. package/dist/render/figcraft-cli.js.map +1 -0
  54. package/dist/render/frames.d.ts +19 -0
  55. package/dist/render/frames.js +53 -0
  56. package/dist/render/frames.js.map +1 -0
  57. package/dist/render/png.d.ts +14 -0
  58. package/dist/render/png.js +185 -0
  59. package/dist/render/png.js.map +1 -0
  60. package/dist/report.d.ts +7 -0
  61. package/dist/report.js +109 -0
  62. package/dist/report.js.map +1 -0
  63. package/dist/run.d.ts +23 -0
  64. package/dist/run.js +144 -0
  65. package/dist/run.js.map +1 -0
  66. package/package.json +51 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 figdiff contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,309 @@
1
- # Temporary Holding Version
1
+ # figdiff
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Review design changes the way you review code.**
4
+
5
+ `figdiff` compares two versions of a [FigCraft](https://github.com/storytold/figcraft) design file (`.figcraft`) and explains what changed, both as a readable list of changes and as before/after images.
6
+
7
+ > **Status: early development (pre-alpha).** Phase 0 is in progress. The parser and a basic text diff exist and are published as a pre-alpha on npm (see [Try it](#try-it)); the HTML report, visual diff and GitHub Action are still planned. Commands in the usage section below are the intended interface and may change. Feedback is very welcome: please open an issue.
8
+
9
+ > **Not affiliated.** figdiff is an independent community project. It is not made by, endorsed by, or connected to the FigCraft / ArtCraft authors. FigCraft is a trademark of its respective owners.
10
+
11
+ ---
12
+
13
+ ## Table of contents
14
+
15
+ - [Try it](#try-it)
16
+ - [The problem](#the-problem)
17
+ - [The idea](#the-idea)
18
+ - [Planned usage](#planned-usage)
19
+ - [How it will work](#how-it-will-work)
20
+ - [Who is it for](#who-is-it-for)
21
+ - [Roadmap](#roadmap)
22
+ - [Risks and open questions](#risks-and-open-questions)
23
+ - [Contributing](#contributing)
24
+ - [License](#license)
25
+
26
+ ---
27
+
28
+ ## Try it
29
+
30
+ The first pre-alpha is on npm under the `alpha` tag. It needs Node.js 20 or newer.
31
+
32
+ ```sh
33
+ npx figdiff@alpha before.figcraft after.figcraft
34
+ ```
35
+
36
+ To install it globally instead: `npm install -g figdiff@alpha`.
37
+
38
+ Today it compares two files and prints a grouped list of changes (`--json` and `--exit-code` are available). The HTML report, visual diff and GitHub Action are on the roadmap below. Matching of nodes relies on ids staying stable between saves in FigCraft, which is not yet verified, so treat results as preliminary.
39
+
40
+ ---
41
+
42
+ ## The problem
43
+
44
+ A `.figcraft` file is a zip archive. When it changes in a Git repository, GitHub and `git diff` can only say "binary file changed". A reviewer has no way to tell whether a designer moved one button or rebuilt a whole screen, short of opening the file in FigCraft and comparing by eye.
45
+
46
+ Code has diffs, pull request comments, and review tools. Design files, stored in the same repositories, have none of that.
47
+
48
+ ## The idea
49
+
50
+ figdiff gives design files the same review workflow as code:
51
+
52
+ - **Semantic diff.** A list of changes in plain language, for example:
53
+ `Frame "Login" › Button "Sign in": fill #2563EB → #1D4ED8`
54
+ - **Visual diff.** Before, after, and highlighted-difference images for every frame that changed.
55
+ - **Automatic pull request comments.** A GitHub Action that posts both of the above on every pull request that touches a design file.
56
+
57
+ ## Planned usage
58
+
59
+ > These commands are the intended interface and may change.
60
+
61
+ **Compare two files locally**
62
+
63
+ ```sh
64
+ npx figdiff before.figcraft after.figcraft
65
+ ```
66
+
67
+ ```text
68
+ Page "App"
69
+ Frame "Components"
70
+ - Landscape › Time=Day › Sun (ellipse)
71
+ ~ Chip › Selected=On › Label: characters All trails → All routes
72
+ Frame "Home — Light"
73
+ ~ Status bar › Indicators › Signal › Bar 1: fills #0F172A → #E6172A
74
+ ~ Status bar › Indicators › Signal › Bar 1: width 3 → 15
75
+
76
+ 4 changes in 2 frames (1 removed, 3 property changes)
77
+ ```
78
+
79
+ Useful flags: `--html report.html` also writes a self-contained HTML report (add `--images` for rendered before/after images), `--json` prints the summary and every change as JSON, and `--exit-code` exits with 1 when the files differ, so it can fail a CI step.
80
+
81
+ **Generate a shareable report**
82
+
83
+ ```sh
84
+ npx figdiff before.figcraft after.figcraft --html report.html
85
+ ```
86
+
87
+ One self-contained HTML file with the list of changes, filters, and light and dark mode. No server needed, so it can be sent by email or chat to a developer or client. With `--images` (and FigCraft's `figcraft-cli` installed) it also embeds before/after images with a swipe slider and a difference view. This part is experimental: it has been tested with a stand-in for `figcraft-cli` but not yet with the real one.
88
+
89
+ **Compare against another Git branch**
90
+
91
+ ```sh
92
+ npx figdiff design/app.figcraft --against main
93
+ ```
94
+
95
+ `--against` accepts any git revision (a branch, tag, or commit such as `HEAD~1`). The file is compared with its committed version, so it also works for uncommitted edits: `--against HEAD` shows what you changed since the last commit.
96
+
97
+ **Use it as a `git difftool`**
98
+
99
+ ```sh
100
+ git config difftool.figdiff.cmd 'figdiff "$LOCAL" "$REMOTE"'
101
+ git difftool -y -t figdiff main -- design/app.figcraft
102
+ ```
103
+
104
+ **Run on every pull request**
105
+
106
+ The GitHub Action (`action.yml`, currently in alpha) posts one comment per pull request with what changed in each `.figcraft` file, and keeps that comment up to date on every push. Full HTML reports are attached to the workflow run.
107
+
108
+ ```yaml
109
+ # .github/workflows/figdiff.yml (also in examples/figdiff.yml)
110
+ name: Design changes
111
+ on:
112
+ pull_request:
113
+ paths: ["**/*.figcraft"]
114
+
115
+ permissions:
116
+ contents: read
117
+ pull-requests: write
118
+
119
+ jobs:
120
+ figdiff:
121
+ runs-on: ubuntu-latest
122
+ steps:
123
+ - uses: actions/checkout@v4
124
+ with:
125
+ fetch-depth: 0 # needed to read the base commit
126
+ - uses: bintangyoga/figdiff@main
127
+ with:
128
+ files: "design/**/*.figcraft"
129
+ ```
130
+
131
+ | Input | Default | Meaning |
132
+ | --- | --- | --- |
133
+ | `files` | `**/*.figcraft` | Glob patterns (commas or new lines) of the design files to look at |
134
+ | `comment` | `true` | `false` writes the reports without commenting |
135
+ | `max-files` | `20` | Compare at most this many files per pull request |
136
+ | `upload-reports` | `true` | Upload the HTML reports as the artifact `figdiff-reports` |
137
+ | `report-dir` | `figdiff-reports` | Where the reports are written |
138
+ | `version` | `latest` | Version of the `figdiff` npm package to use |
139
+ | `token` | `github.token` | Token used to comment |
140
+
141
+ Pull requests from forks get a read-only token, so no comment is posted; the changes are printed in the job log instead. Images in the comment are not included yet (see Phase 3).
142
+
143
+ ## How it will work
144
+
145
+ ```text
146
+ before.figcraft ─┐ ┌─ semantic diff (changes as text/JSON)
147
+ ├─► parse + match ──►┤
148
+ after.figcraft ─┘ └─ visual diff (render + compare images)
149
+ ```
150
+
151
+ 1. **Parser.** Opens the `.figcraft` archive and reads its `document.json` into an internal model. This is the only part that depends on the file format, so it is isolated and versioned.
152
+ 2. **Differ.** Matches nodes between the two versions (by id, with fallbacks) and produces a list of added, removed, moved, and modified nodes.
153
+ 3. **Renderer.** Uses FigCraft's own headless CLI (`figcraft-cli`) to render the changed frames to images, then compares them pixel by pixel.
154
+ 4. **Reporters.** Terminal output, JSON, a self-contained HTML report, and a GitHub pull request comment.
155
+
156
+ Design principles:
157
+
158
+ - **Independent.** A separate project. It reads the public file format and uses the public CLI; it does not modify FigCraft.
159
+ - **Local-first.** Runs on your machine or in your CI. No accounts, no servers, no telemetry.
160
+ - **Resilient to format changes.** FigCraft is in alpha and its format may change. The parser is kept separate from the diff logic and tested against files from multiple FigCraft versions.
161
+ - **Useful without Git.** The CLI and HTML report work on any two files.
162
+
163
+ ## Who is it for
164
+
165
+ - **Developers** who receive design files and want to know what changed since the last version.
166
+ - **Design engineers and designers who use Git**, and want design changes reviewed in pull requests.
167
+ - **Freelancers and agencies** who exchange versions with clients and need a clear "what changed since last time" report.
168
+
169
+ ---
170
+
171
+ ## Roadmap
172
+
173
+ Estimated timeline: about 18 weeks to v1.0, assuming one maintainer at roughly 10 hours per week. Each phase ends with a usable release, and the next phase starts only after the exit criteria are met.
174
+
175
+ | Phase | Weeks | Goal | Release |
176
+ | --- | --- | --- | --- |
177
+ | 0 | 1–2 | Research and setup | n/a |
178
+ | 1 | 3–6 | Semantic diff in the CLI | v0.1.0 |
179
+ | 2 | 7–10 | HTML report and visual diff | v0.2.0 |
180
+ | 3 | 11–14 | GitHub Action | v0.3.0 |
181
+ | 4 | 15–18 | Public launch | v1.0.0 |
182
+ | 5 | After v1.0 | Driven by user demand | n/a |
183
+
184
+ ### Phase 0: Research and setup (weeks 1–2)
185
+
186
+ Confirm that FigCraft's file format and CLI are stable enough to build on, before writing product code.
187
+
188
+ - [x] Download sample files from FigCraft's `examples/` folder, unzip them, and study the structure of `document.json`
189
+ - [x] Document the node schema: id, type, name, parent, position, size, fills, strokes, text, auto layout, components, variables
190
+ - [ ] Verify that node ids stay stable when a file is saved again (critical for matching nodes between versions)
191
+ - [ ] Test `figcraft-cli render`, `export --node`, and `convert` on headless Linux
192
+ - [ ] Check whether `figcraft-cli` is available as a prebuilt binary or must be built with Cargo
193
+ - [ ] Check name availability on npm and GitHub; choose a fallback name if needed
194
+ - [x] Set up the repository: TypeScript, tests, CI
195
+ - [ ] Add linting and formatting (ESLint, Prettier)
196
+
197
+ **Deliverables:** format notes (`docs/format-notes.md`), 5–10 before/after sample file pairs, a working project skeleton.
198
+
199
+ **Exit criteria:** the `document.json` schema is understood and node ids are proven stable, or a fallback matching strategy is designed.
200
+
201
+ ### Phase 1: Semantic diff in the CLI (weeks 3–6)
202
+
203
+ Deliver `figdiff a.figcraft b.figcraft` printing a correct list of changes in the terminal. No images yet.
204
+
205
+ - [x] Parser for core properties: type, name, hierarchy, position, size, fills, strokes, corner radius, text, font
206
+ - [ ] Node matching by id, with fallback to name and hierarchy path
207
+ - [x] Detect added, removed, reparented, and modified nodes
208
+ - [x] Group results by page and top-level frame so output is easy to read
209
+ - [x] Summary line (for example, "12 changes in 3 frames")
210
+ - [x] `--json` output for use by other tools
211
+ - [x] `--exit-code` flag and stable exit codes (0 ok, 1 differences, 2 error)
212
+ - [x] Detect changes in layer order
213
+ - [ ] Tests using the sample file pairs from Phase 0
214
+
215
+ **Release:** `v0.1.0` on npm, usable with `npx figdiff`.
216
+
217
+ **Exit criteria:** on every sample pair, the list of changes is complete and contains no false changes.
218
+
219
+ ### Phase 2: HTML report and visual diff (weeks 7–10)
220
+
221
+ Deliver a self-contained HTML report that anyone can open, including designers who do not use Git.
222
+
223
+ - [x] Self-contained HTML report of the semantic diff (`--html report.html`), with filters and light/dark mode
224
+ - [x] Before/after swipe slider and a difference view with the share of changed pixels
225
+ - [x] Pixel comparison with highlighted changed areas (own PNG decoder, no dependencies; checked against real FigCraft PNGs)
226
+ - [x] A single `report.html` with embedded images and no server
227
+ - [x] Graceful fallback when `figcraft-cli` is unavailable: the report is still written, with a warning
228
+ - [x] Frames that exist in only one version (added or removed) are shown on their own
229
+ - [ ] Verify rendering against a real `figcraft-cli` (implemented from its documentation and tested with a stand-in; the exact `--node` and `--page` behavior is unconfirmed)
230
+ - [ ] Render at 2x as well as 1x
231
+ - [x] Side-by-side view next to the swipe slider
232
+ - [ ] Semantic change list next to the images; clicking a change highlights its location
233
+ - [x] `--against <revision>` (compare a file with its version on a branch, tag or commit) and `git difftool` usage
234
+
235
+ **Release:** `v0.2.0`, with the first demo GIF in this README.
236
+
237
+ **Exit criteria:** 3–5 people outside the project open the report and understand the changes without extra explanation.
238
+
239
+ ### Phase 3: GitHub Action (weeks 11–14)
240
+
241
+ Deliver an automatic comment on every pull request that changes a `.figcraft` file, set up with one workflow file.
242
+
243
+ - [x] Detect changed `.figcraft` files in a pull request and compare each with its base version
244
+ - [ ] Install `figcraft-cli` on the runner, with caching
245
+ - [x] Pull request comment with a summary and per-frame changes (before/after images still to do)
246
+ - [ ] Host images via workflow artifacts or a dedicated branch (GitHub comments need image URLs)
247
+ - [x] Update a single comment on each push instead of posting new ones
248
+ - [x] Configuration options: file patterns, file limit, report folder (render scale and comment language still to do)
249
+ - [ ] Public example repository that uses the Action as a live demo
250
+
251
+ **Release:** `v0.3.0` and a listing on GitHub Marketplace.
252
+
253
+ **Exit criteria:** the Action runs reliably on the example repository and on at least one real project.
254
+
255
+ ### Phase 4: Public launch (weeks 15–18)
256
+
257
+ Deliver a project that other people can use and contribute to, then announce it.
258
+
259
+ - [ ] Support for advanced properties: components and instances, variants, variables and tokens, effects, prototyping, images
260
+ - [ ] Performance testing on large files (thousands of nodes); target a diff in a few seconds
261
+ - [ ] Compatibility table of tested FigCraft versions, updated with each FigCraft release
262
+ - [ ] Documentation: CLI guide, Action guide, contributor guide
263
+ - [ ] `good first issue` labels to welcome contributors
264
+ - [ ] Announcement: FigCraft community, Show HN, Reddit, and design communities
265
+
266
+ **Release:** `v1.0.0`, with a stable CLI and JSON output format.
267
+
268
+ ### Phase 5: After v1.0
269
+
270
+ Direction will follow user demand. Candidates:
271
+
272
+ - **Three-way merge** of design files from different branches, with conflict markers
273
+ - **Token and consistency checks** that warn in a pull request about colors or spacing outside the design system
274
+ - **Other platforms:** GitLab CI, Gitea, Forgejo
275
+ - **MCP server**, so AI agents can ask "what changed in this design?"
276
+ - **Automatic design changelog** generated from Git tags
277
+ - **Upstream collaboration:** open a diff report directly in FigCraft, if the maintainers are interested
278
+
279
+ ---
280
+
281
+ ## Risks and open questions
282
+
283
+ | Risk | Impact | Mitigation |
284
+ | --- | --- | --- |
285
+ | The `.figcraft` format changes (FigCraft is alpha) | Parser breaks on new FigCraft releases | Isolated parser, compatibility table, tests on files from each version |
286
+ | Node ids are not stable between saves | Diffs full of false changes | Fallback matching by name and path, validated in Phase 0 |
287
+ | `figcraft-cli` is hard to install in CI | Slow or failing GitHub Action | Cache the binary; fall back to semantic diff only |
288
+ | FigCraft adds its own diff feature | Less reason to use figdiff | Focus on review in Git and pull requests; talk to the maintainers early |
289
+ | Small FigCraft user base | Slow adoption | Start from real needs; keep the core model usable for other formats later |
290
+
291
+ Open questions:
292
+
293
+ - Is the project name `figdiff` available on npm and GitHub?
294
+ - Does `figcraft-cli` ship as a downloadable binary for Linux, macOS, and Windows?
295
+ - How much of FigCraft's built-in `versions/` and `branches/` data is useful to reuse?
296
+
297
+ ## Contributing
298
+
299
+ Contributions are welcome, even at this early stage. The most useful things right now:
300
+
301
+ - Feedback on the plan: open an issue with your use case
302
+ - Sample `.figcraft` file pairs (before/after) that you are allowed to share
303
+ - Notes on the file format, once Phase 0 begins
304
+
305
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
306
+
307
+ ## License
308
+
309
+ [MIT](LICENSE)
@@ -0,0 +1,14 @@
1
+ export interface ChangedFile {
2
+ status: "added" | "modified" | "deleted" | "renamed";
3
+ /** Path in the new version (or the old one, for a deleted file). */
4
+ path: string;
5
+ /** Path in the base version, for a renamed file. */
6
+ oldPath?: string;
7
+ }
8
+ /**
9
+ * Parse the output of `git diff --name-status -z`. Entries are separated by NUL characters;
10
+ * renames and copies carry two paths.
11
+ */
12
+ export declare function parseNameStatus(output: string): ChangedFile[];
13
+ /** Keep the files that match one of the patterns (a rename matches on either of its two paths). */
14
+ export declare function filterByPatterns(files: ChangedFile[], patterns: string[]): ChangedFile[];
@@ -0,0 +1,35 @@
1
+ import { matchesAny } from "./glob.js";
2
+ /**
3
+ * Parse the output of `git diff --name-status -z`. Entries are separated by NUL characters;
4
+ * renames and copies carry two paths.
5
+ */
6
+ export function parseNameStatus(output) {
7
+ const tokens = output.split("\0");
8
+ const files = [];
9
+ for (let i = 0; i < tokens.length; i++) {
10
+ const code = tokens[i];
11
+ if (code === "")
12
+ continue;
13
+ const kind = code[0];
14
+ if (kind === "R") {
15
+ const oldPath = tokens[++i];
16
+ const path = tokens[++i];
17
+ files.push({ status: "renamed", path, oldPath });
18
+ }
19
+ else if (kind === "C") {
20
+ i++; // the copy source is not interesting
21
+ files.push({ status: "added", path: tokens[++i] });
22
+ }
23
+ else {
24
+ const path = tokens[++i];
25
+ const status = kind === "A" ? "added" : kind === "D" ? "deleted" : "modified";
26
+ files.push({ status, path });
27
+ }
28
+ }
29
+ return files;
30
+ }
31
+ /** Keep the files that match one of the patterns (a rename matches on either of its two paths). */
32
+ export function filterByPatterns(files, patterns) {
33
+ return files.filter((f) => matchesAny(f.path, patterns) || (f.oldPath !== undefined && matchesAny(f.oldPath, patterns)));
34
+ }
35
+ //# sourceMappingURL=changed-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changed-files.js","sourceRoot":"","sources":["../../src/action/changed-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAUvC;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,KAAK,GAAkB,EAAE,CAAC;IAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAW,CAAC;QACjC,IAAI,IAAI,KAAK,EAAE;YAAE,SAAS;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,CAAW,CAAC;YACtC,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,CAAW,CAAC;YACnC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACnD,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACxB,CAAC,EAAE,CAAC,CAAC,qCAAqC;YAC1C,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAW,EAAE,CAAC,CAAC;QAC/D,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,CAAW,CAAC;YACnC,MAAM,MAAM,GAAG,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC;YAC9E,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,gBAAgB,CAAC,KAAoB,EAAE,QAAkB;IACvE,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,UAAU,CAAC,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC3H,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { Summary } from "../diff/types.js";
2
+ import type { ChangedFile } from "./changed-files.js";
3
+ /** Hidden marker used to find our own comment again on later pushes. */
4
+ export declare const COMMENT_MARKER = "<!-- figdiff-comment -->";
5
+ export interface FileResult {
6
+ file: ChangedFile;
7
+ /** Present when the two versions were compared. */
8
+ summary?: Summary;
9
+ /** The text diff, as printed by `figdiff`. */
10
+ text?: string;
11
+ /** Why the comparison could not be made. */
12
+ error?: string;
13
+ /** Name of the HTML report written for this file, if any. */
14
+ reportName?: string;
15
+ }
16
+ export interface CommentContext {
17
+ /** Link to the workflow run, where the report artifact can be downloaded. */
18
+ runUrl?: string;
19
+ /** Whether the HTML reports were uploaded as an artifact. */
20
+ reportsUploaded: boolean;
21
+ version?: string;
22
+ }
23
+ /** A file counts as changed unless it was only re-saved without any design difference. */
24
+ export declare function isSubstantive(r: FileResult): boolean;
25
+ /** The pull request comment for a set of compared files. */
26
+ export declare function buildComment(results: FileResult[], ctx: CommentContext): string;
27
+ /** Replaces an earlier comment when a later push no longer changes any design file. */
28
+ export declare function buildEmptyComment(): string;
@@ -0,0 +1,70 @@
1
+ import { formatSummary } from "../report.js";
2
+ /** Hidden marker used to find our own comment again on later pushes. */
3
+ export const COMMENT_MARKER = "<!-- figdiff-comment -->";
4
+ /** GitHub rejects comments over 65,536 characters; stay well below. */
5
+ const MAX_COMMENT = 60_000;
6
+ const MAX_FILE_TEXT = 15_000;
7
+ /** A file counts as changed unless it was only re-saved without any design difference. */
8
+ export function isSubstantive(r) {
9
+ if (r.error)
10
+ return true;
11
+ if (r.file.status === "added" || r.file.status === "deleted" || r.file.status === "renamed")
12
+ return true;
13
+ return (r.summary?.changes ?? 0) > 0;
14
+ }
15
+ /** Wrap text in a code span, choosing a delimiter that cannot be broken by backticks inside. */
16
+ function code(text) {
17
+ const longest = Math.max(0, ...(text.match(/`+/g) ?? []).map((m) => m.length));
18
+ const fence = "`".repeat(longest + 1);
19
+ return `${fence}${text.startsWith("`") || text.endsWith("`") ? ` ${text} ` : text}${fence}`;
20
+ }
21
+ /** A fenced code block whose fence is longer than any backtick run inside. */
22
+ function block(text) {
23
+ const longest = Math.max(2, ...(text.match(/`+/g) ?? []).map((m) => m.length));
24
+ const fence = "`".repeat(longest + 1);
25
+ return `${fence}text\n${text.replace(/\n$/, "")}\n${fence}`;
26
+ }
27
+ function truncate(text, limit) {
28
+ if (text.length <= limit)
29
+ return text;
30
+ const cut = text.slice(0, limit);
31
+ const kept = cut.slice(0, Math.max(cut.lastIndexOf("\n"), 0));
32
+ const hidden = text.slice(kept.length).split("\n").filter(Boolean).length;
33
+ return `${kept}\n… ${hidden} more lines not shown`;
34
+ }
35
+ function section(r) {
36
+ const { file } = r;
37
+ const name = code(file.path);
38
+ switch (file.status) {
39
+ case "added":
40
+ return `**${name}**: new design file, nothing earlier to compare with.`;
41
+ case "deleted":
42
+ return `**${name}**: design file deleted.`;
43
+ default:
44
+ }
45
+ const renamed = file.status === "renamed" ? ` (renamed from ${code(file.oldPath ?? "")})` : "";
46
+ if (r.error)
47
+ return `**${name}**${renamed}: could not be compared. ${r.error}`;
48
+ if (!r.summary || r.summary.changes === 0) {
49
+ return `**${name}**${renamed}: ${file.status === "renamed" ? "renamed, no design changes." : "saved again, no design changes."}`;
50
+ }
51
+ const details = r.text
52
+ ? `\n\n<details><summary>Show changes</summary>\n\n${block(truncate(r.text, MAX_FILE_TEXT))}\n\n</details>`
53
+ : "";
54
+ return `**${name}**${renamed}: ${formatSummary(r.summary)}${details}`;
55
+ }
56
+ /** The pull request comment for a set of compared files. */
57
+ export function buildComment(results, ctx) {
58
+ const parts = [`${COMMENT_MARKER}\n## Design changes`, ...results.map(section)];
59
+ if (ctx.reportsUploaded && ctx.runUrl && results.some((r) => r.reportName)) {
60
+ parts.push(`Full HTML reports (with filters and, if enabled, images) are attached to [this workflow run](${ctx.runUrl}) as the artifact **figdiff-reports**.`);
61
+ }
62
+ parts.push(`<sub>Generated by figdiff${ctx.version ? ` ${ctx.version}` : ""}. Changes are matched by node id.</sub>`);
63
+ const body = parts.join("\n\n");
64
+ return body.length <= MAX_COMMENT ? body : `${body.slice(0, MAX_COMMENT)}\n\n… (comment shortened)`;
65
+ }
66
+ /** Replaces an earlier comment when a later push no longer changes any design file. */
67
+ export function buildEmptyComment() {
68
+ return `${COMMENT_MARKER}\n## Design changes\n\nThis pull request no longer changes any design file.`;
69
+ }
70
+ //# sourceMappingURL=comment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment.js","sourceRoot":"","sources":["../../src/action/comment.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /** An error from the GitHub API, with the HTTP status so callers can tell "no permission" apart. */
2
+ export declare class GitHubError extends Error {
3
+ readonly status: number;
4
+ constructor(message: string, status: number);
5
+ }
6
+ export interface GitHubApi {
7
+ fetch: typeof fetch;
8
+ /** For example https://api.github.com */
9
+ baseUrl: string;
10
+ token: string;
11
+ }
12
+ /** Find the comment figdiff wrote earlier on this pull request, if any. */
13
+ export declare function findComment(api: GitHubApi, repo: string, pr: number): Promise<number | undefined>;
14
+ /** Create the comment, or update the one found earlier so a pull request only ever has one. */
15
+ export declare function upsertComment(api: GitHubApi, repo: string, pr: number, body: string, existing?: number): Promise<"created" | "updated">;
@@ -0,0 +1,62 @@
1
+ import { COMMENT_MARKER } from "./comment.js";
2
+ /** An error from the GitHub API, with the HTTP status so callers can tell "no permission" apart. */
3
+ export class GitHubError extends Error {
4
+ status;
5
+ constructor(message, status) {
6
+ super(message);
7
+ this.status = status;
8
+ }
9
+ }
10
+ async function request(api, method, path, body) {
11
+ const res = await api.fetch(`${api.baseUrl}${path}`, {
12
+ method,
13
+ headers: {
14
+ authorization: `Bearer ${api.token}`,
15
+ accept: "application/vnd.github+json",
16
+ "x-github-api-version": "2022-11-28",
17
+ "user-agent": "figdiff-action",
18
+ ...(body === undefined ? {} : { "content-type": "application/json" }),
19
+ },
20
+ body: body === undefined ? undefined : JSON.stringify(body),
21
+ });
22
+ if (!res.ok) {
23
+ let detail = "";
24
+ try {
25
+ detail = (await res.json()).message ?? "";
26
+ }
27
+ catch {
28
+ // the error body was not JSON
29
+ }
30
+ throw new GitHubError(`GitHub API ${method} ${path} failed with ${res.status}${detail ? `: ${detail}` : ""}`, res.status);
31
+ }
32
+ return (res.status === 204 ? undefined : await res.json());
33
+ }
34
+ /** Find the comment figdiff wrote earlier on this pull request, if any. */
35
+ export async function findComment(api, repo, pr) {
36
+ for (let page = 1; page <= 10; page++) {
37
+ const comments = await request(api, "GET", `/repos/${repo}/issues/${pr}/comments?per_page=100&page=${page}`);
38
+ const found = comments.find((c) => c.body?.includes(COMMENT_MARKER));
39
+ if (found)
40
+ return found.id;
41
+ if (comments.length < 100)
42
+ return undefined;
43
+ }
44
+ return undefined;
45
+ }
46
+ /** Create the comment, or update the one found earlier so a pull request only ever has one. */
47
+ export async function upsertComment(api, repo, pr, body, existing) {
48
+ if (existing !== undefined) {
49
+ try {
50
+ await request(api, "PATCH", `/repos/${repo}/issues/comments/${existing}`, { body });
51
+ return "updated";
52
+ }
53
+ catch (err) {
54
+ // The old comment may have been deleted or belong to someone else: fall through and post a new one.
55
+ if (!(err instanceof GitHubError) || (err.status !== 404 && err.status !== 403))
56
+ throw err;
57
+ }
58
+ }
59
+ await request(api, "POST", `/repos/${repo}/issues/${pr}/comments`, { body });
60
+ return "created";
61
+ }
62
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/action/github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /** Split an input such as "design/**\/*.figcraft, assets/*.figcraft" (commas or new lines) into patterns. */
2
+ export declare function parsePatterns(input: string): string[];
3
+ /**
4
+ * Convert a glob to a regular expression. Supports `*` (within a folder), `**` (across folders)
5
+ * and `?`. `**\/` also matches no folder at all, so `design/**\/*.figcraft` matches
6
+ * `design/app.figcraft`.
7
+ */
8
+ export declare function globToRegExp(pattern: string): RegExp;
9
+ /** True when the path matches at least one pattern. */
10
+ export declare function matchesAny(path: string, patterns: string[]): boolean;
@@ -0,0 +1,45 @@
1
+ /** Split an input such as "design/**\/*.figcraft, assets/*.figcraft" (commas or new lines) into patterns. */
2
+ export function parsePatterns(input) {
3
+ return input
4
+ .split(/[\n,]/)
5
+ .map((p) => p.trim().replace(/^\.\//, ""))
6
+ .filter((p) => p.length > 0);
7
+ }
8
+ /**
9
+ * Convert a glob to a regular expression. Supports `*` (within a folder), `**` (across folders)
10
+ * and `?`. `**\/` also matches no folder at all, so `design/**\/*.figcraft` matches
11
+ * `design/app.figcraft`.
12
+ */
13
+ export function globToRegExp(pattern) {
14
+ let source = "";
15
+ for (let i = 0; i < pattern.length; i++) {
16
+ const c = pattern[i];
17
+ if (c === "*") {
18
+ if (pattern[i + 1] === "*") {
19
+ i++;
20
+ if (pattern[i + 1] === "/") {
21
+ i++;
22
+ source += "(?:.*/)?";
23
+ }
24
+ else {
25
+ source += ".*";
26
+ }
27
+ }
28
+ else {
29
+ source += "[^/]*";
30
+ }
31
+ }
32
+ else if (c === "?") {
33
+ source += "[^/]";
34
+ }
35
+ else {
36
+ source += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
37
+ }
38
+ }
39
+ return new RegExp(`^${source}$`);
40
+ }
41
+ /** True when the path matches at least one pattern. */
42
+ export function matchesAny(path, patterns) {
43
+ return patterns.some((p) => globToRegExp(p).test(path));
44
+ }
45
+ //# sourceMappingURL=glob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.js","sourceRoot":"","sources":["../../src/action/glob.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,KAAK;SACT,KAAK,CAAC,OAAO,CAAC;SACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;SACzC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACjC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAW,CAAC;QAC/B,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBAC3B,CAAC,EAAE,CAAC;gBACJ,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;oBAC3B,CAAC,EAAE,CAAC;oBACJ,MAAM,IAAI,UAAU,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACN,MAAM,IAAI,IAAI,CAAC;gBACjB,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,OAAO,CAAC;YACpB,CAAC;QACH,CAAC;aAAM,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACrB,MAAM,IAAI,MAAM,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IACD,OAAO,IAAI,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC;AACnC,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,QAAkB;IACzD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC1D,CAAC"}