plugin-updater 1.0.45 → 1.1.2
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 +22 -6
- package/dist/deploy.js +7 -0
- 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,10 +105,24 @@ 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}`);
|
|
109
113
|
}
|
|
114
|
+
else if (sync) {
|
|
115
|
+
// already present: honor --sync by enabling sync on the existing entry
|
|
116
|
+
const existing = entries.find((e) => e.name === name);
|
|
117
|
+
if (existing && existing.sync !== true) {
|
|
118
|
+
existing.sync = true;
|
|
119
|
+
fs.writeFileSync(file, JSON.stringify(entries, null, 2), "utf8");
|
|
120
|
+
console.log(`Enabled sync on ${name} in ${file}`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log(`${name} already present (sync on) in ${file}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
110
126
|
else {
|
|
111
127
|
console.log(`${name} already present in ${file}`);
|
|
112
128
|
}
|
|
@@ -117,8 +133,8 @@ function removePluginEntry(configDir, name) {
|
|
|
117
133
|
const entries = readJson(file) ?? [];
|
|
118
134
|
fs.writeFileSync(file, JSON.stringify(entries.filter((e) => e.name !== name), null, 2), "utf8");
|
|
119
135
|
}
|
|
120
|
-
async function setupEntry(updater, configDir, url, branch) {
|
|
121
|
-
const entry = addPluginEntry(configDir, url, branch);
|
|
136
|
+
async function setupEntry(updater, configDir, url, branch, sync) {
|
|
137
|
+
const entry = addPluginEntry(configDir, url, branch, sync);
|
|
122
138
|
console.log(`Setting up ${entry.name}...`);
|
|
123
139
|
try {
|
|
124
140
|
await updater.updatePluginPublic(entry.name, entry.url, entry.branch);
|
|
@@ -131,7 +147,7 @@ async function setupEntry(updater, configDir, url, branch) {
|
|
|
131
147
|
async function main() {
|
|
132
148
|
const parsed = parseArgs(process.argv.slice(2));
|
|
133
149
|
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]");
|
|
150
|
+
console.log("usage: plugin-updater <init|add|remove|run> [git-urls-or-names...] [--app claude|opencode] [--branch name] [--sync]");
|
|
135
151
|
process.exit(parsed.command ? 1 : 0);
|
|
136
152
|
}
|
|
137
153
|
const app = detectApp(parsed.app);
|
|
@@ -148,7 +164,7 @@ async function main() {
|
|
|
148
164
|
else
|
|
149
165
|
registerOpencodePlugin(configDir);
|
|
150
166
|
for (const url of parsed.urls) {
|
|
151
|
-
await setupEntry(updater, configDir, url, parsed.branch);
|
|
167
|
+
await setupEntry(updater, configDir, url, parsed.branch, parsed.sync);
|
|
152
168
|
}
|
|
153
169
|
console.log("Init complete.");
|
|
154
170
|
}
|
|
@@ -156,7 +172,7 @@ async function main() {
|
|
|
156
172
|
if (parsed.urls.length === 0)
|
|
157
173
|
throw new Error("add requires at least one git url");
|
|
158
174
|
for (const url of parsed.urls) {
|
|
159
|
-
await setupEntry(updater, configDir, url, parsed.branch);
|
|
175
|
+
await setupEntry(updater, configDir, url, parsed.branch, parsed.sync);
|
|
160
176
|
}
|
|
161
177
|
}
|
|
162
178
|
else if (parsed.command === "remove") {
|
package/dist/deploy.js
CHANGED
|
@@ -95,6 +95,13 @@ export async function deployToExecutionDir(pluginName, executionPath, changed, c
|
|
|
95
95
|
else if (fs.existsSync(path.join(distPath, "index.js"))) {
|
|
96
96
|
deploySource = path.join(distPath, "index.js");
|
|
97
97
|
}
|
|
98
|
+
// the build may have produced nothing (e.g. it failed, or the repo was deployed
|
|
99
|
+
// bundle-only with its source stripped) — skip gracefully rather than throwing
|
|
100
|
+
// ENOENT on the copy. Any already-deployed plugin/<name>.js stays in place.
|
|
101
|
+
if (!fs.existsSync(deploySource)) {
|
|
102
|
+
writeLog(`Skipping deploy for ${pluginName}: built file not found at ${deploySource}`, true);
|
|
103
|
+
return fs.existsSync(pluginExecutionFile);
|
|
104
|
+
}
|
|
98
105
|
if (!fs.existsSync(executionPath))
|
|
99
106
|
fs.mkdirSync(executionPath, { recursive: true });
|
|
100
107
|
await callPluginCleanup(pluginExecutionFile, configDir);
|
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