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.
- package/LICENSE +21 -0
- package/README.md +308 -2
- package/dist/action/changed-files.d.ts +14 -0
- package/dist/action/changed-files.js +35 -0
- package/dist/action/changed-files.js.map +1 -0
- package/dist/action/comment.d.ts +28 -0
- package/dist/action/comment.js +70 -0
- package/dist/action/comment.js.map +1 -0
- package/dist/action/github.d.ts +15 -0
- package/dist/action/github.js +62 -0
- package/dist/action/github.js.map +1 -0
- package/dist/action/glob.d.ts +10 -0
- package/dist/action/glob.js +45 -0
- package/dist/action/glob.js.map +1 -0
- package/dist/action/main.d.ts +20 -0
- package/dist/action/main.js +111 -0
- package/dist/action/main.js.map +1 -0
- package/dist/action.d.ts +2 -0
- package/dist/action.js +7 -0
- package/dist/action.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +21 -0
- package/dist/cli.js.map +1 -0
- package/dist/diff/diff.d.ts +11 -0
- package/dist/diff/diff.js +68 -0
- package/dist/diff/diff.js.map +1 -0
- package/dist/diff/summary.d.ts +3 -0
- package/dist/diff/summary.js +40 -0
- package/dist/diff/summary.js.map +1 -0
- package/dist/diff/types.d.ts +59 -0
- package/dist/diff/types.js +2 -0
- package/dist/diff/types.js.map +1 -0
- package/dist/git.d.ts +12 -0
- package/dist/git.js +32 -0
- package/dist/git.js.map +1 -0
- package/dist/html.d.ts +25 -0
- package/dist/html.js +205 -0
- package/dist/html.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/document.d.ts +12 -0
- package/dist/parser/document.js +71 -0
- package/dist/parser/document.js.map +1 -0
- package/dist/parser/index.d.ts +10 -0
- package/dist/parser/index.js +21 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/render/compare.d.ts +19 -0
- package/dist/render/compare.js +61 -0
- package/dist/render/compare.js.map +1 -0
- package/dist/render/figcraft-cli.d.ts +21 -0
- package/dist/render/figcraft-cli.js +63 -0
- package/dist/render/figcraft-cli.js.map +1 -0
- package/dist/render/frames.d.ts +19 -0
- package/dist/render/frames.js +53 -0
- package/dist/render/frames.js.map +1 -0
- package/dist/render/png.d.ts +14 -0
- package/dist/render/png.js +185 -0
- package/dist/render/png.js.map +1 -0
- package/dist/report.d.ts +7 -0
- package/dist/report.js +109 -0
- package/dist/report.js.map +1 -0
- package/dist/run.d.ts +23 -0
- package/dist/run.js +144 -0
- package/dist/run.js.map +1 -0
- package/package.json +51 -4
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { run as runFigdiff } from "../run.js";
|
|
2
|
+
export interface ActionDeps {
|
|
3
|
+
git(args: string[], cwd: string): string;
|
|
4
|
+
readText(path: string): string;
|
|
5
|
+
writeText(path: string, content: string): void;
|
|
6
|
+
mkdirp(path: string): void;
|
|
7
|
+
fetch: typeof fetch;
|
|
8
|
+
run: typeof runFigdiff;
|
|
9
|
+
}
|
|
10
|
+
export interface ActionResult {
|
|
11
|
+
code: number;
|
|
12
|
+
log: string[];
|
|
13
|
+
comment?: "created" | "updated" | "skipped";
|
|
14
|
+
/** Number of design files with real changes. */
|
|
15
|
+
changed: number;
|
|
16
|
+
}
|
|
17
|
+
type Env = Record<string, string | undefined>;
|
|
18
|
+
/** The entry point of the GitHub Action. Everything it needs comes in through `env` and `deps`. */
|
|
19
|
+
export declare function runAction(env: Env, overrides?: Partial<ActionDeps>): Promise<ActionResult>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import { formatText } from "../report.js";
|
|
5
|
+
import { run as runFigdiff } from "../run.js";
|
|
6
|
+
import { filterByPatterns, parseNameStatus } from "./changed-files.js";
|
|
7
|
+
import { buildComment, buildEmptyComment, isSubstantive } from "./comment.js";
|
|
8
|
+
import { parsePatterns } from "./glob.js";
|
|
9
|
+
import { findComment, GitHubError, upsertComment } from "./github.js";
|
|
10
|
+
const defaultDeps = {
|
|
11
|
+
git: (args, cwd) => execFileSync("git", args, { cwd, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }),
|
|
12
|
+
readText: (p) => readFileSync(p, "utf8"),
|
|
13
|
+
writeText: (p, c) => writeFileSync(p, c),
|
|
14
|
+
mkdirp: (p) => void mkdirSync(p, { recursive: true }),
|
|
15
|
+
fetch: (...a) => fetch(...a),
|
|
16
|
+
run: runFigdiff,
|
|
17
|
+
};
|
|
18
|
+
function input(env, name, fallback) {
|
|
19
|
+
const v = env[`INPUT_${name.toUpperCase().replace(/-/g, "_")}`];
|
|
20
|
+
return v !== undefined && v.trim() !== "" ? v.trim() : fallback;
|
|
21
|
+
}
|
|
22
|
+
/** Compare one modified file with its version at the base commit. */
|
|
23
|
+
function compareFile(file, root, base, reportDir, deps) {
|
|
24
|
+
const reportName = `${file.path.replace(/[^A-Za-z0-9._-]+/g, "_")}.html`;
|
|
25
|
+
const result = deps.run([join(root, file.path), "--against", base, "--json", "--html", join(reportDir, reportName)]);
|
|
26
|
+
if (result.code !== 0) {
|
|
27
|
+
const message = result.stderr.replace(/^figdiff:\s*/gm, "").trim().split("\n")[0] ?? "unknown error";
|
|
28
|
+
return { file, error: message };
|
|
29
|
+
}
|
|
30
|
+
for (const w of result.writes) {
|
|
31
|
+
deps.mkdirp(reportDir);
|
|
32
|
+
deps.writeText(w.path, w.content);
|
|
33
|
+
}
|
|
34
|
+
const parsed = JSON.parse(result.stdout);
|
|
35
|
+
return { file, summary: parsed.summary, text: formatText(parsed.changes), reportName };
|
|
36
|
+
}
|
|
37
|
+
/** The entry point of the GitHub Action. Everything it needs comes in through `env` and `deps`. */
|
|
38
|
+
export async function runAction(env, overrides = {}) {
|
|
39
|
+
const deps = { ...defaultDeps, ...overrides };
|
|
40
|
+
const log = [];
|
|
41
|
+
const done = (code, extra = {}) => ({ code, log, changed: 0, ...extra });
|
|
42
|
+
if (!env.GITHUB_EVENT_PATH) {
|
|
43
|
+
log.push("figdiff: GITHUB_EVENT_PATH is not set. Run this action in a GitHub workflow.");
|
|
44
|
+
return done(2);
|
|
45
|
+
}
|
|
46
|
+
const event = JSON.parse(deps.readText(env.GITHUB_EVENT_PATH));
|
|
47
|
+
const pr = event.pull_request;
|
|
48
|
+
if (!pr) {
|
|
49
|
+
log.push("figdiff: this action only works on pull_request events; nothing to do.");
|
|
50
|
+
return done(0, { comment: "skipped" });
|
|
51
|
+
}
|
|
52
|
+
const root = env.GITHUB_WORKSPACE ?? process.cwd();
|
|
53
|
+
const patterns = parsePatterns(input(env, "files", "**/*.figcraft"));
|
|
54
|
+
const reportDir = input(env, "report-dir", "figdiff-reports");
|
|
55
|
+
const wantComment = input(env, "comment", "true") !== "false";
|
|
56
|
+
const maxFiles = Math.max(1, Number.parseInt(input(env, "max-files", "20"), 10) || 20);
|
|
57
|
+
let listed;
|
|
58
|
+
try {
|
|
59
|
+
listed = parseNameStatus(deps.git(["diff", "--name-status", "-z", "--no-renames", `${pr.base.sha}...${pr.head.sha}`], root));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
log.push("figdiff: could not list the changed files. Check out the repository with `fetch-depth: 0` so the base commit is available.");
|
|
63
|
+
return done(2);
|
|
64
|
+
}
|
|
65
|
+
const files = filterByPatterns(listed, patterns);
|
|
66
|
+
log.push(`figdiff: ${files.length} design file(s) changed in this pull request.`);
|
|
67
|
+
const results = [];
|
|
68
|
+
for (const file of files.slice(0, maxFiles)) {
|
|
69
|
+
results.push(file.status === "modified" ? compareFile(file, root, pr.base.sha, reportDir, deps) : { file });
|
|
70
|
+
}
|
|
71
|
+
if (files.length > maxFiles)
|
|
72
|
+
log.push(`figdiff: only the first ${maxFiles} files were compared (max-files).`);
|
|
73
|
+
const changed = results.filter(isSubstantive);
|
|
74
|
+
for (const r of results)
|
|
75
|
+
log.push(`figdiff: ${r.file.path}: ${r.error ?? r.summary?.changes ?? r.file.status}`);
|
|
76
|
+
if (!wantComment)
|
|
77
|
+
return done(0, { comment: "skipped", changed: changed.length });
|
|
78
|
+
const token = env.GITHUB_TOKEN ?? env.INPUT_TOKEN;
|
|
79
|
+
const repo = env.GITHUB_REPOSITORY;
|
|
80
|
+
if (!token || !repo) {
|
|
81
|
+
log.push("figdiff: no token or repository available, so no comment was posted.");
|
|
82
|
+
return done(0, { comment: "skipped", changed: changed.length });
|
|
83
|
+
}
|
|
84
|
+
const api = { fetch: deps.fetch, baseUrl: env.GITHUB_API_URL ?? "https://api.github.com", token };
|
|
85
|
+
try {
|
|
86
|
+
const existing = await findComment(api, repo, pr.number);
|
|
87
|
+
if (changed.length === 0 && existing === undefined) {
|
|
88
|
+
log.push("figdiff: no design changes and no earlier comment; staying quiet.");
|
|
89
|
+
return done(0, { comment: "skipped" });
|
|
90
|
+
}
|
|
91
|
+
const runUrl = env.GITHUB_SERVER_URL && env.GITHUB_RUN_ID ? `${env.GITHUB_SERVER_URL}/${repo}/actions/runs/${env.GITHUB_RUN_ID}` : undefined;
|
|
92
|
+
const body = changed.length === 0
|
|
93
|
+
? buildEmptyComment()
|
|
94
|
+
: buildComment(changed, { runUrl, reportsUploaded: input(env, "upload-reports", "true") !== "false", version: env.FIGDIFF_VERSION });
|
|
95
|
+
const outcome = await upsertComment(api, repo, pr.number, body, existing);
|
|
96
|
+
log.push(`figdiff: comment ${outcome}.`);
|
|
97
|
+
return done(0, { comment: outcome, changed: changed.length });
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (err instanceof GitHubError && err.status === 403) {
|
|
101
|
+
// Pull requests from forks get a read-only token: report instead of failing the build.
|
|
102
|
+
log.push("figdiff: not allowed to comment (403). On pull requests from forks the token is read-only; the changes are in the job log.");
|
|
103
|
+
for (const r of changed)
|
|
104
|
+
log.push(`${basename(r.file.path)}:\n${r.text ?? r.error ?? r.file.status}`);
|
|
105
|
+
return done(0, { comment: "skipped", changed: changed.length });
|
|
106
|
+
}
|
|
107
|
+
log.push(`figdiff: ${err instanceof Error ? err.message : String(err)}`);
|
|
108
|
+
return done(2, { changed: changed.length });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/action/main.ts"],"names":[],"mappings":"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"}
|
package/dist/action.d.ts
ADDED
package/dist/action.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.js","sourceRoot":"","sources":["../src/action.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAC5C,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,GAAG;IAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACjD,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { run } from "./run.js";
|
|
5
|
+
const result = run(process.argv.slice(2));
|
|
6
|
+
let code = result.code;
|
|
7
|
+
let stderr = result.stderr;
|
|
8
|
+
for (const file of result.writes) {
|
|
9
|
+
try {
|
|
10
|
+
mkdirSync(dirname(file.path), { recursive: true });
|
|
11
|
+
writeFileSync(file.path, file.content);
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
stderr = `figdiff: cannot write ${file.path}: ${err instanceof Error ? err.message : String(err)}\n`;
|
|
15
|
+
code = 2;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
process.stdout.write(result.stdout);
|
|
19
|
+
process.stderr.write(stderr);
|
|
20
|
+
process.exitCode = code;
|
|
21
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1C,IAAI,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;AACvB,IAAI,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;AAE3B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,yBAAyB,IAAI,CAAC,IAAI,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;QACrG,IAAI,GAAG,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AAC7B,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Change, DesignNode } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compare two design trees and list what changed.
|
|
4
|
+
*
|
|
5
|
+
* Nodes are matched by id. Fallback matching by name and path is planned
|
|
6
|
+
* for later, once id stability across saves is verified (see docs/format-notes.md).
|
|
7
|
+
*
|
|
8
|
+
* Changes come out in document order of the old tree, followed by added nodes
|
|
9
|
+
* in document order of the new tree.
|
|
10
|
+
*/
|
|
11
|
+
export declare function diffTrees(before: DesignNode, after: DesignNode): Change[];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const SEP = " › ";
|
|
2
|
+
function index(root) {
|
|
3
|
+
const map = new Map();
|
|
4
|
+
const walk = (node, trail, parentId) => {
|
|
5
|
+
map.set(node.id, { node, trail, path: [root.name, ...trail].join(SEP), parentId });
|
|
6
|
+
for (const child of node.children)
|
|
7
|
+
walk(child, [...trail, child.name], node.id);
|
|
8
|
+
};
|
|
9
|
+
walk(root, [], null);
|
|
10
|
+
return map;
|
|
11
|
+
}
|
|
12
|
+
function sameValue(a, b) {
|
|
13
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
14
|
+
}
|
|
15
|
+
function diffProps(a, b) {
|
|
16
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
17
|
+
const changes = [];
|
|
18
|
+
for (const prop of [...keys].sort()) {
|
|
19
|
+
if (!sameValue(a[prop], b[prop]))
|
|
20
|
+
changes.push({ prop, from: a[prop], to: b[prop] });
|
|
21
|
+
}
|
|
22
|
+
return changes;
|
|
23
|
+
}
|
|
24
|
+
/** True when children present in both versions appear in a different order. */
|
|
25
|
+
function childOrderChanged(old, now) {
|
|
26
|
+
const inNow = new Set(now.children.map((c) => c.id));
|
|
27
|
+
const inOld = new Set(old.children.map((c) => c.id));
|
|
28
|
+
const orderBefore = old.children.map((c) => c.id).filter((id) => inNow.has(id));
|
|
29
|
+
const orderAfter = now.children.map((c) => c.id).filter((id) => inOld.has(id));
|
|
30
|
+
return orderBefore.join(",") !== orderAfter.join(",");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Compare two design trees and list what changed.
|
|
34
|
+
*
|
|
35
|
+
* Nodes are matched by id. Fallback matching by name and path is planned
|
|
36
|
+
* for later, once id stability across saves is verified (see docs/format-notes.md).
|
|
37
|
+
*
|
|
38
|
+
* Changes come out in document order of the old tree, followed by added nodes
|
|
39
|
+
* in document order of the new tree.
|
|
40
|
+
*/
|
|
41
|
+
export function diffTrees(before, after) {
|
|
42
|
+
const a = index(before);
|
|
43
|
+
const b = index(after);
|
|
44
|
+
const changes = [];
|
|
45
|
+
for (const [id, old] of a) {
|
|
46
|
+
const now = b.get(id);
|
|
47
|
+
if (!now) {
|
|
48
|
+
changes.push({ kind: "removed", id, path: old.path, trail: old.trail, type: old.node.type });
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const base = { id, path: now.path, trail: now.trail };
|
|
52
|
+
if (old.parentId !== now.parentId) {
|
|
53
|
+
changes.push({ ...base, kind: "moved", fromTrail: old.trail });
|
|
54
|
+
}
|
|
55
|
+
const props = diffProps({ name: old.node.name, type: old.node.type, ...old.node.props }, { name: now.node.name, type: now.node.type, ...now.node.props });
|
|
56
|
+
if (props.length > 0)
|
|
57
|
+
changes.push({ ...base, kind: "modified", props });
|
|
58
|
+
if (childOrderChanged(old.node, now.node))
|
|
59
|
+
changes.push({ ...base, kind: "reordered" });
|
|
60
|
+
}
|
|
61
|
+
for (const [id, now] of b) {
|
|
62
|
+
if (!a.has(id)) {
|
|
63
|
+
changes.push({ kind: "added", id, path: now.path, trail: now.trail, type: now.node.type });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return changes;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/diff/diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Count what changed. A "frame" is a direct child of a page that contains at least one change. */
|
|
2
|
+
export function summarize(changes) {
|
|
3
|
+
const s = {
|
|
4
|
+
changes: 0,
|
|
5
|
+
frames: 0,
|
|
6
|
+
added: 0,
|
|
7
|
+
removed: 0,
|
|
8
|
+
moved: 0,
|
|
9
|
+
reordered: 0,
|
|
10
|
+
modifiedNodes: 0,
|
|
11
|
+
propertyChanges: 0,
|
|
12
|
+
};
|
|
13
|
+
const frames = new Set();
|
|
14
|
+
for (const c of changes) {
|
|
15
|
+
if (c.trail.length >= 2)
|
|
16
|
+
frames.add(`${c.trail[0]}\u0000${c.trail[1]}`);
|
|
17
|
+
switch (c.kind) {
|
|
18
|
+
case "added":
|
|
19
|
+
s.added++;
|
|
20
|
+
break;
|
|
21
|
+
case "removed":
|
|
22
|
+
s.removed++;
|
|
23
|
+
break;
|
|
24
|
+
case "moved":
|
|
25
|
+
s.moved++;
|
|
26
|
+
break;
|
|
27
|
+
case "reordered":
|
|
28
|
+
s.reordered++;
|
|
29
|
+
break;
|
|
30
|
+
case "modified":
|
|
31
|
+
s.modifiedNodes++;
|
|
32
|
+
s.propertyChanges += c.props.length;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
s.frames = frames.size;
|
|
37
|
+
s.changes = s.added + s.removed + s.moved + s.reordered + s.propertyChanges;
|
|
38
|
+
return s;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/diff/summary.ts"],"names":[],"mappings":"AAEA,mGAAmG;AACnG,MAAM,UAAU,SAAS,CAAC,OAAiB;IACzC,MAAM,CAAC,GAAY;QACjB,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,CAAC;QACT,KAAK,EAAE,CAAC;QACR,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,CAAC;QACR,SAAS,EAAE,CAAC;QACZ,aAAa,EAAE,CAAC;QAChB,eAAe,EAAE,CAAC;KACnB,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IAEjC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxE,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;YACf,KAAK,OAAO;gBACV,CAAC,CAAC,KAAK,EAAE,CAAC;gBACV,MAAM;YACR,KAAK,SAAS;gBACZ,CAAC,CAAC,OAAO,EAAE,CAAC;gBACZ,MAAM;YACR,KAAK,OAAO;gBACV,CAAC,CAAC,KAAK,EAAE,CAAC;gBACV,MAAM;YACR,KAAK,WAAW;gBACd,CAAC,CAAC,SAAS,EAAE,CAAC;gBACd,MAAM;YACR,KAAK,UAAU;gBACb,CAAC,CAAC,aAAa,EAAE,CAAC;gBAClB,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;gBACpC,MAAM;QACV,CAAC;IACH,CAAC;IAED,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC;IACvB,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,eAAe,CAAC;IAC5E,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* figdiff's internal, format-independent model of a design.
|
|
3
|
+
* The FigCraft-specific parsing lives in src/parser/.
|
|
4
|
+
*/
|
|
5
|
+
export interface DesignNode {
|
|
6
|
+
/** Stable identifier of the node within the file. */
|
|
7
|
+
id: string;
|
|
8
|
+
/** Node type, for example "frame", "text", "rectangle". */
|
|
9
|
+
type: string;
|
|
10
|
+
/** Layer name shown in the editor. */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Visual and layout properties: fills, strokes, size, position, and so on. */
|
|
13
|
+
props: Record<string, unknown>;
|
|
14
|
+
/** Children in layer order. */
|
|
15
|
+
children: DesignNode[];
|
|
16
|
+
}
|
|
17
|
+
export interface PropChange {
|
|
18
|
+
prop: string;
|
|
19
|
+
from: unknown;
|
|
20
|
+
to: unknown;
|
|
21
|
+
}
|
|
22
|
+
interface ChangeBase {
|
|
23
|
+
/** Id of the node the change is about. */
|
|
24
|
+
id: string;
|
|
25
|
+
/** Names from the page down to the node (the document name is not included). */
|
|
26
|
+
trail: string[];
|
|
27
|
+
/** `trail` joined with " › ", prefixed by the document name. */
|
|
28
|
+
path: string;
|
|
29
|
+
}
|
|
30
|
+
export type Change = (ChangeBase & {
|
|
31
|
+
kind: "added";
|
|
32
|
+
type: string;
|
|
33
|
+
}) | (ChangeBase & {
|
|
34
|
+
kind: "removed";
|
|
35
|
+
type: string;
|
|
36
|
+
}) | (ChangeBase & {
|
|
37
|
+
kind: "moved";
|
|
38
|
+
fromTrail: string[];
|
|
39
|
+
}) | (ChangeBase & {
|
|
40
|
+
kind: "modified";
|
|
41
|
+
props: PropChange[];
|
|
42
|
+
}) | (ChangeBase & {
|
|
43
|
+
kind: "reordered";
|
|
44
|
+
});
|
|
45
|
+
export interface Summary {
|
|
46
|
+
/** Total changes: added + removed + moved + reordered nodes + changed properties. */
|
|
47
|
+
changes: number;
|
|
48
|
+
/** Number of distinct top-level frames that contain at least one change. */
|
|
49
|
+
frames: number;
|
|
50
|
+
added: number;
|
|
51
|
+
removed: number;
|
|
52
|
+
moved: number;
|
|
53
|
+
reordered: number;
|
|
54
|
+
/** Number of nodes with at least one changed property. */
|
|
55
|
+
modifiedNodes: number;
|
|
56
|
+
/** Number of changed properties across all nodes. */
|
|
57
|
+
propertyChanges: number;
|
|
58
|
+
}
|
|
59
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/diff/types.ts"],"names":[],"mappings":""}
|
package/dist/git.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** A copy of an old file version on disk, to be removed with `cleanup()`. */
|
|
2
|
+
export interface GitCopy {
|
|
3
|
+
path: string;
|
|
4
|
+
cleanup(): void;
|
|
5
|
+
}
|
|
6
|
+
/** Runs git and returns what it printed to stdout; must throw on failure. */
|
|
7
|
+
export type GitRun = (args: string[], cwd: string) => Uint8Array;
|
|
8
|
+
/**
|
|
9
|
+
* Save the version of `file` found at git revision `ref` (a branch, tag or commit) to a
|
|
10
|
+
* temporary file, so it can be compared with the working copy.
|
|
11
|
+
*/
|
|
12
|
+
export declare function extractFromGit(ref: string, file: string, git?: GitRun): GitCopy;
|
package/dist/git.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
const defaultGit = (args, cwd) => execFileSync("git", args, { cwd, maxBuffer: 512 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
|
|
6
|
+
/**
|
|
7
|
+
* Save the version of `file` found at git revision `ref` (a branch, tag or commit) to a
|
|
8
|
+
* temporary file, so it can be compared with the working copy.
|
|
9
|
+
*/
|
|
10
|
+
export function extractFromGit(ref, file, git = defaultGit) {
|
|
11
|
+
if (ref.startsWith("-"))
|
|
12
|
+
throw new Error(`Invalid git revision "${ref}"`);
|
|
13
|
+
const absolute = resolve(file);
|
|
14
|
+
const name = basename(absolute);
|
|
15
|
+
let content;
|
|
16
|
+
try {
|
|
17
|
+
// "<ref>:./<name>" is resolved relative to the directory git runs in.
|
|
18
|
+
content = git(["show", `${ref}:./${name}`], dirname(absolute));
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
const e = err;
|
|
22
|
+
if (e.code === "ENOENT")
|
|
23
|
+
throw new Error("git was not found. Install git to use --against.");
|
|
24
|
+
const detail = String(e.stderr ?? "").trim().split("\n")[0];
|
|
25
|
+
throw new Error(`Cannot read ${name} at "${ref}"${detail ? `: ${detail}` : ""}`);
|
|
26
|
+
}
|
|
27
|
+
const dir = mkdtempSync(join(tmpdir(), "figdiff-git-"));
|
|
28
|
+
const path = join(dir, name);
|
|
29
|
+
writeFileSync(path, content);
|
|
30
|
+
return { path, cleanup: () => rmSync(dir, { recursive: true, force: true }) };
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=git.js.map
|
package/dist/git.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAW7D,MAAM,UAAU,GAAW,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CACvC,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;AAEtG;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,IAAY,EAAE,MAAc,UAAU;IAChF,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,GAAG,GAAG,CAAC,CAAC;IAC1E,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAEhC,IAAI,OAAmB,CAAC;IACxB,IAAI,CAAC;QACH,sEAAsE;QACtE,OAAO,GAAG,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,IAAI,EAAE,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,GAAG,GAAsD,CAAC;QACjE,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;QAC7F,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5D,MAAM,IAAI,KAAK,CAAC,eAAe,IAAI,QAAQ,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACnF,CAAC;IAED,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;IACxD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC7B,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC7B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAChF,CAAC"}
|
package/dist/html.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Change } from "./diff/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Images for one frame, as `data:image/...` URIs or http(s) URLs.
|
|
4
|
+
* A frame that exists in only one version (added or removed) has only one side.
|
|
5
|
+
*/
|
|
6
|
+
export interface FrameImages {
|
|
7
|
+
before?: string;
|
|
8
|
+
after?: string;
|
|
9
|
+
/** Image highlighting the pixels that differ. */
|
|
10
|
+
diff?: string;
|
|
11
|
+
/** Share of pixels that differ, from 0 to 1. */
|
|
12
|
+
changedRatio?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface HtmlOptions {
|
|
15
|
+
title?: string;
|
|
16
|
+
beforeName?: string;
|
|
17
|
+
afterName?: string;
|
|
18
|
+
/** Optional rendered images per frame; see `frameKey`. */
|
|
19
|
+
images?: Record<string, FrameImages>;
|
|
20
|
+
}
|
|
21
|
+
/** Key used in `HtmlOptions.images` for a top-level frame. */
|
|
22
|
+
export declare const frameKey: (page: string, frame: string) => string;
|
|
23
|
+
export declare function escapeHtml(text: string): string;
|
|
24
|
+
/** A single self-contained HTML page describing the changes. No network access needed to view it. */
|
|
25
|
+
export declare function formatHtml(changes: Change[], options?: HtmlOptions): string;
|