@ulthon/ul-opencode-event 0.1.17 → 0.1.19
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/dist/config.js +2 -0
- package/dist/dingtalk.js +2 -2
- package/dist/index.js +19 -0
- package/dist/types.d.ts +2 -0
- package/dist/updater.d.ts +38 -0
- package/dist/updater.js +385 -0
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -180,6 +180,7 @@ export function loadConfig() {
|
|
|
180
180
|
// Merge top-level fields (language, timezone) — project config takes priority
|
|
181
181
|
const mergedLanguage = projectConfig?.language ?? globalConfig?.language;
|
|
182
182
|
const mergedTimezone = projectConfig?.timezone ?? globalConfig?.timezone;
|
|
183
|
+
const mergedAutoUpdate = projectConfig?.auto_update ?? globalConfig?.auto_update;
|
|
183
184
|
// Merge includeSessionTypes — project overrides global
|
|
184
185
|
const rawIncludeSessionTypes = projectConfig?.includeSessionTypes ?? globalConfig?.includeSessionTypes;
|
|
185
186
|
// Deprecation warning for old field
|
|
@@ -192,6 +193,7 @@ export function loadConfig() {
|
|
|
192
193
|
channels: enabledChannels,
|
|
193
194
|
...(mergedLanguage !== undefined && { language: mergedLanguage }),
|
|
194
195
|
...(mergedTimezone !== undefined && { timezone: mergedTimezone }),
|
|
196
|
+
...(mergedAutoUpdate !== undefined ? { auto_update: mergedAutoUpdate } : { auto_update: true }),
|
|
195
197
|
includeSessionTypes: validateIncludeSessionTypes(rawIncludeSessionTypes),
|
|
196
198
|
};
|
|
197
199
|
}
|
package/dist/dingtalk.js
CHANGED
|
@@ -91,8 +91,8 @@ export function createDingTalkFormatter(config) {
|
|
|
91
91
|
// Default: escape special characters and pass through
|
|
92
92
|
processedLines.push(escapeMarkdown(trimmed));
|
|
93
93
|
}
|
|
94
|
-
// 3. Join
|
|
95
|
-
let text = processedLines.join('\n');
|
|
94
|
+
// 3. Join with double newlines (DingTalk Markdown requires \n\n for line breaks)
|
|
95
|
+
let text = processedLines.join('\n\n');
|
|
96
96
|
if (text.length > MAX_CONTENT_LENGTH) {
|
|
97
97
|
text = text.slice(0, TRUNCATE_KEEP_LENGTH) + TRUNCATE_SUFFIX;
|
|
98
98
|
}
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createEventHandler } from './handler.js';
|
|
|
3
3
|
import { getDefaultTimezone } from './locales.js';
|
|
4
4
|
import { DEFAULT_SESSION_TYPES } from './types.js';
|
|
5
5
|
import { logger, DEBUG_ENABLED } from './logger.js';
|
|
6
|
+
import { runAutoUpdate } from './updater.js';
|
|
6
7
|
/**
|
|
7
8
|
* Resolve session type from SDK Session info.
|
|
8
9
|
* - undefined/missing sessionInfo → 'unknown' (race condition or file.edited)
|
|
@@ -204,6 +205,10 @@ function formatError(error) {
|
|
|
204
205
|
}
|
|
205
206
|
return String(error);
|
|
206
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Guard flag to ensure auto-update only runs once per plugin load
|
|
210
|
+
*/
|
|
211
|
+
let hasCheckedUpdate = false;
|
|
207
212
|
/**
|
|
208
213
|
* Notification Plugin for OpenCode
|
|
209
214
|
* Listens for session events and sends notifications via configured channels
|
|
@@ -238,6 +243,20 @@ export const NotificationPlugin = async (ctx) => {
|
|
|
238
243
|
}
|
|
239
244
|
if (event.type === 'session.created') {
|
|
240
245
|
sessionMap.set(event.properties.info.id, event.properties.info);
|
|
246
|
+
// Auto-update check (runs once, first main session only)
|
|
247
|
+
if (!hasCheckedUpdate) {
|
|
248
|
+
hasCheckedUpdate = true;
|
|
249
|
+
const info = event.properties.info;
|
|
250
|
+
const isCliMode = process.env.OPENCODE_CLI_RUN_MODE === 'true';
|
|
251
|
+
const isSubSession = info.parentID !== undefined && info.parentID !== '';
|
|
252
|
+
if (!isCliMode && !isSubSession) {
|
|
253
|
+
setTimeout(() => {
|
|
254
|
+
runAutoUpdate(ctx, config.auto_update ?? true).catch((err) => {
|
|
255
|
+
logger.error(`Auto-update check failed: ${err}`);
|
|
256
|
+
});
|
|
257
|
+
}, 0);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
241
260
|
}
|
|
242
261
|
else if (event.type === 'session.updated') {
|
|
243
262
|
sessionMap.set(event.properties.info.id, event.properties.info);
|
package/dist/types.d.ts
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-update module for @ulthon/ul-opencode-event.
|
|
3
|
+
*
|
|
4
|
+
* Checks npm registry for new versions, manages a 24h cache,
|
|
5
|
+
* and optionally runs `bun install` to auto-update.
|
|
6
|
+
*
|
|
7
|
+
* Zero runtime dependencies — only Node.js built-ins + project logger.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Read the current installed version from this module's package.json
|
|
11
|
+
* by walking up from `import.meta.url`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getCurrentVersion(): string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Fetch the latest version string from the npm dist-tags API.
|
|
16
|
+
* Returns `null` on any error (network, parse, timeout).
|
|
17
|
+
*/
|
|
18
|
+
export declare function getLatestVersion(): Promise<string | null>;
|
|
19
|
+
export declare function getCachedCheck(): {
|
|
20
|
+
latestVersion: string;
|
|
21
|
+
timestamp: number;
|
|
22
|
+
} | null;
|
|
23
|
+
export declare function setCachedCheck(latestVersion: string): void;
|
|
24
|
+
export declare function isCacheValid(): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Execute `bun install` in the given workspace directory.
|
|
27
|
+
* Uses Bun.spawn if available, otherwise falls back to child_process.spawn.
|
|
28
|
+
*/
|
|
29
|
+
export declare function runBunInstall(workspaceDir: string): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Orchestrates the full auto-update flow:
|
|
32
|
+
* 1. Skip checks (dev mode, pinned version)
|
|
33
|
+
* 2. Get current version
|
|
34
|
+
* 3. Check cache, fetch latest if stale
|
|
35
|
+
* 4. Compare versions
|
|
36
|
+
* 5. Notify or auto-update
|
|
37
|
+
*/
|
|
38
|
+
export declare function runAutoUpdate(ctx: any, autoUpdate: boolean): Promise<void>;
|
package/dist/updater.js
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-update module for @ulthon/ul-opencode-event.
|
|
3
|
+
*
|
|
4
|
+
* Checks npm registry for new versions, manages a 24h cache,
|
|
5
|
+
* and optionally runs `bun install` to auto-update.
|
|
6
|
+
*
|
|
7
|
+
* Zero runtime dependencies — only Node.js built-ins + project logger.
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import * as os from 'os';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { logger } from './logger.js';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Constants
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const PACKAGE_NAME = '@ulthon/ul-opencode-event';
|
|
18
|
+
const NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${encodeURIComponent(PACKAGE_NAME)}/dist-tags`;
|
|
19
|
+
const NPM_FETCH_TIMEOUT = 5000; // 5 seconds
|
|
20
|
+
const BUN_INSTALL_TIMEOUT = 60000; // 60 seconds
|
|
21
|
+
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours in ms
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Helpers: cache paths
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
function getCacheDir() {
|
|
26
|
+
return process.env.OPENCODE_DATA_DIR ?? path.join(os.homedir(), '.local', 'share', 'opencode');
|
|
27
|
+
}
|
|
28
|
+
function getCacheFilePath() {
|
|
29
|
+
return path.join(getCacheDir(), 'ul-opencode-event-update-cache.json');
|
|
30
|
+
}
|
|
31
|
+
function getUserConfigDir() {
|
|
32
|
+
return path.join(os.homedir(), '.config', 'opencode');
|
|
33
|
+
}
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Exported: version resolution
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
/**
|
|
38
|
+
* Read the current installed version from this module's package.json
|
|
39
|
+
* by walking up from `import.meta.url`.
|
|
40
|
+
*/
|
|
41
|
+
export function getCurrentVersion() {
|
|
42
|
+
try {
|
|
43
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
44
|
+
for (let i = 0; i < 10; i++) {
|
|
45
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
46
|
+
try {
|
|
47
|
+
const raw = fs.readFileSync(pkgPath, 'utf-8');
|
|
48
|
+
const pkg = JSON.parse(raw);
|
|
49
|
+
if (pkg.name === PACKAGE_NAME) {
|
|
50
|
+
return pkg.version ?? null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// package.json not found here, walk up
|
|
55
|
+
}
|
|
56
|
+
const parent = path.dirname(dir);
|
|
57
|
+
if (parent === dir)
|
|
58
|
+
break; // filesystem root
|
|
59
|
+
dir = parent;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Exported: npm registry fetch
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
/**
|
|
71
|
+
* Fetch the latest version string from the npm dist-tags API.
|
|
72
|
+
* Returns `null` on any error (network, parse, timeout).
|
|
73
|
+
*/
|
|
74
|
+
export async function getLatestVersion() {
|
|
75
|
+
try {
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
const timer = setTimeout(() => controller.abort(), NPM_FETCH_TIMEOUT);
|
|
78
|
+
const resp = await fetch(NPM_REGISTRY_URL, {
|
|
79
|
+
signal: controller.signal,
|
|
80
|
+
headers: { Accept: 'application/json' },
|
|
81
|
+
});
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
if (!resp.ok) {
|
|
84
|
+
logger.debug(`[updater] npm registry returned status ${resp.status}`);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const data = (await resp.json());
|
|
88
|
+
return typeof data.latest === 'string' ? data.latest : null;
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
logger.debug(`[updater] Failed to fetch latest version: ${err}`);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Exported: cache read / write / validity
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
export function getCachedCheck() {
|
|
99
|
+
try {
|
|
100
|
+
const raw = fs.readFileSync(getCacheFilePath(), 'utf-8');
|
|
101
|
+
return JSON.parse(raw);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export function setCachedCheck(latestVersion) {
|
|
108
|
+
try {
|
|
109
|
+
const obj = { latestVersion, timestamp: Date.now() };
|
|
110
|
+
fs.mkdirSync(path.dirname(getCacheFilePath()), { recursive: true });
|
|
111
|
+
fs.writeFileSync(getCacheFilePath(), JSON.stringify(obj, null, 2), 'utf-8');
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
logger.error(`[updater] Failed to write update cache: ${err}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export function isCacheValid() {
|
|
118
|
+
const cached = getCachedCheck();
|
|
119
|
+
if (!cached)
|
|
120
|
+
return false;
|
|
121
|
+
return Date.now() - cached.timestamp < CACHE_TTL;
|
|
122
|
+
}
|
|
123
|
+
function readPluginList(directory) {
|
|
124
|
+
const results = [];
|
|
125
|
+
const candidates = [
|
|
126
|
+
path.join(directory, '.opencode', 'settings.json'),
|
|
127
|
+
path.join(getUserConfigDir(), 'settings.json'),
|
|
128
|
+
];
|
|
129
|
+
for (const cfgPath of candidates) {
|
|
130
|
+
try {
|
|
131
|
+
const raw = fs.readFileSync(cfgPath, 'utf-8');
|
|
132
|
+
const settings = JSON.parse(raw);
|
|
133
|
+
if (Array.isArray(settings.plugin)) {
|
|
134
|
+
results.push(...settings.plugin);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// file missing or invalid — skip
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
function isLocalDevMode(directory) {
|
|
144
|
+
try {
|
|
145
|
+
const plugins = readPluginList(directory);
|
|
146
|
+
return plugins.some((entry) => entry.startsWith('file://') && entry.includes('ul-opencode-event'));
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/;
|
|
153
|
+
function isPinnedVersion(directory) {
|
|
154
|
+
try {
|
|
155
|
+
const plugins = readPluginList(directory);
|
|
156
|
+
return plugins.some((entry) => {
|
|
157
|
+
// Match patterns like @ulthon/ul-opencode-event@1.2.3 or @ulthon/ul-opencode-event@1.2.3-beta.1
|
|
158
|
+
if (!entry.includes('ul-opencode-event'))
|
|
159
|
+
return false;
|
|
160
|
+
const atIndex = entry.lastIndexOf('@');
|
|
161
|
+
if (atIndex <= 0)
|
|
162
|
+
return false; // no version part or starts with @ (scoped package)
|
|
163
|
+
const version = entry.slice(atIndex + 1);
|
|
164
|
+
// For scoped packages like @ulthon/ul-opencode-event@1.2.3, the lastIndexOf('@')
|
|
165
|
+
// will find the second @. But we need to make sure the prefix is the scoped package.
|
|
166
|
+
// So we check the version part matches exact semver.
|
|
167
|
+
return EXACT_SEMVER_RE.test(version);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// Helper: package invalidation
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
function invalidatePackage() {
|
|
178
|
+
try {
|
|
179
|
+
const dirs = [
|
|
180
|
+
path.join(getCacheDir(), 'packages', 'node_modules', PACKAGE_NAME),
|
|
181
|
+
path.join(getUserConfigDir(), 'node_modules', PACKAGE_NAME),
|
|
182
|
+
];
|
|
183
|
+
for (const pkgDir of dirs) {
|
|
184
|
+
try {
|
|
185
|
+
if (fs.existsSync(pkgDir)) {
|
|
186
|
+
fs.rmSync(pkgDir, { recursive: true, force: true });
|
|
187
|
+
logger.info(`[updater] Invalidated package at ${pkgDir}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
logger.debug(`[updater] Could not remove ${pkgDir}: ${err}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Also remove bun.lock / bun.lockb from workspace dirs
|
|
195
|
+
const workspaceDirs = [
|
|
196
|
+
path.join(getCacheDir(), 'packages'),
|
|
197
|
+
getUserConfigDir(),
|
|
198
|
+
];
|
|
199
|
+
for (const wsDir of workspaceDirs) {
|
|
200
|
+
for (const lockFile of ['bun.lock', 'bun.lockb']) {
|
|
201
|
+
const lockPath = path.join(wsDir, lockFile);
|
|
202
|
+
try {
|
|
203
|
+
if (fs.existsSync(lockPath)) {
|
|
204
|
+
fs.rmSync(lockPath, { force: true });
|
|
205
|
+
logger.info(`[updater] Removed ${lockPath}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
logger.debug(`[updater] Could not remove ${lockPath}: ${err}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
logger.error(`[updater] Package invalidation failed: ${err}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Helper: resolve workspace directory
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
function resolveWorkspaceDir() {
|
|
222
|
+
const configDir = getUserConfigDir();
|
|
223
|
+
const packagesDir = path.join(getCacheDir(), 'packages');
|
|
224
|
+
// Check if installed in config dir
|
|
225
|
+
const configPkg = path.join(configDir, 'node_modules', PACKAGE_NAME, 'package.json');
|
|
226
|
+
if (fs.existsSync(configPkg))
|
|
227
|
+
return configDir;
|
|
228
|
+
// Check packages dir (node_modules style)
|
|
229
|
+
const packagesPkg = path.join(packagesDir, 'node_modules', PACKAGE_NAME, 'package.json');
|
|
230
|
+
if (fs.existsSync(packagesPkg))
|
|
231
|
+
return packagesDir;
|
|
232
|
+
// Check packages dir (flat)
|
|
233
|
+
const flatPkg = path.join(packagesDir, 'package.json');
|
|
234
|
+
if (fs.existsSync(flatPkg))
|
|
235
|
+
return packagesDir;
|
|
236
|
+
// Default to config dir
|
|
237
|
+
return configDir;
|
|
238
|
+
}
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Exported: bun install
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
/**
|
|
243
|
+
* Execute `bun install` in the given workspace directory.
|
|
244
|
+
* Uses Bun.spawn if available, otherwise falls back to child_process.spawn.
|
|
245
|
+
*/
|
|
246
|
+
export async function runBunInstall(workspaceDir) {
|
|
247
|
+
try {
|
|
248
|
+
const { spawn } = await import('child_process');
|
|
249
|
+
return await new Promise((resolve) => {
|
|
250
|
+
const spawnOptions = {
|
|
251
|
+
cwd: workspaceDir,
|
|
252
|
+
windowsHide: true,
|
|
253
|
+
shell: true,
|
|
254
|
+
};
|
|
255
|
+
const child = spawn('bun', ['install'], spawnOptions);
|
|
256
|
+
let stdout = '';
|
|
257
|
+
let stderr = '';
|
|
258
|
+
child.stdout?.on('data', (data) => {
|
|
259
|
+
stdout += data.toString();
|
|
260
|
+
});
|
|
261
|
+
child.stderr?.on('data', (data) => {
|
|
262
|
+
stderr += data.toString();
|
|
263
|
+
});
|
|
264
|
+
// Timeout guard
|
|
265
|
+
const timer = setTimeout(() => {
|
|
266
|
+
child.kill('SIGKILL');
|
|
267
|
+
logger.warn('[updater] bun install timed out after 60s');
|
|
268
|
+
resolve(false);
|
|
269
|
+
}, BUN_INSTALL_TIMEOUT);
|
|
270
|
+
child.on('close', (code) => {
|
|
271
|
+
clearTimeout(timer);
|
|
272
|
+
if (code === 0) {
|
|
273
|
+
resolve(true);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
logger.warn(`[updater] bun install exited with code ${code}`);
|
|
277
|
+
if (stderr)
|
|
278
|
+
logger.debug(`[updater] bun install stderr: ${stderr}`);
|
|
279
|
+
if (stdout)
|
|
280
|
+
logger.debug(`[updater] bun install stdout: ${stdout}`);
|
|
281
|
+
resolve(false);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
child.on('error', (err) => {
|
|
285
|
+
clearTimeout(timer);
|
|
286
|
+
logger.error(`[updater] bun install spawn error: ${err}`);
|
|
287
|
+
resolve(false);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
logger.error(`[updater] bun install failed: ${err}`);
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
// Helper: toast notification
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
async function showToast(ctx, title, message, variant, duration) {
|
|
300
|
+
try {
|
|
301
|
+
await ctx.client.tui
|
|
302
|
+
.showToast({ body: { title, message, variant, duration } })
|
|
303
|
+
.catch(() => { });
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
logger.info(`[updater] ${title}: ${message}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
// Main entry point
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
/**
|
|
313
|
+
* Orchestrates the full auto-update flow:
|
|
314
|
+
* 1. Skip checks (dev mode, pinned version)
|
|
315
|
+
* 2. Get current version
|
|
316
|
+
* 3. Check cache, fetch latest if stale
|
|
317
|
+
* 4. Compare versions
|
|
318
|
+
* 5. Notify or auto-update
|
|
319
|
+
*/
|
|
320
|
+
export async function runAutoUpdate(ctx, autoUpdate) {
|
|
321
|
+
try {
|
|
322
|
+
// 1. Skip checks
|
|
323
|
+
if (isLocalDevMode(ctx.directory)) {
|
|
324
|
+
logger.info('[updater] Local development mode detected, skipping update check');
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (isPinnedVersion(ctx.directory)) {
|
|
328
|
+
logger.info('[updater] Pinned version detected, skipping auto-update');
|
|
329
|
+
// Still check and notify, but don't auto-install
|
|
330
|
+
autoUpdate = false;
|
|
331
|
+
}
|
|
332
|
+
// 2. Get current version
|
|
333
|
+
const currentVersion = getCurrentVersion();
|
|
334
|
+
if (!currentVersion) {
|
|
335
|
+
logger.debug('[updater] Could not determine current version');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
// 3. Check cache first
|
|
339
|
+
let latestVersion = null;
|
|
340
|
+
if (isCacheValid()) {
|
|
341
|
+
const cached = getCachedCheck();
|
|
342
|
+
if (cached) {
|
|
343
|
+
latestVersion = cached.latestVersion;
|
|
344
|
+
logger.debug(`[updater] Using cached version info: ${latestVersion}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// 4. Fetch latest if no valid cache
|
|
348
|
+
if (!latestVersion) {
|
|
349
|
+
latestVersion = await getLatestVersion();
|
|
350
|
+
if (latestVersion) {
|
|
351
|
+
setCachedCheck(latestVersion);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (!latestVersion) {
|
|
355
|
+
logger.debug('[updater] Could not fetch latest version');
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
// 5. Compare versions (simple string equality)
|
|
359
|
+
if (currentVersion === latestVersion) {
|
|
360
|
+
logger.debug(`[updater] Already on latest version: ${currentVersion}`);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
logger.info(`[updater] Update available: ${currentVersion} -> ${latestVersion}`);
|
|
364
|
+
// 6. If auto-update disabled, just notify
|
|
365
|
+
if (!autoUpdate) {
|
|
366
|
+
await showToast(ctx, `ul-opencode-event ${latestVersion}`, `Update available: v${currentVersion} -> v${latestVersion}\nRun: npm update @ulthon/ul-opencode-event`, 'info', 8000);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
// 7. Auto-update: invalidate + bun install
|
|
370
|
+
invalidatePackage();
|
|
371
|
+
const workspaceDir = resolveWorkspaceDir();
|
|
372
|
+
const success = await runBunInstall(workspaceDir);
|
|
373
|
+
if (success) {
|
|
374
|
+
await showToast(ctx, 'ul-opencode-event Updated!', `v${currentVersion} -> v${latestVersion}\nRestart OpenCode to apply.`, 'success', 8000);
|
|
375
|
+
logger.info(`[updater] Update installed: ${currentVersion} -> ${latestVersion}`);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
await showToast(ctx, 'ul-opencode-event Update Failed', `Failed to install v${latestVersion}. Run manually: npm update @ulthon/ul-opencode-event`, 'error', 8000);
|
|
379
|
+
logger.warn('[updater] bun install failed, update not installed');
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
catch (err) {
|
|
383
|
+
logger.error(`[updater] Auto-update check failed: ${err}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
package/package.json
CHANGED