opencode-plugin-auto-update 0.2.0 → 0.3.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 +1 -1
- package/dist/index.js +109 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Automatically updates OpenCode plugins in the background on startup. No prompts,
|
|
|
16
16
|
|
|
17
17
|
- Zero startup delay (async, detached update)
|
|
18
18
|
- Bun-first installs with npm fallback
|
|
19
|
-
-
|
|
19
|
+
- Shows a brief toast summary after updates
|
|
20
20
|
- Skips local/path/git plugins
|
|
21
21
|
|
|
22
22
|
## 📦 Installation
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { appendFile, readFile as
|
|
2
|
+
import { appendFile, readFile as readFile4 } from "fs/promises";
|
|
3
3
|
import { join as join3 } from "path";
|
|
4
4
|
import { homedir as homedir3 } from "os";
|
|
5
5
|
|
|
@@ -126,6 +126,22 @@ async function getHostname() {
|
|
|
126
126
|
|
|
127
127
|
// src/update.ts
|
|
128
128
|
var DEFAULT_CONFIG_DIR2 = join2(homedir2(), ".config", "opencode");
|
|
129
|
+
function normalizePluginConfig(config) {
|
|
130
|
+
const existingPlugin = Array.isArray(config.plugin) ? [...config.plugin] : [];
|
|
131
|
+
const existingPlugins = Array.isArray(config.plugins) ? [...config.plugins] : [];
|
|
132
|
+
if (existingPlugins.length === 0) {
|
|
133
|
+
return { config, changed: false };
|
|
134
|
+
}
|
|
135
|
+
const merged = [];
|
|
136
|
+
for (const entry of [...existingPlugin, ...existingPlugins]) {
|
|
137
|
+
if (!merged.includes(entry)) {
|
|
138
|
+
merged.push(entry);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
config.plugin = merged;
|
|
142
|
+
delete config.plugins;
|
|
143
|
+
return { config, changed: true };
|
|
144
|
+
}
|
|
129
145
|
async function runAutoUpdate(options = {}) {
|
|
130
146
|
const disabled = options.disabled ?? envFlag("OPENCODE_AUTO_UPDATE_DISABLED");
|
|
131
147
|
if (disabled) {
|
|
@@ -133,7 +149,7 @@ async function runAutoUpdate(options = {}) {
|
|
|
133
149
|
}
|
|
134
150
|
const debug = options.debug ?? envFlag("OPENCODE_AUTO_UPDATE_DEBUG");
|
|
135
151
|
const ignoreThrottle = options.ignoreThrottle ?? envFlag("OPENCODE_AUTO_UPDATE_BYPASS_THROTTLE");
|
|
136
|
-
const intervalHours = options.intervalHours ?? envNumber("OPENCODE_AUTO_UPDATE_INTERVAL_HOURS",
|
|
152
|
+
const intervalHours = options.intervalHours ?? envNumber("OPENCODE_AUTO_UPDATE_INTERVAL_HOURS", 0);
|
|
137
153
|
const preservePinned = options.preservePinned ?? envFlag("OPENCODE_AUTO_UPDATE_PINNED");
|
|
138
154
|
const configDir = options.configDir ?? DEFAULT_CONFIG_DIR2;
|
|
139
155
|
const configPath = join2(configDir, "opencode.json");
|
|
@@ -159,15 +175,24 @@ async function runAutoUpdate(options = {}) {
|
|
|
159
175
|
try {
|
|
160
176
|
const state = await readThrottleState({ configDir });
|
|
161
177
|
const now = Date.now();
|
|
162
|
-
const intervalMs =
|
|
178
|
+
const intervalMs = intervalHours * 60 * 60 * 1e3;
|
|
163
179
|
if (!ignoreThrottle && state.lastRun && now - state.lastRun < intervalMs) {
|
|
164
180
|
log("[auto-update] Throttled, skipping update.");
|
|
165
181
|
return;
|
|
166
182
|
}
|
|
167
183
|
await writeThrottleState({ ...state, lastRun: now }, { debug, configDir });
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
184
|
+
const rawConfig = await readConfig(configPath);
|
|
185
|
+
if (!rawConfig) {
|
|
186
|
+
log("[auto-update] No config found, skipping.");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const normalized = normalizePluginConfig(rawConfig);
|
|
190
|
+
if (normalized.changed) {
|
|
191
|
+
await writeConfig(configPath, normalized.config);
|
|
192
|
+
log("[auto-update] Migrated config.plugins -> config.plugin");
|
|
193
|
+
}
|
|
194
|
+
const { plugins } = getPluginList(normalized.config);
|
|
195
|
+
if (!plugins || plugins.length === 0) {
|
|
171
196
|
log("[auto-update] No plugins found to update.");
|
|
172
197
|
return;
|
|
173
198
|
}
|
|
@@ -187,9 +212,28 @@ async function runAutoUpdate(options = {}) {
|
|
|
187
212
|
error
|
|
188
213
|
});
|
|
189
214
|
if (updateResult.changed) {
|
|
190
|
-
const updatedConfig = {
|
|
215
|
+
const updatedConfig = {
|
|
216
|
+
...normalized.config,
|
|
217
|
+
plugin: updateResult.plugins
|
|
218
|
+
};
|
|
219
|
+
delete updatedConfig.plugins;
|
|
191
220
|
await writeConfig(configPath, updatedConfig);
|
|
192
221
|
}
|
|
222
|
+
const hasOcx = await commandExists("ocx");
|
|
223
|
+
if (hasOcx) {
|
|
224
|
+
log("[auto-update] Found ocx, checking for extension updates...");
|
|
225
|
+
const ocxResult = await runCommand("ocx", ["update"]);
|
|
226
|
+
if (ocxResult.code === 0) {
|
|
227
|
+
const output = ocxResult.stdout.trim();
|
|
228
|
+
if (output) {
|
|
229
|
+
log("[auto-update] ocx update result:", output);
|
|
230
|
+
} else {
|
|
231
|
+
log("[auto-update] ocx update complete (no output).");
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
error("[auto-update] ocx update failed:", ocxResult.stderr || ocxResult.stdout);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
193
237
|
await writeThrottleState(
|
|
194
238
|
{ ...state, lastRun: now, lastSuccess: Date.now() },
|
|
195
239
|
{ debug, configDir }
|
|
@@ -216,16 +260,10 @@ async function writeConfig(configPath, config) {
|
|
|
216
260
|
`, "utf-8");
|
|
217
261
|
}
|
|
218
262
|
function getPluginList(config) {
|
|
219
|
-
if (!config) {
|
|
220
|
-
return { plugins: null, key: null };
|
|
221
|
-
}
|
|
222
263
|
if (Array.isArray(config.plugin)) {
|
|
223
|
-
return { plugins: config.plugin
|
|
224
|
-
}
|
|
225
|
-
if (Array.isArray(config.plugins)) {
|
|
226
|
-
return { plugins: config.plugins, key: "plugins" };
|
|
264
|
+
return { plugins: config.plugin };
|
|
227
265
|
}
|
|
228
|
-
return { plugins: null
|
|
266
|
+
return { plugins: null };
|
|
229
267
|
}
|
|
230
268
|
async function updatePlugins(options) {
|
|
231
269
|
const { plugins, configDir, preservePinned, useBun, log, error } = options;
|
|
@@ -392,8 +430,47 @@ function formatLogMessage(args) {
|
|
|
392
430
|
}).join(" ");
|
|
393
431
|
}
|
|
394
432
|
|
|
433
|
+
// src/utils/circular-log.ts
|
|
434
|
+
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
435
|
+
var MAX_ENTRIES = 5;
|
|
436
|
+
async function readCircularLog(logPath) {
|
|
437
|
+
try {
|
|
438
|
+
const contents = await readFile3(logPath, "utf-8");
|
|
439
|
+
return JSON.parse(contents);
|
|
440
|
+
} catch {
|
|
441
|
+
return { entries: [] };
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
async function appendCircularLog(logPath, entry) {
|
|
445
|
+
const log = await readCircularLog(logPath);
|
|
446
|
+
log.entries.push(entry);
|
|
447
|
+
if (log.entries.length > MAX_ENTRIES) {
|
|
448
|
+
log.entries = log.entries.slice(-MAX_ENTRIES);
|
|
449
|
+
}
|
|
450
|
+
await writeFile3(logPath, JSON.stringify(log, null, 2) + "\n", "utf-8");
|
|
451
|
+
}
|
|
452
|
+
function formatLogEntry(plugins, errors, duration, success) {
|
|
453
|
+
return {
|
|
454
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
455
|
+
pluginsUpdated: plugins,
|
|
456
|
+
errors: errors.slice(0, 10),
|
|
457
|
+
duration,
|
|
458
|
+
success
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
395
462
|
// src/index.ts
|
|
396
463
|
var CONFIG_PATH = join3(homedir3(), ".config", "opencode", "opencode-plugin-auto-update.json");
|
|
464
|
+
function extractUpdatedPlugins(logEntries) {
|
|
465
|
+
const plugins = [];
|
|
466
|
+
for (const entry of logEntries) {
|
|
467
|
+
const match = entry.match(/Updated\s+(.+?)\s+from\s+v[\d.]+\s+to\s+v[\d.]+/i);
|
|
468
|
+
if (match && match[1]) {
|
|
469
|
+
plugins.push(match[1]);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return plugins;
|
|
473
|
+
}
|
|
397
474
|
var DEBUG_FILE = "/tmp/opencode-auto-update-debug.log";
|
|
398
475
|
var DEBUG_ENABLED = false;
|
|
399
476
|
async function src_default(ctx) {
|
|
@@ -405,6 +482,7 @@ async function src_default(ctx) {
|
|
|
405
482
|
let lastToastMessage = null;
|
|
406
483
|
const localConfig = await readLocalConfig();
|
|
407
484
|
configIgnoreThrottle = localConfig.ignoreThrottle ?? false;
|
|
485
|
+
const configDebug = localConfig.debug ?? false;
|
|
408
486
|
const shouldIgnoreThrottle = () => envBypassThrottle || configIgnoreThrottle;
|
|
409
487
|
const notifyUser = async (message) => {
|
|
410
488
|
await writeDebug("notifyUser invoked");
|
|
@@ -423,9 +501,8 @@ async function src_default(ctx) {
|
|
|
423
501
|
});
|
|
424
502
|
await writeDebug("toast shown");
|
|
425
503
|
}
|
|
426
|
-
console.log(message);
|
|
427
|
-
await writeDebug("console fallback used");
|
|
428
504
|
};
|
|
505
|
+
const CIRCULAR_LOG_PATH = join3(ctx.directory, "..", ".auto-update-history.json");
|
|
429
506
|
const startUpdate = () => {
|
|
430
507
|
if (updateStarted) {
|
|
431
508
|
return;
|
|
@@ -434,21 +511,32 @@ async function src_default(ctx) {
|
|
|
434
511
|
void writeDebug(`startUpdate invoked (ignoreThrottle=${shouldIgnoreThrottle()})`);
|
|
435
512
|
const logEntries = [];
|
|
436
513
|
const errorEntries = [];
|
|
514
|
+
const startTime = Date.now();
|
|
437
515
|
runAutoUpdate({
|
|
438
|
-
debug:
|
|
516
|
+
debug: configDebug,
|
|
439
517
|
ignoreThrottle: shouldIgnoreThrottle(),
|
|
440
518
|
onLog: (message) => logEntries.push(message),
|
|
441
519
|
onError: (message) => errorEntries.push(message)
|
|
442
520
|
}).then(() => {
|
|
521
|
+
const duration = Date.now() - startTime;
|
|
522
|
+
const success = errorEntries.length === 0;
|
|
523
|
+
const pluginsUpdated = extractUpdatedPlugins(logEntries);
|
|
524
|
+
void appendCircularLog(
|
|
525
|
+
CIRCULAR_LOG_PATH,
|
|
526
|
+
formatLogEntry(pluginsUpdated, errorEntries, duration, success)
|
|
527
|
+
);
|
|
443
528
|
updateMessage = formatUpdateMessage(logEntries, errorEntries);
|
|
444
529
|
void writeDebug(`update finished (logs=${logEntries.length}, errors=${errorEntries.length})`);
|
|
445
530
|
notifyUser(updateMessage).catch((error) => {
|
|
446
531
|
void writeDebug(`notifyUser error: ${String(error)}`);
|
|
447
|
-
console.error("[opencode-plugin-auto-update] Failed to notify user:", error);
|
|
448
532
|
});
|
|
449
533
|
}).catch((error) => {
|
|
534
|
+
const duration = Date.now() - startTime;
|
|
535
|
+
void appendCircularLog(
|
|
536
|
+
CIRCULAR_LOG_PATH,
|
|
537
|
+
formatLogEntry([], [String(error)], duration, false)
|
|
538
|
+
);
|
|
450
539
|
void writeDebug(`runAutoUpdate error: ${String(error)}`);
|
|
451
|
-
console.error("[opencode-plugin-auto-update] Update check failed:", error);
|
|
452
540
|
});
|
|
453
541
|
};
|
|
454
542
|
startupTimer = setTimeout(() => {
|
|
@@ -494,7 +582,7 @@ async function writeDebug(message) {
|
|
|
494
582
|
}
|
|
495
583
|
async function readLocalConfig() {
|
|
496
584
|
try {
|
|
497
|
-
const raw = await
|
|
585
|
+
const raw = await readFile4(CONFIG_PATH, "utf-8");
|
|
498
586
|
const parsed = JSON.parse(raw);
|
|
499
587
|
return parsed ?? {};
|
|
500
588
|
} catch {
|