Add auto updater
This commit is contained in:
256
tools/generate-release-manifest.js
Normal file
256
tools/generate-release-manifest.js
Normal file
@@ -0,0 +1,256 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const rootDir = path.resolve(__dirname, '..');
|
||||
const defaultManifestPath = path.join(rootDir, 'dist-electron', 'release-manifest.json');
|
||||
const descriptorCandidates = ['latest.yml', 'latest-mac.yml', 'latest-linux.yml'];
|
||||
|
||||
function normalizeVersion(rawValue) {
|
||||
if (typeof rawValue !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const trimmedValue = rawValue.trim();
|
||||
|
||||
if (!trimmedValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return trimmedValue.replace(/^v/i, '').split('+')[0] || null;
|
||||
}
|
||||
|
||||
function parseVersion(rawValue) {
|
||||
const normalized = normalizeVersion(rawValue);
|
||||
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const match = normalized.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?$/);
|
||||
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
major: Number.parseInt(match[1], 10),
|
||||
minor: Number.parseInt(match[2] || '0', 10),
|
||||
patch: Number.parseInt(match[3] || '0', 10),
|
||||
prerelease: match[4] ? match[4].split('.') : []
|
||||
};
|
||||
}
|
||||
|
||||
function comparePrereleaseIdentifiers(left, right) {
|
||||
const leftNumeric = /^\d+$/.test(left) ? Number.parseInt(left, 10) : null;
|
||||
const rightNumeric = /^\d+$/.test(right) ? Number.parseInt(right, 10) : null;
|
||||
|
||||
if (leftNumeric !== null && rightNumeric !== null) {
|
||||
return leftNumeric - rightNumeric;
|
||||
}
|
||||
|
||||
if (leftNumeric !== null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rightNumeric !== null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return left.localeCompare(right);
|
||||
}
|
||||
|
||||
function compareVersions(leftValue, rightValue) {
|
||||
const left = parseVersion(leftValue);
|
||||
const right = parseVersion(rightValue);
|
||||
|
||||
if (!left && !right) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!left) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!right) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (left.major !== right.major) {
|
||||
return left.major - right.major;
|
||||
}
|
||||
|
||||
if (left.minor !== right.minor) {
|
||||
return left.minor - right.minor;
|
||||
}
|
||||
|
||||
if (left.patch !== right.patch) {
|
||||
return left.patch - right.patch;
|
||||
}
|
||||
|
||||
if (left.prerelease.length === 0 && right.prerelease.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (left.prerelease.length === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (right.prerelease.length === 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const maxLength = Math.max(left.prerelease.length, right.prerelease.length);
|
||||
|
||||
for (let index = 0; index < maxLength; index += 1) {
|
||||
const leftIdentifier = left.prerelease[index];
|
||||
const rightIdentifier = right.prerelease[index];
|
||||
|
||||
if (!leftIdentifier) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!rightIdentifier) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const comparison = comparePrereleaseIdentifiers(leftIdentifier, rightIdentifier);
|
||||
|
||||
if (comparison !== 0) {
|
||||
return comparison;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {};
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const token = argv[index];
|
||||
|
||||
if (!token.startsWith('--')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = token.slice(2);
|
||||
const nextToken = argv[index + 1];
|
||||
|
||||
if (!nextToken || nextToken.startsWith('--')) {
|
||||
args[key] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
args[key] = nextToken;
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
function ensureHttpUrl(rawValue) {
|
||||
if (typeof rawValue !== 'string' || rawValue.trim().length === 0) {
|
||||
throw new Error('A release feed URL is required. Pass it with --feed-url.');
|
||||
}
|
||||
|
||||
const parsedUrl = new URL(rawValue.trim());
|
||||
|
||||
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
|
||||
throw new Error('The release feed URL must use http:// or https://.');
|
||||
}
|
||||
|
||||
return parsedUrl.toString().replace(/\/$/, '');
|
||||
}
|
||||
|
||||
function readJsonIfExists(filePath) {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
}
|
||||
|
||||
function warnIfNoDescriptors(distPath) {
|
||||
const availableDescriptors = descriptorCandidates.filter((fileName) => {
|
||||
return fs.existsSync(path.join(distPath, fileName));
|
||||
});
|
||||
|
||||
if (availableDescriptors.length === 0) {
|
||||
console.warn('[release-manifest] Warning: no latest*.yml descriptor was found in dist-electron/.');
|
||||
console.warn('[release-manifest] Upload a feed directory that contains the Electron Builder update descriptors.');
|
||||
}
|
||||
}
|
||||
|
||||
function buildDefaultManifest(version) {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
minimumServerVersion: version,
|
||||
pollIntervalMinutes: 30,
|
||||
versions: []
|
||||
};
|
||||
}
|
||||
|
||||
function sortManifestVersions(versions) {
|
||||
return [...versions].sort((left, right) => compareVersions(right.version, left.version));
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
const packageJson = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8'));
|
||||
const version = normalizeVersion(args.version || packageJson.version);
|
||||
|
||||
if (!version) {
|
||||
throw new Error('Unable to determine the release version. Pass it with --version.');
|
||||
}
|
||||
|
||||
const feedUrl = ensureHttpUrl(args['feed-url']);
|
||||
const manifestPath = path.resolve(rootDir, args.manifest || defaultManifestPath);
|
||||
const existingPath = path.resolve(rootDir, args.existing || manifestPath);
|
||||
const existingManifest = readJsonIfExists(existingPath) || buildDefaultManifest(version);
|
||||
const distPath = path.join(rootDir, 'dist-electron');
|
||||
|
||||
if (fs.existsSync(distPath)) {
|
||||
warnIfNoDescriptors(distPath);
|
||||
}
|
||||
|
||||
const nextEntry = {
|
||||
version,
|
||||
feedUrl,
|
||||
publishedAt: typeof args['published-at'] === 'string'
|
||||
? args['published-at']
|
||||
: new Date().toISOString(),
|
||||
...(typeof args.notes === 'string' && args.notes.trim().length > 0
|
||||
? { notes: args.notes.trim() }
|
||||
: {})
|
||||
};
|
||||
const nextManifest = {
|
||||
schemaVersion: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
minimumServerVersion: normalizeVersion(args['minimum-server-version'])
|
||||
|| normalizeVersion(existingManifest.minimumServerVersion)
|
||||
|| version,
|
||||
pollIntervalMinutes: Number.isFinite(Number(args['poll-interval-minutes']))
|
||||
? Math.max(5, Math.round(Number(args['poll-interval-minutes'])))
|
||||
: Math.max(5, Math.round(Number(existingManifest.pollIntervalMinutes || 30))),
|
||||
versions: sortManifestVersions(
|
||||
[...(Array.isArray(existingManifest.versions) ? existingManifest.versions : [])]
|
||||
.filter((entry) => normalizeVersion(entry && entry.version) !== version)
|
||||
.concat(nextEntry)
|
||||
)
|
||||
};
|
||||
|
||||
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
||||
fs.writeFileSync(manifestPath, `${JSON.stringify(nextManifest, null, 2)}\n`, 'utf8');
|
||||
|
||||
console.log(`[release-manifest] Wrote ${manifestPath}`);
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
|
||||
console.error(`[release-manifest] ${message}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
Reference in New Issue
Block a user