-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathzotero-version.ts
More file actions
142 lines (122 loc) · 3.49 KB
/
zotero-version.ts
File metadata and controls
142 lines (122 loc) · 3.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { homedir } from "node:os";
import { join } from "node:path";
import { ensureDir, pathExists, readJSON, writeJSON } from "fs-extra/esm";
/**
* Platform identifier for version information
*/
export type Platform = "mac" | "win-x64" | "win-arm64" | "win32" | "linux-x86_64" | "linux-i686" | "linux-arm64";
/**
* Version info structure
*/
export interface ZoteroVersionInfo {
lastUpdated: number;
release: Record<Platform, string>;
beta: Record<Platform, string>;
}
const CACHE_DIR = join(homedir(), ".scaffold", "cache");
const CACHE_FILE = join(CACHE_DIR, "zotero-version.json");
const CACHE_TTL = 3 * 24 * 60 * 60 * 1000; // 3 days in milliseconds
const API_BETA = "https://www.zotero.org/download/client/version?channel=beta";
const API_RELEASE = "https://www.zotero.org/download/client/version?channel=release";
/**
* Fetch Zotero version information from API
*/
async function fetchVersionsFromAPI(): Promise<Omit<ZoteroVersionInfo, "lastUpdated">> {
const [betaRes, releaseRes] = await Promise.all([
fetch(API_BETA),
fetch(API_RELEASE),
]);
if (!betaRes.ok || !releaseRes.ok) {
throw new Error("Failed to fetch Zotero version information from API");
}
const [beta, release] = await Promise.all([
betaRes.json(),
releaseRes.json(),
]);
return {
beta: beta as Record<Platform, string>,
release: release as Record<Platform, string>,
};
}
/**
* Load version cache from disk
*/
async function loadCache(): Promise<ZoteroVersionInfo | null> {
try {
if (!await pathExists(CACHE_FILE)) {
return null;
}
const cache = await readJSON(CACHE_FILE) as ZoteroVersionInfo;
const age = Date.now() - cache.lastUpdated;
// Check if cache is still valid
if (age > CACHE_TTL) {
return null;
}
return cache;
}
catch {
return null;
}
}
/**
* Save version cache to disk
*/
async function saveCache(data: Omit<ZoteroVersionInfo, "lastUpdated">): Promise<void> {
await ensureDir(CACHE_DIR);
const cache: ZoteroVersionInfo = {
lastUpdated: Date.now(),
...data,
};
await writeJSON(CACHE_FILE, cache, { spaces: 2 });
}
/**
* Get Zotero version information with caching
*/
export async function getZoteroVersionInfo(): Promise<ZoteroVersionInfo> {
// Try to load from cache first
const cachedData = await loadCache();
if (cachedData) {
return cachedData;
}
// Fetch from API if cache is not available
const freshData = await fetchVersionsFromAPI();
await saveCache(freshData);
return {
lastUpdated: Date.now(),
...freshData,
};
}
/**
* Parse version string to extract major version
* E.g., "9.0-beta.21+1a89239a1" -> 9, "9.0" -> 9
*/
export function parseMajorVersion(version: string): number {
const match = version.match(/^(\d+)/);
if (!match) {
throw new Error(`Invalid version format: ${version}`);
}
return Number.parseInt(match[1], 10);
}
/**
* Compare two version strings
* Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
*/
export function compareVersions(v1: string, v2: string): number {
const v1Parts = v1.split(/[.-]/).map((p) => {
const num = Number.parseInt(p, 10);
return Number.isNaN(num) ? 0 : num;
});
const v2Parts = v2.split(/[.-]/).map((p) => {
const num = Number.parseInt(p, 10);
return Number.isNaN(num) ? 0 : num;
});
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
const p1 = v1Parts[i] ?? 0;
const p2 = v2Parts[i] ?? 0;
if (p1 < p2)
return -1;
if (p1 > p2)
return 1;
}
return 0;
}