opencode-worktree 0.3.5 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -75
- package/package.json +1 -1
- package/src/config.ts +168 -34
- package/src/git.ts +131 -0
- package/src/types.ts +26 -0
- package/src/ui.ts +368 -58
- package/src/update-check.ts +74 -94
package/src/update-check.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
2
|
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import https from "node:https";
|
|
5
5
|
|
|
6
6
|
export type UpdateInfo = {
|
|
7
7
|
hasUpdate: boolean;
|
|
@@ -10,147 +10,123 @@ export type UpdateInfo = {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
type UpdateCache = {
|
|
13
|
-
name: string;
|
|
13
|
+
name: string;
|
|
14
14
|
latest: string;
|
|
15
15
|
lastCheck: number;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
const CACHE_DIR = path.join(os.homedir(), ".config", "configstore");
|
|
19
19
|
const CACHE_FILE = path.join(CACHE_DIR, "opencode-worktree-update.json");
|
|
20
|
+
const REQUEST_TIMEOUT_MS = 4000;
|
|
20
21
|
|
|
21
|
-
// Check interval: 1 hour in milliseconds
|
|
22
|
-
const CHECK_INTERVAL_MS = 60 * 60 * 1000;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Read the cached update info from disk
|
|
26
|
-
* Returns null if cache doesn't exist or package name doesn't match
|
|
27
|
-
*/
|
|
28
22
|
function readCache(packageName: string): UpdateCache | null {
|
|
29
23
|
try {
|
|
30
24
|
if (!fs.existsSync(CACHE_FILE)) {
|
|
31
25
|
return null;
|
|
32
26
|
}
|
|
27
|
+
|
|
33
28
|
const content = fs.readFileSync(CACHE_FILE, "utf-8");
|
|
34
29
|
const cache = JSON.parse(content) as UpdateCache;
|
|
35
|
-
|
|
36
|
-
// Verify the cache is for the correct package
|
|
37
30
|
if (cache.name !== packageName) {
|
|
38
31
|
return null;
|
|
39
32
|
}
|
|
40
|
-
|
|
33
|
+
|
|
41
34
|
return cache;
|
|
42
35
|
} catch {
|
|
43
36
|
return null;
|
|
44
37
|
}
|
|
45
38
|
}
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
function writeCache(packageName: string, latestVersion: string): void {
|
|
41
|
+
try {
|
|
42
|
+
if (!fs.existsSync(CACHE_DIR)) {
|
|
43
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fs.writeFileSync(
|
|
47
|
+
CACHE_FILE,
|
|
48
|
+
JSON.stringify(
|
|
49
|
+
{
|
|
50
|
+
name: packageName,
|
|
51
|
+
latest: latestVersion,
|
|
52
|
+
lastCheck: Date.now(),
|
|
53
|
+
},
|
|
54
|
+
null,
|
|
55
|
+
2,
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
} catch {
|
|
59
|
+
// Best-effort cache write.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
51
63
|
function isNewerVersion(current: string, latest: string): boolean {
|
|
52
|
-
// Handle dev versions
|
|
53
64
|
if (current === "dev" || current === "0.0.0") {
|
|
54
65
|
return false;
|
|
55
66
|
}
|
|
56
67
|
|
|
57
|
-
const parseVersion = (
|
|
58
|
-
|
|
68
|
+
const parseVersion = (version: string): number[] =>
|
|
69
|
+
version
|
|
59
70
|
.replace(/^v/, "")
|
|
60
71
|
.split(".")
|
|
61
|
-
.map((
|
|
62
|
-
};
|
|
72
|
+
.map((part) => parseInt(part, 10) || 0);
|
|
63
73
|
|
|
64
74
|
const currentParts = parseVersion(current);
|
|
65
75
|
const latestParts = parseVersion(latest);
|
|
66
76
|
|
|
67
77
|
for (let i = 0; i < 3; i++) {
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
if (
|
|
78
|
+
const currentPart = currentParts[i] || 0;
|
|
79
|
+
const latestPart = latestParts[i] || 0;
|
|
80
|
+
|
|
81
|
+
if (latestPart > currentPart) return true;
|
|
82
|
+
if (latestPart < currentPart) return false;
|
|
72
83
|
}
|
|
73
84
|
|
|
74
85
|
return false;
|
|
75
86
|
}
|
|
76
87
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
https.get(url, { headers: { 'Accept': 'application/json' } }, (res) => {
|
|
95
|
-
let data = '';
|
|
96
|
-
res.on('data', (chunk) => { data += chunk; });
|
|
97
|
-
res.on('end', () => {
|
|
88
|
+
function fetchLatestVersion(packageName: string): void {
|
|
89
|
+
const url = `https://registry.npmjs.org/${packageName}/latest`;
|
|
90
|
+
|
|
91
|
+
const request = https.get(
|
|
92
|
+
url,
|
|
93
|
+
{ headers: { Accept: "application/json" } },
|
|
94
|
+
(response) => {
|
|
95
|
+
if (response.statusCode !== 200) {
|
|
96
|
+
response.resume();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let data = "";
|
|
101
|
+
response.on("data", (chunk: Buffer) => {
|
|
102
|
+
data += chunk.toString("utf-8");
|
|
103
|
+
});
|
|
104
|
+
response.on("end", () => {
|
|
98
105
|
try {
|
|
99
|
-
const
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
103
|
-
}
|
|
104
|
-
fs.writeFileSync(cacheFile, JSON.stringify({
|
|
105
|
-
name: '${packageName}',
|
|
106
|
-
latest: pkg.version,
|
|
107
|
-
lastCheck: Date.now()
|
|
108
|
-
}, null, 2));
|
|
106
|
+
const payload = JSON.parse(data) as { version?: string };
|
|
107
|
+
if (payload.version) {
|
|
108
|
+
writeCache(packageName, payload.version);
|
|
109
109
|
}
|
|
110
|
-
} catch {
|
|
110
|
+
} catch {
|
|
111
|
+
// Ignore invalid response payloads.
|
|
112
|
+
}
|
|
111
113
|
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// Unref so the parent process can exit independently
|
|
124
|
-
child.unref();
|
|
125
|
-
} catch {
|
|
126
|
-
// Silently fail - background fetch is best-effort
|
|
127
|
-
}
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
request.setTimeout(REQUEST_TIMEOUT_MS, () => {
|
|
118
|
+
request.destroy();
|
|
119
|
+
});
|
|
120
|
+
request.on("error", () => {
|
|
121
|
+
// Best-effort network check.
|
|
122
|
+
});
|
|
128
123
|
}
|
|
129
124
|
|
|
130
|
-
|
|
131
|
-
* Check for updates. This is non-blocking:
|
|
132
|
-
* 1. Reads the cached latest version (if available)
|
|
133
|
-
* 2. Compares against current version
|
|
134
|
-
* 3. Spawns a background process to refresh the cache for next time
|
|
135
|
-
*
|
|
136
|
-
* Returns null if no cache exists yet (first run)
|
|
137
|
-
*/
|
|
138
|
-
export function checkForUpdate(pkg: {
|
|
125
|
+
export function getCachedUpdateNotice(pkg: {
|
|
139
126
|
name: string;
|
|
140
127
|
version: string;
|
|
141
128
|
}): UpdateInfo | null {
|
|
142
129
|
const cache = readCache(pkg.name);
|
|
143
|
-
const now = Date.now();
|
|
144
|
-
|
|
145
|
-
// Spawn background fetch if cache is stale or doesn't exist
|
|
146
|
-
const shouldFetch =
|
|
147
|
-
!cache || now - cache.lastCheck > CHECK_INTERVAL_MS;
|
|
148
|
-
|
|
149
|
-
if (shouldFetch) {
|
|
150
|
-
fetchLatestInBackground(pkg.name);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// If no cache, we can't determine if there's an update yet
|
|
154
130
|
if (!cache) {
|
|
155
131
|
return null;
|
|
156
132
|
}
|
|
@@ -161,3 +137,7 @@ export function checkForUpdate(pkg: {
|
|
|
161
137
|
latest: cache.latest,
|
|
162
138
|
};
|
|
163
139
|
}
|
|
140
|
+
|
|
141
|
+
export function checkForUpdatesOnLaunch(pkg: { name: string }): void {
|
|
142
|
+
fetchLatestVersion(pkg.name);
|
|
143
|
+
}
|