refactor: dedupe diff logic, simplify runner, decompose canvas
- extract 4 diff fns into generic diffEntries + diffEntriesCode - drop 120s polling loop; await gzip promise directly - filter-before-map in webhook, inline color cast helpers - decompose drawSections into measure/render helpers - validate colors VM eval result shape - chain git commits via lastCommit promise - drop unused Differs fields, trim types
This commit is contained in:
parent
0f7170e1c7
commit
dc2b7299ff
10 changed files with 390 additions and 399 deletions
|
|
@ -1,8 +1,8 @@
|
|||
import { readdir } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { createCanvas, GlobalFonts } from "@napi-rs/canvas";
|
||||
import { createCanvas, GlobalFonts, type SKRSContext2D } from "@napi-rs/canvas";
|
||||
import { file } from "bun";
|
||||
import type { Row, Section } from "./factory";
|
||||
import type { Item, Row, Section } from "./factory";
|
||||
import { measureText, transformSvg, wrapText } from "./utils";
|
||||
|
||||
async function bulkRegister(family: string, path: string) {
|
||||
|
|
@ -11,6 +11,37 @@ async function bulkRegister(family: string, path: string) {
|
|||
await bulkRegister("GG Sans", join(import.meta.dir, "fonts/ggsans"));
|
||||
await bulkRegister("GG Mono", join(import.meta.dir, "fonts/ggmono"));
|
||||
|
||||
const layout = {
|
||||
background: { padding: 24, gap: 18 },
|
||||
section: { gap: 8 },
|
||||
card: { radius: 24, padding: 12, gapX: 16, gapY: 8 },
|
||||
blob: { radius: 12 },
|
||||
color: { radius: 24 },
|
||||
};
|
||||
|
||||
const textSizes = { header: 24, title: 18, blob: 24 };
|
||||
|
||||
const colors = {
|
||||
background: "#1c1d23",
|
||||
section: "#26272f",
|
||||
blob: "#6c6f7c1f",
|
||||
textNormal: "#c7c8ce",
|
||||
textMuted: "#818491",
|
||||
watermark: "#2f3035",
|
||||
};
|
||||
|
||||
const itemSize = 128;
|
||||
const blobSize = itemSize + layout.card.gapY + textSizes.title;
|
||||
|
||||
const watermark = await transformSvg(await file(join(import.meta.dir, "placeholders/watermark.svg")).text(), {
|
||||
color: colors.watermark,
|
||||
height: layout.background.padding,
|
||||
});
|
||||
|
||||
const mctx = createCanvas(1, 1).getContext("2d");
|
||||
const headerFont = `600 ${textSizes.header}px GG Sans`;
|
||||
const titleFont = `400 ${textSizes.title}px GG Mono`;
|
||||
|
||||
function calcColumnWidth(column: { textWidth: number; itemWidth: number }[]) {
|
||||
return Math.max(0, ...column.map(({ textWidth, itemWidth }) => Math.max(0, textWidth, itemWidth)));
|
||||
}
|
||||
|
|
@ -27,81 +58,37 @@ function calcSectionHeight(columns: Row[][]) {
|
|||
return Math.max(0, ...columns.map((column) => calcColumnHeight(column)));
|
||||
}
|
||||
|
||||
const layout = {
|
||||
background: {
|
||||
padding: 24,
|
||||
gap: 18,
|
||||
},
|
||||
section: {
|
||||
gap: 8,
|
||||
},
|
||||
card: {
|
||||
radius: 24,
|
||||
padding: 12,
|
||||
gapX: 16,
|
||||
gapY: 8,
|
||||
},
|
||||
blob: {
|
||||
radius: 24 - 12,
|
||||
},
|
||||
color: {
|
||||
radius: 24,
|
||||
},
|
||||
};
|
||||
function measureItemWidth(item: Item) {
|
||||
if (item.type === "blob") return blobSize;
|
||||
if (item.type === "image") return (item.image.width / item.image.height) * itemSize;
|
||||
return itemSize;
|
||||
}
|
||||
|
||||
const textSizes = {
|
||||
header: 24,
|
||||
title: 18,
|
||||
blob: 24,
|
||||
};
|
||||
|
||||
const colors = {
|
||||
background: "#1c1d23", // BACKGROUND_BASE_LOWEST
|
||||
section: "#26272f", // CARD_BACKGROUND_DEFAULT
|
||||
blob: "#6c6f7c1f", // BACKGROUND_MOD_MUTED
|
||||
textNormal: "#c7c8ce", // TEXT_NORMAL
|
||||
textMuted: "#818491", // TEXT_MUTED
|
||||
watermark: "#2f3035", // TEXT_OVERLAY_DARK
|
||||
};
|
||||
|
||||
const itemSize = 128;
|
||||
const blobSize = itemSize + layout.card.gapY + textSizes.title;
|
||||
|
||||
const watermark = await transformSvg(await file(join(import.meta.dir, "placeholders/watermark.svg")).text(), {
|
||||
color: colors.watermark,
|
||||
height: layout.background.padding,
|
||||
});
|
||||
|
||||
const mctx = createCanvas(1, 1).getContext("2d");
|
||||
export function drawSections(sections: Section[]) {
|
||||
const headerFont = `600 ${textSizes.header}px GG Sans`;
|
||||
const titleFont = `400 ${textSizes.title}px GG Mono`;
|
||||
function measureRow(row: Row) {
|
||||
return {
|
||||
title: row.title,
|
||||
subtitle: row.subtitle,
|
||||
textWidth: Math.max(
|
||||
0,
|
||||
row.title ? measureText(mctx, titleFont, row.title).width : 0,
|
||||
row.subtitle ? measureText(mctx, titleFont, row.subtitle).width : 0,
|
||||
),
|
||||
item: row.item,
|
||||
itemWidth: measureItemWidth(row.item),
|
||||
};
|
||||
}
|
||||
|
||||
function measureSections(sections: Section[]) {
|
||||
const sects = sections.map(({ header, columns }) => ({
|
||||
headerWidth: measureText(mctx, headerFont, header).width,
|
||||
header,
|
||||
columns: columns.map((column) =>
|
||||
column.map(({ title, subtitle, item }) => ({
|
||||
title,
|
||||
subtitle,
|
||||
textWidth: Math.max(
|
||||
0,
|
||||
title ? measureText(mctx, titleFont, title).width : 0,
|
||||
subtitle ? measureText(mctx, titleFont, subtitle).width : 0,
|
||||
),
|
||||
item,
|
||||
itemWidth:
|
||||
item.type === "blob"
|
||||
? blobSize
|
||||
: item.type === "image"
|
||||
? (item.image.width / item.image.height) * itemSize
|
||||
: itemSize,
|
||||
})),
|
||||
),
|
||||
columns: columns.map((column) => column.map(measureRow)),
|
||||
}));
|
||||
|
||||
const columnWidths = Array.from({ length: Math.max(0, ...sects.map(({ columns }) => columns.length)) }, (_, i) =>
|
||||
Math.max(0, ...sects.map(({ columns }) => (columns[i] ? calcColumnWidth(columns[i]) : 0))),
|
||||
);
|
||||
|
||||
const sectionWidth =
|
||||
columnWidths.reduce((a, b) => a + b, 0) + layout.card.gapX * (columnWidths.length - 1) + layout.card.padding * 2;
|
||||
|
||||
|
|
@ -118,6 +105,56 @@ export function drawSections(sections: Section[]) {
|
|||
)
|
||||
.reduce((a, b) => a + b, 0) - layout.background.gap;
|
||||
|
||||
return { sects, columnWidths, sectionWidth, width, height };
|
||||
}
|
||||
|
||||
function renderBlob(ctx: SKRSContext2D, blob: string, x: number, y: number, width: number, height: number) {
|
||||
ctx.fillStyle = colors.blob;
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x, y, width, height, layout.blob.radius);
|
||||
ctx.fill();
|
||||
|
||||
ctx.font = `600 ${textSizes.blob}px GG Sans`;
|
||||
ctx.fillStyle = colors.textNormal;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
|
||||
const wrapped = wrapText(ctx, blob, width);
|
||||
const lineHeight = textSizes.blob / 2;
|
||||
for (let i = 0; i < wrapped.length; i++) {
|
||||
ctx.fillText(wrapped[i], x + width / 2, y + height / 2 - lineHeight * (wrapped.length - 1) + textSizes.blob * i);
|
||||
}
|
||||
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "top";
|
||||
}
|
||||
|
||||
function renderColor(ctx: SKRSContext2D, colorGrid: string[][], x: number, y: number) {
|
||||
for (let i = 0; i < colorGrid.length; i++) {
|
||||
const row = colorGrid[i];
|
||||
for (let j = 0; j < row.length; j++) {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(
|
||||
Math.floor(x + (itemSize / row.length) * j),
|
||||
Math.floor(y + (itemSize / colorGrid.length) * i),
|
||||
Math.ceil(itemSize / row.length),
|
||||
Math.ceil(itemSize / colorGrid.length),
|
||||
);
|
||||
ctx.clip();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = row[j];
|
||||
ctx.roundRect(x, y, itemSize, itemSize, layout.color.radius);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function drawSections(sections: Section[]) {
|
||||
const { sects, columnWidths, sectionWidth, width, height } = measureSections(sections);
|
||||
|
||||
const canvas = createCanvas(layout.background.padding * 2 + width, layout.background.padding * 2 + height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.textBaseline = "top";
|
||||
|
|
@ -125,8 +162,8 @@ export function drawSections(sections: Section[]) {
|
|||
ctx.fillStyle = colors.background;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
let x = layout.background.padding,
|
||||
y = layout.background.padding;
|
||||
let x = layout.background.padding;
|
||||
let y = layout.background.padding;
|
||||
for (const sect of sects) {
|
||||
ctx.fillStyle = colors.textNormal;
|
||||
ctx.font = headerFont;
|
||||
|
|
@ -165,54 +202,12 @@ export function drawSections(sections: Section[]) {
|
|||
|
||||
const midX = centerX - row.itemWidth / 2;
|
||||
if (row.item.type === "blob") {
|
||||
const blobHeight = sectionHeight - wy + y;
|
||||
|
||||
ctx.fillStyle = colors.blob;
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(wx, wy, columnWidth, blobHeight, layout.blob.radius);
|
||||
ctx.fill();
|
||||
|
||||
ctx.font = `600 ${textSizes.blob}px GG Sans`;
|
||||
ctx.fillStyle = colors.textNormal;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
|
||||
const wrapped = wrapText(ctx, row.item.blob, columnWidth);
|
||||
const height = textSizes.blob / 2;
|
||||
for (let i = 0; i < wrapped.length; i++)
|
||||
ctx.fillText(
|
||||
wrapped[i],
|
||||
wx + columnWidth / 2,
|
||||
wy + blobHeight / 2 - height * (wrapped.length - 1) + textSizes.blob * i,
|
||||
);
|
||||
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "top";
|
||||
renderBlob(ctx, row.item.blob, wx, wy, columnWidth, sectionHeight - wy + y);
|
||||
} else if (row.item.type === "image") {
|
||||
ctx.drawImage(row.item.image, midX, wy, row.itemWidth, itemSize);
|
||||
wy += itemSize + layout.card.gapY;
|
||||
} else if (row.item.type === "color") {
|
||||
for (let i = 0; i < row.item.colors.length; i++) {
|
||||
const colors = row.item.colors[i];
|
||||
for (let j = 0; j < colors.length; j++) {
|
||||
const color = colors[j];
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(
|
||||
Math.floor(midX + (itemSize / colors.length) * j),
|
||||
Math.floor(wy + (itemSize / row.item.colors.length) * i),
|
||||
Math.ceil(itemSize / colors.length),
|
||||
Math.ceil(itemSize / row.item.colors.length),
|
||||
);
|
||||
ctx.clip();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = color;
|
||||
ctx.roundRect(midX, wy, itemSize, itemSize, layout.color.radius);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
renderColor(ctx, row.item.colors, midX, wy);
|
||||
wy += itemSize + layout.card.gapY;
|
||||
}
|
||||
}
|
||||
|
|
@ -225,7 +220,6 @@ export function drawSections(sections: Section[]) {
|
|||
y += layout.background.gap;
|
||||
}
|
||||
|
||||
// watermark
|
||||
ctx.drawImage(watermark, canvas.width - watermark.width - 1, 1);
|
||||
|
||||
return canvas;
|
||||
|
|
|
|||
11
src/mock.ts
11
src/mock.ts
|
|
@ -1,13 +1,16 @@
|
|||
import { type CodeDiff, DiffType } from "./types";
|
||||
|
||||
export default {
|
||||
code: new Map([
|
||||
raw: new Map(),
|
||||
semantic: new Map(),
|
||||
icons: new Map(),
|
||||
code: new Map<string, CodeDiff>([
|
||||
[
|
||||
"app/foomodule.tsx",
|
||||
{
|
||||
type: DiffType.Added,
|
||||
size: 24782,
|
||||
} as CodeDiff,
|
||||
},
|
||||
],
|
||||
[
|
||||
"app/ui/barmodule.ts",
|
||||
|
|
@ -15,14 +18,14 @@ export default {
|
|||
type: DiffType.Renamed,
|
||||
oldName: "app/legacy/ui/bar_module.ts",
|
||||
size: 5212,
|
||||
} as CodeDiff,
|
||||
},
|
||||
],
|
||||
[
|
||||
"app/intl/ReallyBigCoolComponent.tsx",
|
||||
{
|
||||
type: DiffType.Removed,
|
||||
size: 46532,
|
||||
} as CodeDiff,
|
||||
},
|
||||
],
|
||||
]),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,8 +35,11 @@ export type CodeDiff =
|
|||
size: number;
|
||||
};
|
||||
|
||||
export type Differs = Record<"semantic" | "raw" | "icons", Map<string, Diff> | undefined> & {
|
||||
code: Map<string, CodeDiff> | undefined;
|
||||
export type Differs = {
|
||||
semantic: Map<string, Diff>;
|
||||
raw: Map<string, Diff>;
|
||||
icons: Map<string, Diff>;
|
||||
code: Map<string, CodeDiff>;
|
||||
};
|
||||
|
||||
export type RawColors = Record<string, string>;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,11 @@ export const gitChanged = new Set<string>();
|
|||
export async function fetchGitChanged() {
|
||||
for (const cwd of ["../data", "../canvas"] as const) {
|
||||
try {
|
||||
const out = await Bun.$`git status -z`.cwd(cwd).quiet().nothrow().then((r) => r.text());
|
||||
const out = await Bun.$`git status -z`
|
||||
.cwd(cwd)
|
||||
.quiet()
|
||||
.nothrow()
|
||||
.then((r) => r.text());
|
||||
for (const changed of out
|
||||
.split("\x00")
|
||||
.filter((x) => x !== "")
|
||||
|
|
@ -16,35 +20,26 @@ export async function fetchGitChanged() {
|
|||
}
|
||||
}
|
||||
|
||||
const gitQueue: Promise<void>[] = [];
|
||||
let lastCommit: Promise<void> = Promise.resolve();
|
||||
|
||||
export async function commit(files: string[], message: string, cwd = "../data") {
|
||||
while (gitQueue[0]) {
|
||||
await gitQueue[0];
|
||||
}
|
||||
|
||||
const { promise, resolve } = Promise.withResolvers<void>();
|
||||
gitQueue.push(promise);
|
||||
|
||||
if (process.env.NODE_ENV === "test" && !commitAnyway) {
|
||||
await fetchGitChanged();
|
||||
} else {
|
||||
await Bun.$`git restore --staged .`.cwd(cwd).nothrow().quiet().then(handleShellErr);
|
||||
if (cwd === "../data") await fetchGitChanged();
|
||||
await Bun.$`git add ${{ raw: files.map((x) => Bun.$.escape(x)).join(" ") }}`
|
||||
.cwd(cwd)
|
||||
.nothrow()
|
||||
.quiet()
|
||||
.then(handleShellErr);
|
||||
await Bun.$`git commit -m ${message}`
|
||||
.cwd(cwd)
|
||||
.nothrow()
|
||||
.quiet()
|
||||
.then((e) => void e);
|
||||
}
|
||||
|
||||
resolve();
|
||||
|
||||
const promInd = gitQueue.indexOf(promise);
|
||||
if (promInd !== -1) gitQueue.splice(promInd, 1);
|
||||
lastCommit = lastCommit.then(async () => {
|
||||
if (process.env.NODE_ENV === "test" && !commitAnyway) {
|
||||
await fetchGitChanged();
|
||||
} else {
|
||||
await Bun.$`git restore --staged .`.cwd(cwd).nothrow().quiet().then(handleShellErr);
|
||||
if (cwd === "../data") await fetchGitChanged();
|
||||
await Bun.$`git add ${{ raw: files.map((x) => Bun.$.escape(x)).join(" ") }}`
|
||||
.cwd(cwd)
|
||||
.nothrow()
|
||||
.quiet()
|
||||
.then(handleShellErr);
|
||||
await Bun.$`git commit -m ${message}`
|
||||
.cwd(cwd)
|
||||
.nothrow()
|
||||
.quiet()
|
||||
.then((e) => void e);
|
||||
}
|
||||
});
|
||||
return lastCommit;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { exists, rm } from "node:fs/promises";
|
||||
import mock from "../mock";
|
||||
import type { Differs } from "../types";
|
||||
import { commit } from "./git";
|
||||
|
|
@ -8,7 +7,6 @@ import codeTask from "./tasks/code";
|
|||
import colorsTask from "./tasks/colors";
|
||||
import decompile from "./tasks/decompile";
|
||||
import diffs from "./tasks/diffs";
|
||||
import iconsTask from "./tasks/icons";
|
||||
import { formatError, handleShellErr, join } from "./utils";
|
||||
|
||||
export async function runTasks() {
|
||||
|
|
@ -40,7 +38,9 @@ export async function runTasks() {
|
|||
},
|
||||
true,
|
||||
);
|
||||
let differs: Differs | undefined;
|
||||
let differs: Differs = { raw: new Map(), semantic: new Map(), icons: new Map(), code: new Map() };
|
||||
|
||||
let gzipDone: Promise<void> | undefined;
|
||||
|
||||
if (!isMock) {
|
||||
try {
|
||||
|
|
@ -50,11 +50,19 @@ export async function runTasks() {
|
|||
await Bun.$`git reset --hard`.cwd("../data").nothrow().quiet().then(handleShellErr);
|
||||
await Bun.write("../data/version.txt", version);
|
||||
// canvas branch may not exist in test
|
||||
await Bun.$`git reset --hard`.cwd("../canvas").nothrow().quiet().then(() => {});
|
||||
await Bun.$`git reset --hard`
|
||||
.cwd("../canvas")
|
||||
.nothrow()
|
||||
.quiet()
|
||||
.then(() => {});
|
||||
await Bun.write("../canvas/version.txt", version).catch(() => {});
|
||||
|
||||
await Bun.$`git restore --staged .`.cwd("../data").nothrow().quiet().then(handleShellErr);
|
||||
await Bun.$`git restore --staged .`.cwd("../canvas").nothrow().quiet().then(() => {});
|
||||
await Bun.$`git restore --staged .`
|
||||
.cwd("../canvas")
|
||||
.nothrow()
|
||||
.quiet()
|
||||
.then(() => {});
|
||||
|
||||
progress.update("preinit_discard", true);
|
||||
|
||||
|
|
@ -76,11 +84,11 @@ export async function runTasks() {
|
|||
}
|
||||
|
||||
try {
|
||||
await wrapPromise(
|
||||
gzipDone = (await wrapPromise(
|
||||
decompile(progress, join(apksFolder, "base", "assets", "index.android.bundle")),
|
||||
progress,
|
||||
"decompile",
|
||||
);
|
||||
)) as Promise<void> | undefined;
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to decompile!\n${e}`);
|
||||
}
|
||||
|
|
@ -88,8 +96,7 @@ export async function runTasks() {
|
|||
const code = (await Bun.file(codePath).text()).replace(/\r/g, "").split("\n");
|
||||
|
||||
await wrapPromise(codeTask(progress, code), progress, "code");
|
||||
if (progress.someFailed("code"))
|
||||
throw new Error(`Failed at parser tasks!\n${progress.prettyErrors("code")}`);
|
||||
if (progress.someFailed("code")) throw new Error(`Failed at parser tasks!\n${progress.prettyErrors("code")}`);
|
||||
// colors is non-critical, run in background
|
||||
wrapPromise(colorsTask(code), progress, "colors").catch((e) => {
|
||||
progress.update("colors", false, String(e));
|
||||
|
|
@ -103,22 +110,25 @@ export async function runTasks() {
|
|||
progress.update("icons", null);
|
||||
progress.update("diff_icons", null);
|
||||
|
||||
let waits = 0;
|
||||
while (!progress.isFinished("decompile_gzip") && waits < 120) {
|
||||
await Bun.sleep(1000);
|
||||
waits++;
|
||||
}
|
||||
if (!progress.isFinished("decompile_gzip")) {
|
||||
console.warn("decompile_gzip still not finished after 120s, continuing anyway");
|
||||
progress.update("decompile_gzip", true);
|
||||
if (gzipDone) {
|
||||
try {
|
||||
await gzipDone;
|
||||
} catch (e) {
|
||||
if (progress.someFailed("decompile_gzip"))
|
||||
throw new Error(`Failed at the decompile gzip task!\n${progress.prettyErrors("decompile_gzip")}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (progress.someFailed("decompile_gzip"))
|
||||
throw new Error(`Failed at the decompile gzip task!\n${progress.prettyErrors("decompile_gzip")}`);
|
||||
|
||||
try {
|
||||
differs = await diffs(progress);
|
||||
progress.update("diff", true);
|
||||
const result = await diffs(progress);
|
||||
if (result) {
|
||||
differs = result;
|
||||
progress.update("diff", true);
|
||||
}
|
||||
} catch (e: any) {
|
||||
progress.update("diff", false, formatError(e));
|
||||
throw new Error(`Failed to generate diffs!\n${formatError(e)}`);
|
||||
|
|
@ -127,7 +137,7 @@ export async function runTasks() {
|
|||
differs = mock;
|
||||
}
|
||||
|
||||
if (differs && !isQuiet) {
|
||||
if (!isQuiet) {
|
||||
try {
|
||||
const { webhook } = await import("./tasks/webhook");
|
||||
await wrapPromise(webhook(differs), progress, "webhook");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { mkdir, rm, readdir, stat } from "node:fs/promises";
|
||||
import { mkdir, readdir, rm, stat } from "node:fs/promises";
|
||||
import { join as pathJoin } from "node:path";
|
||||
import { commit } from "../git";
|
||||
import type { Progress } from "../progress";
|
||||
|
|
@ -13,7 +13,10 @@ function extractModuleId(filename: string): number | undefined {
|
|||
}
|
||||
|
||||
function sanitizePath(p: string): string {
|
||||
return p.replace(/\\/g, "/").replace(/^\.\.\//, "").replace(/[<>:"|?*]/g, "_");
|
||||
return p
|
||||
.replace(/\\/g, "/")
|
||||
.replace(/^\.\.\//, "")
|
||||
.replace(/[<>:"|?*]/g, "_");
|
||||
}
|
||||
|
||||
export default async function code(progress: Progress, _code: string[]) {
|
||||
|
|
|
|||
|
|
@ -28,19 +28,34 @@ function evalRawModule(snippet: string): any {
|
|||
code = code.replace(/set\.fileFinishedImporting/g, "__importSet.fileFinishedImporting");
|
||||
code = code.replace(/export\s+const\s+_private/, "const _private");
|
||||
code = code + "\n; _private;";
|
||||
return runInNewContext(code, {});
|
||||
const result = runInNewContext(code, {});
|
||||
if (!result || typeof result !== "object" || !result.RawColors || typeof result.RawColors !== "object") {
|
||||
throw new Error(
|
||||
"VM evaluation failed: RawColors not found or invalid shape. The regex transforms may not match the current decompiler output.",
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function evalSemanticModule(snippet: string): any {
|
||||
let code = snippet;
|
||||
code = code.replace(/^import set from "set".*$/m, "const __importSet = { fileFinishedImporting: () => {} };");
|
||||
code = code.replace(/^import items from "items".*$/m, "const items = { _private: { Themes: new Proxy({}, { get(_, k){ return k.toString().toLowerCase(); } }) } };");
|
||||
code = code.replace(
|
||||
/^import items from "items".*$/m,
|
||||
"const items = { _private: { Themes: new Proxy({}, { get(_, k){ return k.toString().toLowerCase(); } }) } };",
|
||||
);
|
||||
code = code.replace(/^import.*$/gm, "");
|
||||
code = code.replace(/set\.fileFinishedImporting/g, "__importSet.fileFinishedImporting");
|
||||
code = code.replace(/export\s+const\s+_private/, "const _private");
|
||||
code = code + "\n; _private;";
|
||||
code = fixHermesDecIndex(code);
|
||||
return runInNewContext(code, {});
|
||||
const result = runInNewContext(code, {});
|
||||
if (!result || typeof result !== "object" || !result.SemanticColors || typeof result.SemanticColors !== "object") {
|
||||
throw new Error(
|
||||
"VM evaluation failed: SemanticColors not found or invalid shape. The regex transforms may not match the current decompiler output.",
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getInternalRawColors(code: string[]) {
|
||||
|
|
@ -59,7 +74,9 @@ export function getInternalSemanticColors(code: string[], raw: Record<string, st
|
|||
if (!snippet) throw new Error("Cannot find semantic definitions module");
|
||||
const mod = evalSemanticModule(snippet);
|
||||
const semantic: SemanticColors = {};
|
||||
for (const [key, colors] of Object.entries(mod.SemanticColors as Record<string, Record<string, { raw: string; opacity: number }>>)) {
|
||||
for (const [key, colors] of Object.entries(
|
||||
mod.SemanticColors as Record<string, Record<string, { raw: string; opacity: number }>>,
|
||||
)) {
|
||||
const color: SemanticColors[string] = {};
|
||||
for (const [theme, val] of Object.entries(colors)) {
|
||||
if (typeof val !== "object" || !("raw" in val)) continue;
|
||||
|
|
@ -88,5 +105,9 @@ export default async function colors(code: string[]) {
|
|||
await Bun.write("../canvas/semantic.json", JSON.stringify(sortObj(semantic), null, 4));
|
||||
await Bun.write("../canvas/semantic_simple.json", JSON.stringify(sortObj(convertSimpleSemantic(semantic)), null, 4));
|
||||
|
||||
await commit(["raw.json", "semantic.json", "semantic_simple.json"], `chore: update colors for ${cuteVersion}`, "../canvas");
|
||||
await commit(
|
||||
["raw.json", "semantic.json", "semantic_simple.json"],
|
||||
`chore: update colors for ${cuteVersion}`,
|
||||
"../canvas",
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,18 +26,11 @@ export default async function decompile(progress: Progress, pathToBundle: string
|
|||
progress.start("decompile_decompiling");
|
||||
const decompilerBin = join(pathToDecompiler, "target/release/hermes-decomp");
|
||||
if (!(await Bun.file(decompilerBin).exists())) {
|
||||
await Bun.$`cargo build --release -p hbc-decomp-cli`
|
||||
.cwd(pathToDecompiler)
|
||||
.quiet()
|
||||
.nothrow()
|
||||
.then(handleShellErr);
|
||||
await Bun.$`cargo build --release -p hbc-decomp-cli`.cwd(pathToDecompiler).quiet().nothrow().then(handleShellErr);
|
||||
}
|
||||
|
||||
if (!(await Bun.file(codePath).exists())) {
|
||||
await Bun.$`${decompilerBin} decompile ${pathToBundle} --output ${codePath}`
|
||||
.quiet()
|
||||
.nothrow()
|
||||
.then(handleShellErr);
|
||||
await Bun.$`${decompilerBin} decompile ${pathToBundle} --output ${codePath}`.quiet().nothrow().then(handleShellErr);
|
||||
}
|
||||
|
||||
if (!(await exists(modulesPath))) {
|
||||
|
|
@ -70,15 +63,20 @@ export default async function decompile(progress: Progress, pathToBundle: string
|
|||
|
||||
const gzipper = new Worker(gzipWorkerURL);
|
||||
progress.start("decompile_gzip");
|
||||
const { promise: gzipDone, resolve: resolveGzip } = Promise.withResolvers<void>();
|
||||
gzipper.addEventListener("message", async ({ data }) => {
|
||||
if (data === true) {
|
||||
await commit([gzFile, "module-paths.json", "index.android.bundle"], `chore: update decompiled code for ${cuteVersion}`);
|
||||
await commit(
|
||||
[gzFile, "module-paths.json", "index.android.bundle"],
|
||||
`chore: update decompiled code for ${cuteVersion}`,
|
||||
);
|
||||
progress.update("decompile_gzip", true);
|
||||
}
|
||||
gzipper.terminate();
|
||||
resolveGzip();
|
||||
});
|
||||
gzipper.postMessage({ path: codePath, target: join("../data", gzFile) });
|
||||
} else {
|
||||
progress.update("decompile_gzip", null);
|
||||
return gzipDone;
|
||||
}
|
||||
progress.update("decompile_gzip", null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,185 +12,183 @@ import { type Progress, wrapPromise } from "../progress";
|
|||
import { diffAnyway, prevFiles } from "../shared";
|
||||
import { join, parseJsonl } from "../utils";
|
||||
|
||||
async function diffRaw() {
|
||||
if (!prevFiles.has("raw.json")) {
|
||||
throw new Error("Missing prevFile: raw.json");
|
||||
}
|
||||
interface DiffOpts<TOld, TNew> {
|
||||
getSource: (key: string, val: TNew) => string;
|
||||
getLabel?: (key: string, val: TNew) => string;
|
||||
getOldSource?: (key: string, val: TOld) => string;
|
||||
getOldLabel?: (key: string, val: TOld) => string;
|
||||
isRename: (newKey: string, oldVal: TOld, newVal: TNew) => boolean;
|
||||
isChanged?: (oldVal: TOld, newVal: TNew) => boolean;
|
||||
}
|
||||
|
||||
const oldRaw = JSON.parse(new TextDecoder().decode(prevFiles.get("raw.json"))) as RawColors;
|
||||
const newRaw: RawColors = await Bun.file(join("../canvas", "raw.json")).json();
|
||||
|
||||
const renamed = new Set<string>();
|
||||
function diffEntries<TOld, TNew>(
|
||||
oldEntries: Record<string, TOld>,
|
||||
newEntries: Record<string, TNew>,
|
||||
opts: DiffOpts<TOld, TNew>,
|
||||
): Map<string, Diff> {
|
||||
const changes = new Map<string, Diff>();
|
||||
for (const [name, source] of Object.entries(newRaw))
|
||||
if (!oldRaw[name]) {
|
||||
const oldName = Object.entries(oldRaw).find(([key, val]) => !newRaw[key] && val === source)?.[0];
|
||||
const renamed = new Set<string>();
|
||||
|
||||
if (!oldName)
|
||||
for (const [name, newVal] of Object.entries(newEntries)) {
|
||||
const oldVal = oldEntries[name];
|
||||
if (oldVal === undefined) {
|
||||
const oldName = Object.entries(oldEntries).find(
|
||||
([key, val]) => !(key in newEntries) && opts.isRename(name, val, newVal),
|
||||
)?.[0];
|
||||
|
||||
if (!oldName) {
|
||||
changes.set(name, {
|
||||
type: DiffType.Added,
|
||||
source,
|
||||
source: opts.getSource(name, newVal),
|
||||
label: opts.getLabel?.(name, newVal),
|
||||
});
|
||||
else {
|
||||
} else {
|
||||
renamed.add(oldName);
|
||||
changes.set(name, {
|
||||
type: DiffType.Renamed,
|
||||
oldName,
|
||||
source,
|
||||
});
|
||||
changes.set(name, { type: DiffType.Renamed, oldName, source: opts.getSource(name, newVal) });
|
||||
}
|
||||
} else if (oldRaw[name].toLowerCase() !== source.toLowerCase())
|
||||
} else if (opts.isChanged?.(oldVal, newVal)) {
|
||||
changes.set(name, {
|
||||
type: DiffType.Changed,
|
||||
source,
|
||||
oldSource: oldRaw[name],
|
||||
});
|
||||
for (const [name, source] of Object.entries(oldRaw))
|
||||
if (!(newRaw[name] || renamed.has(name)))
|
||||
changes.set(name, {
|
||||
type: DiffType.Removed,
|
||||
source,
|
||||
source: opts.getSource(name, newVal),
|
||||
label: opts.getLabel?.(name, newVal),
|
||||
oldSource: opts.getOldSource?.(name, oldVal) ?? "",
|
||||
oldLabel: opts.getOldLabel?.(name, oldVal),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, oldVal] of Object.entries(oldEntries)) {
|
||||
if (name in newEntries || renamed.has(name)) continue;
|
||||
changes.set(name, {
|
||||
type: DiffType.Removed,
|
||||
source: opts.getOldSource?.(name, oldVal) ?? "",
|
||||
label: opts.getOldLabel?.(name, oldVal),
|
||||
});
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
function getSemanticLabel(semantic: any) {
|
||||
const light = semantic.light,
|
||||
dark = semantic.darker ?? semantic.dark;
|
||||
if (dark && light) return `☀️ ${light[0]}, 🌙 ${dark[0]}`;
|
||||
function diffEntriesCode(
|
||||
oldEntries: Record<string, { size: number; compSize: number }>,
|
||||
newEntries: Record<string, { size: number; compSize: number }>,
|
||||
): Map<string, CodeDiff> {
|
||||
const changes = new Map<string, CodeDiff>();
|
||||
const renamed = new Set<string>();
|
||||
|
||||
for (const [name, newVal] of Object.entries(newEntries)) {
|
||||
if (!(name in oldEntries)) {
|
||||
const oldName = Object.entries(oldEntries).find(
|
||||
([key, val]) => !(key in newEntries) && val.compSize === newVal.compSize,
|
||||
)?.[0];
|
||||
|
||||
if (!oldName) {
|
||||
changes.set(name, { type: DiffType.Added, size: newVal.size });
|
||||
} else {
|
||||
renamed.add(oldName);
|
||||
changes.set(name, { type: DiffType.Renamed, oldName, size: newVal.size });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, oldVal] of Object.entries(oldEntries)) {
|
||||
if (!(name in newEntries || renamed.has(name))) {
|
||||
changes.set(name, { type: DiffType.Removed, size: oldVal.size });
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
async function diffRaw() {
|
||||
if (!prevFiles.has("raw.json")) throw new Error("Missing prevFile: raw.json");
|
||||
|
||||
const oldRaw = JSON.parse(new TextDecoder().decode(prevFiles.get("raw.json"))) as RawColors;
|
||||
const newRaw: RawColors = await Bun.file(join("../canvas", "raw.json")).json();
|
||||
|
||||
return diffEntries(oldRaw, newRaw, {
|
||||
getSource: (_, val) => val,
|
||||
isRename: (newKey, oldVal, newVal) => !(newKey in newRaw) && oldVal === newVal,
|
||||
isChanged: (oldVal, newVal) => oldVal.toLowerCase() !== newVal.toLowerCase(),
|
||||
});
|
||||
}
|
||||
|
||||
function getSemanticLabel(semantic: Record<string, [string, unknown]>) {
|
||||
const light = semantic.light;
|
||||
const dark = semantic.darker ?? semantic.dark;
|
||||
if (dark && light) return `${light[0]}, ${dark[0]}`;
|
||||
return Object.entries(semantic)
|
||||
.map(([name, [color]]) => `${name} ${color}`)
|
||||
.join(", ");
|
||||
}
|
||||
|
||||
async function diffSemantic() {
|
||||
if (!prevFiles.has("semantic.json")) {
|
||||
throw new Error("Missing prevFile: semantic.json");
|
||||
}
|
||||
if (!prevFiles.has("semantic.json")) throw new Error("Missing prevFile: semantic.json");
|
||||
|
||||
const oldSemantic = JSON.parse(new TextDecoder().decode(prevFiles.get("semantic.json"))) as SemanticColors;
|
||||
const newSemantic: SemanticColors = await Bun.file(join("../canvas", "semantic.json")).json();
|
||||
|
||||
const renamed = new Set<string>();
|
||||
const changes = new Map<string, Diff>();
|
||||
for (const [name, semantic] of Object.entries(newSemantic)) {
|
||||
const source = Object.values(semantic)
|
||||
.map(([color]) => color)
|
||||
.join(",");
|
||||
if (!oldSemantic[name]) {
|
||||
const themes = Object.keys(semantic).length;
|
||||
const oldName = Object.entries(oldSemantic).find(
|
||||
([key, val]) =>
|
||||
!newSemantic[key] &&
|
||||
Object.keys(val).length === themes &&
|
||||
Object.entries(val).every(([skey, sval]) => sval[0] === semantic[skey][0]),
|
||||
)?.[0];
|
||||
const changes = diffEntries(oldSemantic, newSemantic, {
|
||||
getSource: (_, val) =>
|
||||
Object.values(val)
|
||||
.map(([color]) => color)
|
||||
.join(","),
|
||||
getLabel: (_, val) => getSemanticLabel(val),
|
||||
isRename: (newKey, oldVal, newVal) => {
|
||||
const themes = Object.keys(newVal).length;
|
||||
return (
|
||||
!(newKey in newSemantic) &&
|
||||
Object.keys(oldVal).length === themes &&
|
||||
Object.entries(oldVal).every(([skey, sval]) => sval[0] === newVal[skey]?.[0])
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
if (!oldName)
|
||||
changes.set(name, {
|
||||
type: DiffType.Added,
|
||||
source,
|
||||
label: getSemanticLabel(semantic),
|
||||
});
|
||||
else {
|
||||
renamed.add(oldName);
|
||||
changes.set(name, {
|
||||
type: DiffType.Renamed,
|
||||
oldName,
|
||||
source,
|
||||
});
|
||||
}
|
||||
} else
|
||||
for (const [theme, [color]] of Object.entries(semantic))
|
||||
if (!oldSemantic[name][theme])
|
||||
changes.set(`${name}.${theme}`, {
|
||||
type: DiffType.Added,
|
||||
source: color,
|
||||
});
|
||||
else if (oldSemantic[name][theme][0].toLowerCase() !== color.toLowerCase())
|
||||
for (const [name, semantic] of Object.entries(newSemantic)) {
|
||||
if (oldSemantic[name]) {
|
||||
for (const [theme, [color]] of Object.entries(semantic)) {
|
||||
if (!oldSemantic[name][theme]) {
|
||||
changes.set(`${name}.${theme}`, { type: DiffType.Added, source: color });
|
||||
} else if (oldSemantic[name][theme][0].toLowerCase() !== color.toLowerCase()) {
|
||||
changes.set(`${name}.${theme}`, {
|
||||
type: DiffType.Changed,
|
||||
source: color,
|
||||
oldSource: oldSemantic[name][theme][0],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [name, semantic] of Object.entries(oldSemantic)) {
|
||||
if (renamed.has(name)) continue;
|
||||
|
||||
const source = Object.values(semantic)
|
||||
.map(([color]) => color)
|
||||
.join(",");
|
||||
if (!newSemantic[name])
|
||||
changes.set(name, {
|
||||
type: DiffType.Removed,
|
||||
source,
|
||||
label: getSemanticLabel(semantic),
|
||||
});
|
||||
else
|
||||
for (const [theme, [source]] of Object.entries(semantic))
|
||||
if (!newSemantic[name][theme])
|
||||
changes.set(`${name}.${theme}`, {
|
||||
type: DiffType.Removed,
|
||||
source,
|
||||
});
|
||||
for (const [name, semantic] of Object.entries(oldSemantic)) {
|
||||
if (name in newSemantic) {
|
||||
for (const [theme, [source]] of Object.entries(semantic)) {
|
||||
if (!newSemantic[name][theme]) {
|
||||
changes.set(`${name}.${theme}`, { type: DiffType.Removed, source });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
async function diffIcons() {
|
||||
if (!prevFiles.has("icons.json")) {
|
||||
throw new Error("Missing prevFile: icons.json");
|
||||
}
|
||||
if (!prevFiles.has("icons.json")) throw new Error("Missing prevFile: icons.json");
|
||||
|
||||
const oldIcons = JSON.parse(new TextDecoder().decode(prevFiles.get("icons.json"))) as Icons;
|
||||
const newIcons: Icons = await Bun.file(join("../canvas", "icons.json")).json();
|
||||
|
||||
const iconDir = {
|
||||
old: join("../canvas", "oldicons"),
|
||||
new: join("../canvas", "icons"),
|
||||
};
|
||||
const iconDir = { old: join("../canvas", "oldicons"), new: join("../canvas", "icons") };
|
||||
|
||||
const renamed = new Set<string>();
|
||||
const changes = new Map<string, Diff>();
|
||||
for (const [name, icon] of Object.entries(newIcons)) {
|
||||
const label = icon.hash.slice(0, 8);
|
||||
if (!oldIcons[name]) {
|
||||
const oldName = Object.entries(oldIcons).find(([key, val]) => !newIcons[key] && val.hash === icon.hash)?.[0];
|
||||
|
||||
if (!oldName)
|
||||
changes.set(name, {
|
||||
type: DiffType.Added,
|
||||
source: join(iconDir.new, icon.file),
|
||||
label,
|
||||
});
|
||||
else {
|
||||
renamed.add(oldName);
|
||||
changes.set(name, {
|
||||
type: DiffType.Renamed,
|
||||
oldName,
|
||||
source: join(iconDir.new, icon.file),
|
||||
});
|
||||
}
|
||||
} else if (oldIcons[name].hash !== icon.hash)
|
||||
changes.set(name, {
|
||||
type: DiffType.Changed,
|
||||
source: join(iconDir.new, icon.file),
|
||||
label,
|
||||
oldSource: join(iconDir.old, oldIcons[name].file),
|
||||
oldLabel: oldIcons[name].hash.slice(0, 8),
|
||||
});
|
||||
}
|
||||
for (const [name, icon] of Object.entries(oldIcons))
|
||||
if (!(newIcons[name] || renamed.has(name)))
|
||||
changes.set(name, {
|
||||
type: DiffType.Removed,
|
||||
source: join(iconDir.old, icon.file),
|
||||
label: icon.hash.slice(0, 8),
|
||||
});
|
||||
|
||||
return changes;
|
||||
return diffEntries(oldIcons, newIcons, {
|
||||
getSource: (_, val) => join(iconDir.new, val.file),
|
||||
getLabel: (_, val) => val.hash.slice(0, 8),
|
||||
getOldSource: (_, val) => join(iconDir.old, val.file),
|
||||
getOldLabel: (_, val) => val.hash.slice(0, 8),
|
||||
isRename: (newKey, oldVal, newVal) => !(newKey in newIcons) && oldVal.hash === newVal.hash,
|
||||
isChanged: (oldVal, newVal) => oldVal.hash !== newVal.hash,
|
||||
});
|
||||
}
|
||||
|
||||
function parseSource(text: string) {
|
||||
|
|
@ -201,42 +199,12 @@ function parseSource(text: string) {
|
|||
}
|
||||
|
||||
async function diffCode() {
|
||||
if (!prevFiles.has("source.jsonl")) {
|
||||
throw new Error("Missing prevFile: source.jsonl");
|
||||
}
|
||||
if (!prevFiles.has("source.jsonl")) throw new Error("Missing prevFile: source.jsonl");
|
||||
|
||||
const oldCode = parseSource(new TextDecoder().decode(prevFiles.get("source.jsonl")));
|
||||
const newCode = parseSource(await Bun.file(join("../data", "source.jsonl")).text());
|
||||
|
||||
const renamed = new Set<string>();
|
||||
const changes = new Map<string, CodeDiff>();
|
||||
for (const [name, { size, compSize }] of Object.entries(newCode)) {
|
||||
if (!oldCode[name]) {
|
||||
const oldName = Object.entries(oldCode).find(([key, val]) => !newCode[key] && val.compSize === compSize)?.[0];
|
||||
|
||||
if (!oldName)
|
||||
changes.set(name, {
|
||||
type: DiffType.Added,
|
||||
size,
|
||||
});
|
||||
else {
|
||||
renamed.add(oldName);
|
||||
changes.set(name, {
|
||||
type: DiffType.Renamed,
|
||||
oldName,
|
||||
size,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [name, { size }] of Object.entries(oldCode))
|
||||
if (!(newCode[name] || renamed.has(name)))
|
||||
changes.set(name, {
|
||||
type: DiffType.Removed,
|
||||
size,
|
||||
});
|
||||
|
||||
return changes;
|
||||
return diffEntriesCode(oldCode, newCode);
|
||||
}
|
||||
|
||||
export default async function diffs(progress: Progress) {
|
||||
|
|
@ -251,7 +219,7 @@ export default async function diffs(progress: Progress) {
|
|||
return;
|
||||
}
|
||||
|
||||
const differs = {} as Differs;
|
||||
const differs: Differs = { raw: new Map(), semantic: new Map(), icons: new Map(), code: new Map() };
|
||||
|
||||
await Promise.all([
|
||||
gitChanged.has("raw.json")
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { drawSections } from "../../canvas";
|
|||
import { makeSections } from "../../canvas/factory";
|
||||
import { type CodeDiff, type Diff, type Differs, DiffType } from "../../types";
|
||||
import { cuteVersion, maxDiffChanges, version } from "../shared";
|
||||
import { assert, formatBytes, sortEntries } from "../utils";
|
||||
import { formatBytes, sortEntries } from "../utils";
|
||||
|
||||
function fileBase(path: string, other?: string) {
|
||||
if (!other) return `... ${basename(path)}`;
|
||||
|
|
@ -49,43 +49,39 @@ interface FormattedDiff {
|
|||
}
|
||||
|
||||
function formatDiff(diffs: Map<string, Diff | CodeDiff>): FormattedDiff {
|
||||
const entries = [...diffs.entries()];
|
||||
const entries = sortEntries([...diffs.entries()]);
|
||||
|
||||
const sections = {
|
||||
Added: sortEntries(entries)
|
||||
.map(
|
||||
([name, diff]) =>
|
||||
diff.type === DiffType.Added &&
|
||||
("size" in diff
|
||||
? `+ ${fileBase(name)} (${formatBytes(diff.size)})`
|
||||
: `+ ${name}: ${diff.label || diff.source}`),
|
||||
)
|
||||
.filter((x) => typeof x === "string"),
|
||||
Changed: sortEntries(entries)
|
||||
.map(
|
||||
([name, diff]) =>
|
||||
diff.type === DiffType.Changed &&
|
||||
`- ${name}: ${diff.oldLabel || diff.oldSource}\n+ ${name}: ${diff.label || diff.source}`,
|
||||
)
|
||||
.filter((x) => typeof x === "string"),
|
||||
Renamed: sortEntries(entries)
|
||||
.map(
|
||||
([name, diff]) =>
|
||||
diff.type === DiffType.Renamed &&
|
||||
("size" in diff
|
||||
? `- ${fileBase(diff.oldName, name)}\n+ ${fileBase(name, diff.oldName)}`
|
||||
: `- ${diff.oldName}\n+ ${name}`),
|
||||
)
|
||||
.filter((x) => typeof x === "string"),
|
||||
Removed: sortEntries(entries)
|
||||
.map(
|
||||
([name, diff]) =>
|
||||
diff.type === DiffType.Removed &&
|
||||
("size" in diff
|
||||
? `- ${fileBase(name)} (${formatBytes(diff.size)})`
|
||||
: `- ${name}: ${diff.label || diff.source}`),
|
||||
)
|
||||
.filter((x) => typeof x === "string"),
|
||||
Added: entries
|
||||
.filter(([, diff]) => diff.type === DiffType.Added)
|
||||
.map(([name, diff]) => {
|
||||
if ("size" in diff) return `+ ${fileBase(name)} (${formatBytes(diff.size)})`;
|
||||
const d = diff as Extract<Diff, { type: DiffType.Added | DiffType.Removed }>;
|
||||
return `+ ${name}: ${d.label || d.source}`;
|
||||
}),
|
||||
Changed: entries
|
||||
.filter(([, diff]) => diff.type === DiffType.Changed)
|
||||
.map(([name, diff]) => {
|
||||
const d = diff as Extract<Diff, { type: DiffType.Changed }>;
|
||||
return `- ${name}: ${d.oldLabel || d.oldSource}\n+ ${name}: ${d.label || d.source}`;
|
||||
}),
|
||||
Renamed: entries
|
||||
.filter(([, diff]) => diff.type === DiffType.Renamed)
|
||||
.map(([name, diff]) => {
|
||||
if ("size" in diff) {
|
||||
const cd = diff as Extract<CodeDiff, { type: DiffType.Renamed }>;
|
||||
return `- ${fileBase(cd.oldName, name)}\n+ ${fileBase(name, cd.oldName)}`;
|
||||
}
|
||||
const d = diff as Extract<Diff, { type: DiffType.Renamed }>;
|
||||
return `- ${d.oldName}\n+ ${name}`;
|
||||
}),
|
||||
Removed: entries
|
||||
.filter(([, diff]) => diff.type === DiffType.Removed)
|
||||
.map(([name, diff]) => {
|
||||
if ("size" in diff) return `- ${fileBase(name)} (${formatBytes(diff.size)})`;
|
||||
const d = diff as Extract<Diff, { type: DiffType.Added | DiffType.Removed }>;
|
||||
return `- ${name}: ${d.label || d.source}`;
|
||||
}),
|
||||
};
|
||||
|
||||
const text = Object.entries(sections).filter(([, lines]) => lines.length);
|
||||
|
|
@ -214,7 +210,7 @@ async function sendWebhook(webhook: string, role: string, embeds: WebhookEmbed[]
|
|||
}
|
||||
|
||||
export async function webhook(diffs: Differs) {
|
||||
if (diffs.raw?.size || diffs.semantic?.size) {
|
||||
if (diffs.raw.size || diffs.semantic.size) {
|
||||
const wh = process.env.color_webhook;
|
||||
if (!wh) {
|
||||
console.warn("Missing color_webhook, skipping color webhook");
|
||||
|
|
@ -223,14 +219,14 @@ export async function webhook(diffs: Differs) {
|
|||
wh,
|
||||
"1227327297795657850",
|
||||
[
|
||||
diffs.raw?.size && {
|
||||
diffs.raw.size && {
|
||||
title: "Raw colors",
|
||||
body: formatDiff(diffs.raw),
|
||||
image: drawSections(await makeSections(diffs.raw)),
|
||||
footer: makeFooter(diffs.raw.size, "raw color"),
|
||||
key: "raw-colors",
|
||||
},
|
||||
diffs.semantic?.size && {
|
||||
diffs.semantic.size && {
|
||||
title: "Semantic colors",
|
||||
body: formatDiff(diffs.semantic),
|
||||
image: drawSections(await makeSections(diffs.semantic)),
|
||||
|
|
@ -243,7 +239,7 @@ export async function webhook(diffs: Differs) {
|
|||
}
|
||||
}
|
||||
|
||||
if (diffs.icons?.size) {
|
||||
if (diffs.icons.size) {
|
||||
const wh = process.env.icons_webhook;
|
||||
if (!wh) {
|
||||
console.warn("Missing icons_webhook, skipping");
|
||||
|
|
@ -265,7 +261,7 @@ export async function webhook(diffs: Differs) {
|
|||
}
|
||||
}
|
||||
|
||||
if (diffs.code?.size) {
|
||||
if (diffs.code.size) {
|
||||
const wh = process.env.code_webhook;
|
||||
if (!wh) {
|
||||
console.warn("Missing code_webhook, skipping");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue