plugin-updater 1.0.44 → 1.1.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 +13 -0
- package/dist/cli.js +10 -6
- package/dist/git.js +11 -10
- package/dist/index.js +10 -1
- package/dist/syncbridge.d.ts +1 -0
- package/dist/syncbridge.js +41 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,6 +41,19 @@ flowchart TD
|
|
|
41
41
|
end
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
## Cross-app plugin sync (`sync: true`)
|
|
45
|
+
|
|
46
|
+
A `plugins.json` entry flagged `sync: true` is mirrored into the **other** app's `plugins.json`, so a plugin enabled in OpenCode is also installed in Claude Code (and vice versa). At the start of `earlyLaunch`, plugin-updater loads [sync-bridge](https://github.com/intisy/sync-bridge)'s library bundle (`dist/lib.js`) and calls `syncPlugins()`, then re-reads the list so a freshly-synced-in plugin is cloned and built in the **same** launch. It is additive (never removes) and a no-op when sync-bridge isn't installed.
|
|
47
|
+
|
|
48
|
+
```jsonc
|
|
49
|
+
{ "name": "antigravity-auth", "url": "https://github.com/intisy/antigravity-auth", "enabled": true, "autoUpdate": false, "sync": true }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Set it from the CLI with `--sync`:
|
|
53
|
+
```bash
|
|
54
|
+
plugin-updater add https://github.com/intisy/antigravity-auth --sync
|
|
55
|
+
```
|
|
56
|
+
|
|
44
57
|
## API
|
|
45
58
|
|
|
46
59
|
| Method | Description |
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,8 @@ function parseArgs(argv) {
|
|
|
11
11
|
parsed.app = argv[++i];
|
|
12
12
|
else if (argv[i] === "--branch")
|
|
13
13
|
parsed.branch = argv[++i];
|
|
14
|
+
else if (argv[i] === "--sync")
|
|
15
|
+
parsed.sync = true;
|
|
14
16
|
else
|
|
15
17
|
parsed.urls.push(argv[i]);
|
|
16
18
|
}
|
|
@@ -93,7 +95,7 @@ function registerOpencodePlugin(configDir) {
|
|
|
93
95
|
fs.writeFileSync(ocPath, JSON.stringify(oc, null, 2), "utf8");
|
|
94
96
|
console.log(`Registered plugin-updater in ${ocPath}`);
|
|
95
97
|
}
|
|
96
|
-
function addPluginEntry(configDir, url, branch) {
|
|
98
|
+
function addPluginEntry(configDir, url, branch, sync) {
|
|
97
99
|
const cleanUrl = url.replace(/\.git$/, "");
|
|
98
100
|
const name = cleanUrl.split("/").pop() ?? cleanUrl;
|
|
99
101
|
ensurePluginsJson(configDir);
|
|
@@ -103,6 +105,8 @@ function addPluginEntry(configDir, url, branch) {
|
|
|
103
105
|
const entry = { name, url: cleanUrl, enabled: true, autoUpdate: true };
|
|
104
106
|
if (branch)
|
|
105
107
|
entry.branch = branch;
|
|
108
|
+
if (sync)
|
|
109
|
+
entry.sync = true;
|
|
106
110
|
entries.push(entry);
|
|
107
111
|
fs.writeFileSync(file, JSON.stringify(entries, null, 2), "utf8");
|
|
108
112
|
console.log(`Added ${name} to ${file}`);
|
|
@@ -117,8 +121,8 @@ function removePluginEntry(configDir, name) {
|
|
|
117
121
|
const entries = readJson(file) ?? [];
|
|
118
122
|
fs.writeFileSync(file, JSON.stringify(entries.filter((e) => e.name !== name), null, 2), "utf8");
|
|
119
123
|
}
|
|
120
|
-
async function setupEntry(updater, configDir, url, branch) {
|
|
121
|
-
const entry = addPluginEntry(configDir, url, branch);
|
|
124
|
+
async function setupEntry(updater, configDir, url, branch, sync) {
|
|
125
|
+
const entry = addPluginEntry(configDir, url, branch, sync);
|
|
122
126
|
console.log(`Setting up ${entry.name}...`);
|
|
123
127
|
try {
|
|
124
128
|
await updater.updatePluginPublic(entry.name, entry.url, entry.branch);
|
|
@@ -131,7 +135,7 @@ async function setupEntry(updater, configDir, url, branch) {
|
|
|
131
135
|
async function main() {
|
|
132
136
|
const parsed = parseArgs(process.argv.slice(2));
|
|
133
137
|
if (!["init", "add", "run", "remove"].includes(parsed.command)) {
|
|
134
|
-
console.log("usage: plugin-updater <init|add|remove|run> [git-urls-or-names...] [--app claude|opencode] [--branch name]");
|
|
138
|
+
console.log("usage: plugin-updater <init|add|remove|run> [git-urls-or-names...] [--app claude|opencode] [--branch name] [--sync]");
|
|
135
139
|
process.exit(parsed.command ? 1 : 0);
|
|
136
140
|
}
|
|
137
141
|
const app = detectApp(parsed.app);
|
|
@@ -148,7 +152,7 @@ async function main() {
|
|
|
148
152
|
else
|
|
149
153
|
registerOpencodePlugin(configDir);
|
|
150
154
|
for (const url of parsed.urls) {
|
|
151
|
-
await setupEntry(updater, configDir, url, parsed.branch);
|
|
155
|
+
await setupEntry(updater, configDir, url, parsed.branch, parsed.sync);
|
|
152
156
|
}
|
|
153
157
|
console.log("Init complete.");
|
|
154
158
|
}
|
|
@@ -156,7 +160,7 @@ async function main() {
|
|
|
156
160
|
if (parsed.urls.length === 0)
|
|
157
161
|
throw new Error("add requires at least one git url");
|
|
158
162
|
for (const url of parsed.urls) {
|
|
159
|
-
await setupEntry(updater, configDir, url, parsed.branch);
|
|
163
|
+
await setupEntry(updater, configDir, url, parsed.branch, parsed.sync);
|
|
160
164
|
}
|
|
161
165
|
}
|
|
162
166
|
else if (parsed.command === "remove") {
|
package/dist/git.js
CHANGED
|
@@ -63,19 +63,20 @@ export function updatePlugin(pluginName, gitUrl, branch, commitHash, updateInter
|
|
|
63
63
|
// this checkout — a loader running against a stale core/core-auth is the
|
|
64
64
|
// top cause of "looks broken but it's just stale". Rebuild if they moved.
|
|
65
65
|
if (fs.existsSync(path.join(targetDir, ".gitmodules"))) {
|
|
66
|
-
|
|
66
|
+
// Cheap LOCAL drift check first: `git submodule status` prefixes a line
|
|
67
|
+
// with '+' when the submodule is off its pinned commit and '-' when it is
|
|
68
|
+
// uninitialized. Only pay for the expensive recursive sync/update when one
|
|
69
|
+
// of those is true — otherwise this ran a full submodule update on every
|
|
70
|
+
// plugin every launch, which was the bulk of the startup delay.
|
|
71
|
+
let status = "";
|
|
67
72
|
try {
|
|
68
|
-
|
|
73
|
+
status = execSync("git submodule status --recursive", { cwd: targetDir }).toString();
|
|
69
74
|
}
|
|
70
75
|
catch { /* ignore */ }
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
after = execSync("git submodule status --recursive", { cwd: targetDir }).toString();
|
|
76
|
-
}
|
|
77
|
-
catch { /* ignore */ }
|
|
78
|
-
if (before !== after) {
|
|
76
|
+
const drifted = status.split("\n").some((line) => line.startsWith("+") || line.startsWith("-"));
|
|
77
|
+
if (drifted) {
|
|
78
|
+
executeGit("git submodule sync --recursive", targetDir);
|
|
79
|
+
executeGit("git submodule update --init --recursive", targetDir);
|
|
79
80
|
writeLog(`Fast-path: ${pluginName} submodules were out of sync — resynced, forcing rebuild`);
|
|
80
81
|
return { success: true, changed: true };
|
|
81
82
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { getPlugins, readOpencodeJson } from "./config.js";
|
|
|
4
4
|
import { selfUpdate, updateNpmPlugin } from "./npm.js";
|
|
5
5
|
import { updatePlugin } from "./git.js";
|
|
6
6
|
import { deployToExecutionDir } from "./deploy.js";
|
|
7
|
+
import { syncPluginsAcrossApps } from "./syncbridge.js";
|
|
7
8
|
import path from "path";
|
|
8
9
|
import fs from "fs";
|
|
9
10
|
// remove repos/ clones and deployed plugin/ files for plugins no longer in
|
|
@@ -56,6 +57,10 @@ export async function earlyLaunch(configDir, plugins) {
|
|
|
56
57
|
return {};
|
|
57
58
|
setEarlyLaunchConfigDir(configDir);
|
|
58
59
|
writeLog("Starting earlyLaunch updater sequence");
|
|
60
|
+
// pull in any `sync: true` plugins from the other app BEFORE building, then
|
|
61
|
+
// re-read the list so a freshly-synced-in plugin is cloned/built this pass.
|
|
62
|
+
await syncPluginsAcrossApps(configDir);
|
|
63
|
+
plugins = getPlugins(configDir);
|
|
59
64
|
selfUpdate(configDir);
|
|
60
65
|
// npm plugins listed in opencode.json
|
|
61
66
|
const { plugins: npmNames } = readOpencodeJson(configDir);
|
|
@@ -119,5 +124,9 @@ export async function activate(opencodeHookInput) {
|
|
|
119
124
|
}
|
|
120
125
|
// consumers like the loader TUI import this module for its API only — running
|
|
121
126
|
// the full updater sequence on import would print over their screen
|
|
122
|
-
if (process.env.PLUGIN_UPDATER_LIBRARY_MODE !== "1")
|
|
127
|
+
if (process.env.PLUGIN_UPDATER_LIBRARY_MODE !== "1") {
|
|
128
|
+
// signal loaders (which activate later in the same process) that we are
|
|
129
|
+
// self-driving updates, so their runEarlyLaunchHooks skips a duplicate pass
|
|
130
|
+
process.env.PLUGIN_UPDATER_ACTIVATION = "1";
|
|
123
131
|
activate();
|
|
132
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function syncPluginsAcrossApps(configDir: string): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { pathToFileURL } from "url";
|
|
4
|
+
import { writeLog } from "./log.js";
|
|
5
|
+
// sync-bridge is the only component allowed to span both app homes, so the
|
|
6
|
+
// cross-app plugin-list merge lives there. It ships its in-process API as a
|
|
7
|
+
// separate bundle (dist/lib.js) — the plugin hook (dist/index.js) deliberately
|
|
8
|
+
// exports nothing usable. We load that library from the cloned-plugin location
|
|
9
|
+
// where plugin-updater itself deploys git plugins.
|
|
10
|
+
function resolveSyncBridgeLib(configDir) {
|
|
11
|
+
const candidates = [
|
|
12
|
+
path.join(configDir, "repos", "sync-bridge", "dist", "lib.js"),
|
|
13
|
+
];
|
|
14
|
+
for (const candidate of candidates) {
|
|
15
|
+
if (fs.existsSync(candidate))
|
|
16
|
+
return candidate;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
// Mirror every plugins.json entry flagged `sync: true` into the other app's
|
|
21
|
+
// plugins.json. A no-op (logged, never thrown) when sync-bridge isn't installed
|
|
22
|
+
// or is an older version without syncPlugins.
|
|
23
|
+
export async function syncPluginsAcrossApps(configDir) {
|
|
24
|
+
const libPath = resolveSyncBridgeLib(configDir);
|
|
25
|
+
if (!libPath) {
|
|
26
|
+
writeLog("sync-bridge not installed; skipping cross-app plugin sync");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const bridge = (await import(pathToFileURL(libPath).href));
|
|
31
|
+
if (typeof bridge.syncPlugins !== "function") {
|
|
32
|
+
writeLog("sync-bridge has no syncPlugins (older version); skipping cross-app plugin sync", true);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const result = bridge.syncPlugins();
|
|
36
|
+
writeLog(`Cross-app plugin sync: ${JSON.stringify(result)}`);
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
writeLog(`Cross-app plugin sync failed: ${e.message}`, true);
|
|
40
|
+
}
|
|
41
|
+
}
|
package/dist/types.d.ts
CHANGED