release: publish Twitch VOD Manager 1.0.3
Polish navigation, settings, streamer and queue workflows; add safe pause and partial-file lifecycle handling; apply the product identity across Windows surfaces; and replace the public README with a complete English product guide and isolated screenshot.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ResEdit from 'resedit';
|
||||
import { writeFileAtomicSync } from './infra/fs-atomic';
|
||||
|
||||
export interface WindowsDevExecutableOptions {
|
||||
sourcePath: string;
|
||||
destinationPath: string;
|
||||
iconPath: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export async function prepareWindowsDevExecutable(options: WindowsDevExecutableOptions): Promise<string> {
|
||||
const source = path.resolve(options.sourcePath);
|
||||
const destination = path.resolve(options.destinationPath);
|
||||
const icon = path.resolve(options.iconPath);
|
||||
const stampPath = `${destination}.json`;
|
||||
const sourceStats = fs.statSync(source);
|
||||
const iconStats = fs.statSync(icon);
|
||||
const fingerprint = JSON.stringify({
|
||||
sourceSize: sourceStats.size,
|
||||
sourceModified: sourceStats.mtimeMs,
|
||||
iconSize: iconStats.size,
|
||||
iconModified: iconStats.mtimeMs,
|
||||
version: options.version
|
||||
});
|
||||
|
||||
if (fs.existsSync(destination) && fs.existsSync(stampPath) && fs.readFileSync(stampPath, 'utf8') === fingerprint) {
|
||||
return destination;
|
||||
}
|
||||
|
||||
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
||||
const temporaryDestination = `${destination}.tmp`;
|
||||
fs.copyFileSync(source, temporaryDestination);
|
||||
|
||||
try {
|
||||
const executable = ResEdit.NtExecutable.from(fs.readFileSync(temporaryDestination), { ignoreCert: true });
|
||||
const resources = ResEdit.NtExecutableResource.from(executable);
|
||||
const versionEntries = ResEdit.Resource.VersionInfo.fromEntries(resources.entries);
|
||||
const versionInfo = versionEntries[0] || ResEdit.Resource.VersionInfo.createEmpty();
|
||||
const languages = versionInfo.getAllLanguagesForStringValues();
|
||||
const language = languages[0] || { lang: 0x0409, codepage: 1200 };
|
||||
versionInfo.setStringValues(language, {
|
||||
FileDescription: 'Twitch VOD Manager',
|
||||
ProductName: 'Twitch VOD Manager',
|
||||
InternalName: 'Twitch VOD Manager',
|
||||
OriginalFilename: 'Twitch VOD Manager.exe'
|
||||
});
|
||||
versionInfo.setFileVersion(options.version);
|
||||
versionInfo.setProductVersion(options.version);
|
||||
versionInfo.outputToResourceEntries(resources.entries);
|
||||
|
||||
const iconFile = ResEdit.Data.IconFile.from(fs.readFileSync(icon));
|
||||
ResEdit.Resource.IconGroupEntry.replaceIconsForResource(
|
||||
resources.entries,
|
||||
1,
|
||||
language.lang,
|
||||
iconFile.icons.map((entry) => entry.data)
|
||||
);
|
||||
|
||||
resources.outputResource(executable);
|
||||
fs.writeFileSync(temporaryDestination, Buffer.from(executable.generate()));
|
||||
fs.rmSync(destination, { force: true });
|
||||
fs.renameSync(temporaryDestination, destination);
|
||||
writeFileAtomicSync(stampPath, fingerprint);
|
||||
return destination;
|
||||
} catch (error) {
|
||||
fs.rmSync(temporaryDestination, { force: true });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user