mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
120 lines
3.6 KiB
TypeScript
120 lines
3.6 KiB
TypeScript
import { createHash } from 'node:crypto';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
const YOMITAN_SYNC_SCRIPT_PATHS = [
|
|
path.join('js', 'app', 'popup.js'),
|
|
path.join('js', 'display', 'popup-main.js'),
|
|
path.join('js', 'display', 'display.js'),
|
|
path.join('js', 'display', 'display-audio.js'),
|
|
];
|
|
|
|
function readManifestVersion(manifestPath: string): string | null {
|
|
try {
|
|
const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) as { version?: unknown };
|
|
return typeof parsed.version === 'string' ? parsed.version : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function hashDirectoryContents(dirPath: string): string | null {
|
|
try {
|
|
if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
|
|
return null;
|
|
}
|
|
|
|
const hash = createHash('sha256');
|
|
const queue = [''];
|
|
while (queue.length > 0) {
|
|
const relativeDir = queue.shift()!;
|
|
const absoluteDir = path.join(dirPath, relativeDir);
|
|
const entries = fs.readdirSync(absoluteDir, { withFileTypes: true });
|
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
|
|
for (const entry of entries) {
|
|
const relativePath = path.join(relativeDir, entry.name);
|
|
const normalizedRelativePath = relativePath.split(path.sep).join('/');
|
|
hash.update(normalizedRelativePath);
|
|
if (entry.isDirectory()) {
|
|
queue.push(relativePath);
|
|
continue;
|
|
}
|
|
if (!entry.isFile()) {
|
|
continue;
|
|
}
|
|
hash.update(fs.readFileSync(path.join(dirPath, relativePath)));
|
|
}
|
|
}
|
|
|
|
return hash.digest('hex');
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function areFilesEqual(sourcePath: string, targetPath: string): boolean {
|
|
if (!fs.existsSync(sourcePath) || !fs.existsSync(targetPath)) return false;
|
|
try {
|
|
return fs.readFileSync(sourcePath).equals(fs.readFileSync(targetPath));
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function shouldCopyYomitanExtension(sourceDir: string, targetDir: string): boolean {
|
|
if (!fs.existsSync(targetDir)) {
|
|
return true;
|
|
}
|
|
|
|
const sourceManifest = path.join(sourceDir, 'manifest.json');
|
|
const targetManifest = path.join(targetDir, 'manifest.json');
|
|
if (!fs.existsSync(sourceManifest) || !fs.existsSync(targetManifest)) {
|
|
return true;
|
|
}
|
|
|
|
const sourceVersion = readManifestVersion(sourceManifest);
|
|
const targetVersion = readManifestVersion(targetManifest);
|
|
if (sourceVersion === null || targetVersion === null || sourceVersion !== targetVersion) {
|
|
return true;
|
|
}
|
|
|
|
for (const relativePath of YOMITAN_SYNC_SCRIPT_PATHS) {
|
|
if (!areFilesEqual(path.join(sourceDir, relativePath), path.join(targetDir, relativePath))) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const sourceHash = hashDirectoryContents(sourceDir);
|
|
const targetHash = hashDirectoryContents(targetDir);
|
|
return sourceHash === null || targetHash === null || sourceHash !== targetHash;
|
|
}
|
|
|
|
export function ensureExtensionCopy(
|
|
sourceDir: string,
|
|
userDataPath: string,
|
|
): {
|
|
targetDir: string;
|
|
copied: boolean;
|
|
} {
|
|
if (process.platform === 'win32') {
|
|
return { targetDir: sourceDir, copied: false };
|
|
}
|
|
|
|
const extensionsRoot = path.join(userDataPath, 'extensions');
|
|
const targetDir = path.join(extensionsRoot, 'yomitan');
|
|
|
|
let shouldCopy = !fs.existsSync(targetDir);
|
|
if (!shouldCopy) {
|
|
shouldCopy = hashDirectoryContents(sourceDir) !== hashDirectoryContents(targetDir);
|
|
}
|
|
|
|
if (shouldCopy) {
|
|
fs.mkdirSync(extensionsRoot, { recursive: true });
|
|
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
fs.cpSync(sourceDir, targetDir, { recursive: true });
|
|
}
|
|
|
|
return { targetDir, copied: shouldCopy };
|
|
}
|