Files
Recordly/scripts/native-helper-manifest.mjs
webadderall 9a5ff5c0a2 feat: ui overhaul — Phosphor icons, redesigned launcher, editor layout, and component refresh
- Replace all lucide-react imports with @phosphor-icons/react across every
  component (LaunchWindow, AnnotationSettingsPanel, ExtensionManager,
  ExtensionIcon, VideoPlayback, AnnotationOverlay, CropControl, TutorialHelp,
  PlaybackControls, all shadcn ui/ primitives, etc.)
- ExtensionIcon: new Phosphor-based resolver with Lucide name alias map for
  backwards-compatible extension manifests
- LaunchWindow / SourceSelector: full redesign — new icon rail layout,
  updated backgrounds, improved source selection UX, audio/video controls,
  countdown overlay
- VideoPlayback: overhaul playback controls and preview panel layout
- Timeline items: border-radius 16px→8px, cyan→blue unification, pill height
  85% for breathing room, resize handle vertical centering
- shadcn ui/: refresh accordion, dialog, dropdown, select, slider, switch,
  tabs, toggle, popover, button, card, input, label, sonner, content-clamp,
  audio-level-meter
- App.tsx / index.css / App.css / tailwind.config: global style updates
- types.ts, captionLayout.ts, captionStyle.ts, webcamOverlay.ts: data model
  updates to support new timeline and export features
- package.json: add @phosphor-icons/react, bump dependencies
- scripts/, .eslintrc, biome, tsconfig, vite.config: tooling alignment
- extension-sources: update built-in extension bundles
2026-04-16 18:01:04 +10:00

189 lines
4.5 KiB
JavaScript

import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import path from "node:path";
const MANIFEST_FILE_NAME = "helpers-manifest.json";
function getPlatformArchDir(platform, arch) {
if (platform !== "win32") {
throw new Error(`Unsupported native helper manifest platform: ${platform}`);
}
return arch === "arm64" ? "win32-arm64" : "win32-x64";
}
function hashBuffer(buffer) {
return createHash("sha256").update(buffer).digest("hex");
}
function collectSourceFiles(sourceDir, rootDir = sourceDir) {
const entries = readdirSync(sourceDir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
if (entry.name === "build" || entry.name === "bin") {
continue;
}
const absolutePath = path.join(sourceDir, entry.name);
if (entry.isDirectory()) {
files.push(...collectSourceFiles(absolutePath, rootDir));
continue;
}
if (!entry.isFile()) {
continue;
}
files.push(path.relative(rootDir, absolutePath).replaceAll("\\", "/"));
}
files.sort((left, right) => left.localeCompare(right));
return files;
}
function hashSourceTree(sourceDir) {
const hash = createHash("sha256");
for (const relativePath of collectSourceFiles(sourceDir)) {
const absolutePath = path.join(sourceDir, relativePath);
hash.update(relativePath);
hash.update("\n");
hash.update(readFileSync(absolutePath));
hash.update("\n");
}
return hash.digest("hex");
}
function hashFile(filePath) {
return hashBuffer(readFileSync(filePath));
}
export function getNativeHelperManifestPath({
projectRoot,
platform = process.platform,
arch = process.arch,
}) {
return path.join(
projectRoot,
"electron",
"native",
"bin",
getPlatformArchDir(platform, arch),
MANIFEST_FILE_NAME,
);
}
function readManifest(manifestPath, platform, arch) {
if (!existsSync(manifestPath)) {
return {
version: 1,
platform,
arch,
helpers: {},
};
}
const manifestContent = JSON.parse(readFileSync(manifestPath, "utf8"));
return {
version: 1,
platform,
arch,
helpers: {},
...manifestContent,
};
}
export function updateNativeHelperManifest({
projectRoot,
helperId,
sourceDir,
binaryPath,
binaryName,
platform = process.platform,
arch = process.arch,
}) {
const manifestPath = getNativeHelperManifestPath({ projectRoot, platform, arch });
const manifestDir = path.dirname(manifestPath);
mkdirSync(manifestDir, { recursive: true });
const manifest = readManifest(manifestPath, platform, arch);
manifest.helpers[helperId] = {
binaryName,
binarySha256: hashFile(binaryPath),
sourceDir: path.relative(projectRoot, sourceDir).replaceAll("\\", "/"),
sourceFingerprint: hashSourceTree(sourceDir),
updatedAt: new Date().toISOString(),
};
writeFileSync(`${manifestPath}`, `${JSON.stringify(manifest, null, 2)}\n`);
return manifestPath;
}
export function verifyNativeHelperManifest({
projectRoot,
helperId,
sourceDir,
binaryPath,
binaryName,
platform = process.platform,
arch = process.arch,
}) {
const manifestPath = getNativeHelperManifestPath({ projectRoot, platform, arch });
if (!existsSync(manifestPath)) {
return {
ok: false,
manifestPath,
reasons: ["manifest missing"],
};
}
if (!existsSync(binaryPath)) {
return {
ok: false,
manifestPath,
reasons: ["bundled helper missing"],
};
}
const manifest = readManifest(manifestPath, platform, arch);
const helperManifest = manifest.helpers?.[helperId];
if (!helperManifest) {
return {
ok: false,
manifestPath,
reasons: [`${helperId} entry missing`],
};
}
const reasons = [];
if (helperManifest.binaryName !== binaryName) {
reasons.push(
`expected binary ${binaryName}, found ${helperManifest.binaryName ?? "unknown"}`,
);
}
const expectedBinaryHash = helperManifest.binarySha256;
const actualBinaryHash = hashFile(binaryPath);
if (expectedBinaryHash !== actualBinaryHash) {
reasons.push("binary hash mismatch");
}
const expectedSourceFingerprint = helperManifest.sourceFingerprint;
const actualSourceFingerprint = hashSourceTree(sourceDir);
if (expectedSourceFingerprint !== actualSourceFingerprint) {
reasons.push("source fingerprint mismatch");
}
return {
ok: reasons.length === 0,
manifestPath,
reasons,
};
}
export function formatNativeHelperManifestWarning(helperLabel, verificationResult) {
const reasonText = verificationResult.reasons.join(", ");
return `[${helperLabel}] Bundled helper provenance check failed (${reasonText}). Rebuild the helper to refresh ${path.basename(verificationResult.manifestPath)}.`;
}